Repository: Azure/draft
Pull request #327 (2024-07-02 22:15:21)

Create workflow from draft config
# Description

This PR attempts an extensive rework of how Draft populates its templates. Rather than using a map of customInputs, flags and prompts will instead directly populate the DraftConfig struct which will be used to populate the template.

Fixes # (issue)
Feature # (details)

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Is it a  breaking change which will impact consuming tool(s).

- [ ] Test A
- [ ] Test B


# Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules



squillace: Elsewise... what an amazing improvement. AMAZING!!!
fibonacci1729: 1. I agree with the adding the "http(s)" prefix.
2. Would an optional `--open` flag (default to false) be sufficient?
bacongobbler: I completely agree this would be a fantastic feature for web apps. The problem is that Draft does not deploy strictly web-facing applications, as some apps (like draftd or gitlab) are not HTTP based. In fact they're gRPC or SSH endpoints.

In kubernetes the port type can be either "TCP" (the default, such as all the example apps) or "UDP". We could attach that to the local address displayed on `draft connect`, but that still wouldn't be very useful to the webapp developer. That would get us closer to the target requirement, though.
radu-matei: Reiterating through @squillace's comments on #422 (and posting in the correct issue...):

> 1. We could detect port 80 and prefix based on that.
> 2. We could spawn the location, but then add a "if it's a web app, click http://localhost:xxxx; otherwise, open the endpoint using the appropriate tool."
> 3. We could add some metadata in the draft.toml file for this, and prompt the user to switch it off/on?
> 4. ....uh....
I'm inclined to go with 2 or 3... thoughts?
bacongobbler: I wouldn't mind option 2!
bacongobbler: I'm leaning more and more towards just making the default protocol for `draft connect` set to `http://`. Even if the underlying application protocol isn't http, the user knows that and will handle the connection string accordingly. It's a nice enhancement for users who DO write web applications with Draft (which one could argue is probably the large majority of our userbase).

Patch:
diff --git a/.github/workflows/integration-linux.yml b/.github/workflows/integration-linux.yml
index 8d09293..dc4e6bd 100644
--- a/.github/workflows/integration-linux.yml
+++ b/.github/workflows/integration-linux.yml
@@ -139,7 +139,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -273,7 +273,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -398,7 +398,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -577,7 +577,7 @@ jobs:
           curl -m 3 $SERVICEIP:8080
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -711,7 +711,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:8080
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -836,7 +836,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:8080
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -1015,7 +1015,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -1149,7 +1149,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -1274,7 +1274,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -1453,7 +1453,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -1587,7 +1587,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -1712,7 +1712,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -1891,7 +1891,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -2025,7 +2025,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -2150,7 +2150,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -2329,7 +2329,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -2463,7 +2463,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -2588,7 +2588,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -2767,7 +2767,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -2901,7 +2901,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -3026,7 +3026,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -3205,7 +3205,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -3339,7 +3339,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -3464,7 +3464,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -3643,7 +3643,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -3777,7 +3777,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -3902,7 +3902,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -4081,7 +4081,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -4215,7 +4215,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -4340,7 +4340,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -4519,7 +4519,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -4653,7 +4653,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -4778,7 +4778,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -4957,7 +4957,7 @@ jobs:
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -5091,7 +5091,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -5216,7 +5216,7 @@ jobs:
           echo 'Curling service IP'
           curl -m 3 $SERVICEIP:80
           kill $tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
diff --git a/Dockerfile b/Dockerfile
index 1f55630..7b2d1b6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -20,4 +20,4 @@ COPY . ./
 RUN make go-generate
 
 RUN go mod download
-ENTRYPOINT ["go"]
+ENTRYPOINT ["go"]
\ No newline at end of file
diff --git a/cmd/create.go b/cmd/create.go
index 4f57204..365d4fb 100644
--- a/cmd/create.go
+++ b/cmd/create.go
@@ -7,7 +7,6 @@ import (
 	"os"
 	"strings"
 
-	"golang.org/x/exp/maps"
 	"gopkg.in/yaml.v3"
 
 	"github.com/Azure/draft/pkg/reporeader"
@@ -41,7 +40,6 @@ const emptyDefaultFlagValue = ""
 const currentDirDefaultFlagValue = "."
 
 type createCmd struct {
-	appName    string
 	lang       string
 	dest       string
 	deployType string
@@ -79,14 +77,13 @@ func newCreateCmd() *cobra.Command {
 	f := cmd.Flags()
 
 	f.StringVarP(&cc.createConfigPath, "create-config", "c", emptyDefaultFlagValue, "specify the path to the configuration file")
-	f.StringVarP(&cc.appName, "app", "a", emptyDefaultFlagValue, "specify the name of the helm release")
 	f.StringVarP(&cc.lang, "language", "l", emptyDefaultFlagValue, "specify the language used to create the Kubernetes deployment")
 	f.StringVarP(&cc.dest, "destination", "d", currentDirDefaultFlagValue, "specify the path to the project directory")
-	f.StringVarP(&cc.deployType, "deploy-type", "", emptyDefaultFlagValue, "specify deployement type (eg. helm, kustomize, manifests)")
+	f.StringVarP(&cc.deployType, "deploy-type", "", emptyDefaultFlagValue, "specify deployment type (eg. helm, kustomize, manifests)")
 	f.BoolVar(&cc.dockerfileOnly, "dockerfile-only", false, "only create Dockerfile in the project directory")
 	f.BoolVar(&cc.deploymentOnly, "deployment-only", false, "only create deployment files in the project directory")
 	f.BoolVar(&cc.skipFileDetection, "skip-file-detection", false, "skip file detection step")
-	f.StringArrayVarP(&cc.flagVariables, "variable", "", []string{}, "pass additional variables using repeated --variable flag")
+	f.StringArrayVarP(&cc.flagVariables, "variable", "", []string{}, "pass template variables (e.g. --variable PORT=8080 --variable APPNAME=test)")
 
 	return cmd
 }
@@ -116,14 +113,7 @@ func (cc *createCmd) initConfig() error {
 func (cc *createCmd) run() error {
 	log.Debugf("config: %s", cc.createConfigPath)
 
-	for _, flagVar := range cc.flagVariables {
-		flagVarName, flagVarValue, ok := strings.Cut(flagVar, "=")
-		if !ok {
-			return fmt.Errorf("invalid variable format: %s", flagVar)
-		}
-		flagVariablesMap[flagVarName] = flagVarValue
-		log.Debugf("flag variable %s=%s", flagVarName, flagVarValue)
-	}
+	flagVariablesMap = flagVariablesToMap(cc.flagVariables)
 
 	var dryRunRecorder *dryrunpkg.DryRunRecorder
 	if dryRun {
@@ -276,7 +266,7 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan
 			}
 		}
 		if !variableExists {
-			langConfig.Variables = append(langConfig.Variables, config.BuilderVar{
+			langConfig.Variables = append(langConfig.Variables, &config.BuilderVar{
 				Name: k,
 				Default: config.BuilderVarDefault{
 					Value: v,
@@ -285,56 +275,53 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan
 		}
 	}
 
-	var inputs map[string]string
 	if cc.createConfig.LanguageVariables == nil {
-		inputs, err = prompts.RunPromptsFromConfigWithSkips(langConfig, maps.Keys(flagVariablesMap))
-		if err != nil {
+		langConfig.VariableMapToDraftConfig(flagVariablesMap)
+
+		if err = prompts.RunPromptsFromConfigWithSkips(langConfig); err != nil {
 			return err
 		}
 	} else {
-		inputs, err = validateConfigInputsToPrompts(langConfig, cc.createConfig.LanguageVariables)
+		err = validateConfigInputsToPrompts(langConfig, cc.createConfig.LanguageVariables)
 		if err != nil {
 			return err
 		}
 	}
 
 	if cc.templateVariableRecorder != nil {
-		for k, v := range inputs {
-			cc.templateVariableRecorder.Record(k, v)
+		for _, variable := range langConfig.Variables {
+			cc.templateVariableRecorder.Record(variable.Name, variable.Value)
 		}
 	}
 
-	maps.Copy(inputs, flagVariablesMap)
-
-	if err = cc.supportedLangs.CreateDockerfileForLanguage(lowerLang, inputs, cc.templateWriter); err != nil {
+	if err = cc.supportedLangs.CreateDockerfileForLanguage(lowerLang, langConfig, cc.templateWriter); err != nil {
 		return fmt.Errorf("there was an error when creating the Dockerfile for language %s: %w", cc.createConfig.LanguageType, err)
 	}
 
 	log.Info("--> Creating Dockerfile...\n")
-	return err
+	return nil
 }
 
 func (cc *createCmd) createDeployment() error {
 	log.Info("--- Deployment File Creation ---")
 	d := deployments.CreateDeploymentsFromEmbedFS(template.Deployments, cc.dest)
 	var deployType string
-	var customInputs map[string]string
+	var deployConfig *config.DraftConfig
 	var err error
 
 	if cc.createConfig.DeployType != "" {
 		deployType = strings.ToLower(cc.createConfig.DeployType)
-		deployConfig, err := d.GetConfig(deployType)
+		deployConfig, err = d.GetConfig(deployType)
 		if err != nil {
 			return err
 		}
 		if deployConfig == nil {
 			return errors.New("invalid deployment type")
 		}
-		customInputs, err = validateConfigInputsToPrompts(deployConfig, cc.createConfig.DeployVariables)
+		err = validateConfigInputsToPrompts(deployConfig, cc.createConfig.DeployVariables)
 		if err != nil {
 			return err
 		}
-
 	} else {
 		if cc.deployType == "" {
 			selection := &promptui.Select{
@@ -350,27 +337,28 @@ func (cc *createCmd) createDeployment() error {
 			deployType = cc.deployType
 		}
 
-		deployConfig, err := d.GetConfig(deployType)
+		deployConfig, err = d.GetConfig(deployType)
 		if err != nil {
 			return err
 		}
-		customInputs, err = prompts.RunPromptsFromConfigWithSkips(deployConfig, maps.Keys(flagVariablesMap))
+
+		deployConfig.VariableMapToDraftConfig(flagVariablesMap)
+
+		err = prompts.RunPromptsFromConfigWithSkips(deployConfig)
 		if err != nil {
 			return err
 		}
 	}
 
-	maps.Copy(customInputs, flagVariablesMap)
-
 	if cc.templateVariableRecorder != nil {
-		for k, v := range customInputs {
-			cc.templateVariableRecorder.Record(k, v)
+		for _, variable := range deployConfig.Variables {
+			cc.templateVariableRecorder.Record(variable.Name, variable.Value)
 		}
 	}
 
 	log.Infof("--> Creating %s Kubernetes resources...\n", deployType)
 
-	return d.CopyDeploymentFiles(deployType, customInputs, cc.templateWriter)
+	return d.CopyDeploymentFiles(deployType, deployConfig, cc.templateWriter)
 }
 
 func (cc *createCmd) createFiles(detectedLang *config.DraftConfig, lowerLang string) error {
@@ -464,26 +452,11 @@ func init() {
 	rootCmd.AddCommand(newCreateCmd())
 }
 
-func validateConfigInputsToPrompts(draftConfig *config.DraftConfig, provided []UserInputs) (map[string]string, error) {
-	customInputs := make(map[string]string)
-
+func validateConfigInputsToPrompts(draftConfig *config.DraftConfig, provided []UserInputs) error {
 	// set inputs to provided values
-	for _, variable := range provided {
-		customInputs[variable.Name] = variable.Value
-	}
-
-	if err := draftConfig.ApplyDefaultVariables(customInputs); err != nil {
-		return nil, fmt.Errorf("validate config inputs to prompts: %w", err)
+	for _, providedVar := range provided {
+		draftConfig.SetVariable(providedVar.Name, providedVar.Value)
 	}
 
-	for _, variable := range draftConfig.Variables {
-		value, ok := customInputs[variable.Name]
-		if !ok {
-			return nil, fmt.Errorf("config missing required variable: %s with description: %s", variable.Name, variable.Description)
-		} else if value == "" {
-			return nil, fmt.Errorf("value for variable %s is empty", variable.Name)
-		}
-	}
-
-	return customInputs, nil
+	return nil
 }
diff --git a/cmd/generate-workflow.go b/cmd/generate-workflow.go
index 0e73a5f..463a00e 100644
--- a/cmd/generate-workflow.go
+++ b/cmd/generate-workflow.go
@@ -7,7 +7,6 @@ import (
 	"github.com/manifoldco/promptui"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
-	"golang.org/x/exp/maps"
 
 	"github.com/Azure/draft/pkg/prompts"
 	"github.com/Azure/draft/pkg/templatewriter"
@@ -17,15 +16,12 @@ import (
 )
 
 type generateWorkflowCmd struct {
-	workflowConfig workflows.WorkflowConfig
 	dest           string
 	deployType     string
 	flagVariables  []string
 	templateWriter templatewriter.TemplateWriter
 }
 
-var flagValuesMap map[string]string
-
 func newGenerateWorkflowCmd() *cobra.Command {
 
 	gwCmd := &generateWorkflowCmd{}
@@ -36,12 +32,8 @@ func newGenerateWorkflowCmd() *cobra.Command {
 		Long: `This command will generate a Github workflow to build and deploy an application containerized 
 with draft on AKS. This command assumes the 'setup-gh' command has been run properly.`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			flagValuesMap = make(map[string]string)
-			if cmd.Flags().NFlag() != 0 {
-				flagValuesMap = gwCmd.workflowConfig.SetFlagValuesToMap()
-			}
 			log.Info("--> Generating Github workflow")
-			if err := gwCmd.generateWorkflows(gwCmd.dest, gwCmd.deployType, gwCmd.flagVariables, gwCmd.templateWriter, flagValuesMap); err != nil {
+			if err := gwCmd.generateWorkflows(); err != nil {
 				return err
 			}
 
@@ -52,20 +44,10 @@ with draft on AKS. This command assumes the 'setup-gh' command has been run prop
 	}
 
 	f := cmd.Flags()
+
 	f.StringVarP(&gwCmd.dest, "destination", "d", currentDirDefaultFlagValue, "specify the path to the project directory")
-	f.StringVar(&gwCmd.deployType, "deploy-type", emptyDefaultFlagValue, "specify the type of deployment")
-	f.StringVarP(&gwCmd.workflowConfig.WorkflowName, "workflow", "w", emptyDefaultFlagValue, "specify the Github workflow name")
-	f.StringVarP(&gwCmd.workflowConfig.BranchName, "branch", "b", emptyDefaultFlagValue, "specify the Github branch to automatically deploy from")
-	f.StringVar(&gwCmd.workflowConfig.AcrResourceGroup, "acr-resource-group", emptyDefaultFlagValue, "specify the Azure container registry resource group")
-	f.StringVarP(&gwCmd.workflowConfig.AcrName, "registry-name", "r", emptyDefaultFlagValue, "specify the Azure container registry name")
-	f.StringVar(&gwCmd.workflowConfig.ContainerName, "container-name", emptyDefaultFlagValue, "specify the container image name")
-	f.StringVarP(&gwCmd.workflowConfig.ClusterResourceGroup, "cluster-resource-group", "g", emptyDefaultFlagValue, "specify the Azure resource group of your AKS cluster")
-	f.StringVarP(&gwCmd.workflowConfig.ClusterName, "cluster-name", "c", emptyDefaultFlagValue, "specify the AKS cluster name")
-	f.StringVar(&gwCmd.workflowConfig.Dockerfile, "dockerfile", emptyDefaultFlagValue, "specify the path to the Dockerfile")
-	f.StringVarP(&gwCmd.workflowConfig.BuildContextPath, "build-context-path", "x", emptyDefaultFlagValue, "specify the docker build context path")
-	f.StringVarP(&gwCmd.workflowConfig.Namespace, "namespace", "n", emptyDefaultFlagValue, "specify the Kubernetes namespace")
-	f.StringVar(&gwCmd.workflowConfig.PrivateCluster, "private-cluster", emptyDefaultFlagValue, "specify if the AKS cluster is private")
-	f.StringArrayVarP(&gwCmd.flagVariables, "variable", "", []string{}, "pass additional variables")
+	f.StringVarP(&gwCmd.deployType, "deploy-type", "", "", "specify the k8s deployment type (helm, kustomize, manifests)")
+	f.StringArrayVarP(&gwCmd.flagVariables, "variable", "", []string{}, "pass template variables (e.g. --variable CLUSTERNAME=testCluster --variable DOCKERFILE=./Dockerfile)")
 	gwCmd.templateWriter = &writers.LocalFSWriter{}
 	return cmd
 }
@@ -74,44 +56,50 @@ func init() {
 	rootCmd.AddCommand(newGenerateWorkflowCmd())
 }
 
-func (gwc *generateWorkflowCmd) generateWorkflows(dest string, deployType string, flagVariables []string, templateWriter templatewriter.TemplateWriter, flagValuesMap map[string]string) error {
-	if flagValuesMap == nil {
-		return fmt.Errorf("flagValuesMap is nil")
-	}
+func (gwc *generateWorkflowCmd) generateWorkflows() error {
 	var err error
-	for _, flagVar := range flagVariables {
-		flagVarName, flagVarValue, ok := strings.Cut(flagVar, "=")
-		if !ok {
-			return fmt.Errorf("invalid variable format: %s", flagVar)
-		}
-		flagValuesMap[flagVarName] = flagVarValue
-		log.Debugf("flag variable %s=%s", flagVarName, flagVarValue)
-	}
 
-	if deployType == "" {
+	flagVariablesMap = flagVariablesToMap(gwc.flagVariables)
+
+	if gwc.deployType == "" {
 		selection := &promptui.Select{
 			Label: "Select k8s Deployment Type",
 			Items: []string{"helm", "kustomize", "manifests"},
 		}
 
-		_, deployType, err = selection.Run()
+		_, gwc.deployType, err = selection.Run()
 		if err != nil {
 			return err
 		}
 	}
 
-	workflow := workflows.CreateWorkflowsFromEmbedFS(template.Workflows, dest)
-	workflowConfig, err := workflow.GetConfig(deployType)
+	workflow := workflows.CreateWorkflowsFromEmbedFS(template.Workflows, gwc.dest)
+	draftConfig, err := workflow.GetConfig(gwc.deployType)
 	if err != nil {
 		return fmt.Errorf("get config: %w", err)
 	}
 
-	customInputs, err := prompts.RunPromptsFromConfigWithSkips(workflowConfig, maps.Keys(flagValuesMap))
-	if err != nil {
+	draftConfig.VariableMapToDraftConfig(flagVariablesMap)
+
+	if err = prompts.RunPromptsFromConfigWithSkips(draftConfig); err != nil {
 		return err
 	}
 
-	maps.Copy(customInputs, flagValuesMap)
+	if err := workflows.UpdateProductionDeployments(gwc.deployType, gwc.dest, draftConfig, gwc.templateWriter); err != nil {
+		return fmt.Errorf("update production deployments: %w", err)
+	}
+
+	return workflow.CreateWorkflowFiles(gwc.deployType, draftConfig, gwc.templateWriter)
+}
 
-	return workflow.CreateWorkflowFiles(deployType, customInputs, templateWriter)
+func flagVariablesToMap(flagVariables []string) map[string]string {
+	flagVariablesMap := make(map[string]string)
+	for _, flagVar := range flagVariables {
+		flagVarName, flagVarValue, ok := strings.Cut(flagVar, "=")
+		if !ok {
+			log.Fatalf("invalid variable format: %s", flagVar)
+		}
+		flagVariablesMap[flagVarName] = flagVarValue
+	}
+	return flagVariablesMap
 }
diff --git a/cmd/update.go b/cmd/update.go
index 6509c3b..148f86d 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -3,9 +3,9 @@ package cmd
 import (
 	"embed"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"os"
-	"strings"
 
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -23,7 +23,6 @@ type updateCmd struct {
 	provider                 string
 	addon                    string
 	flagVariables            []string
-	userInputs               map[string]string
 	templateWriter           templatewriter.TemplateWriter
 	addonFS                  embed.FS
 	templateVariableRecorder config.TemplateVariableRecorder
@@ -51,7 +50,7 @@ func newUpdateCmd() *cobra.Command {
 	f.StringVarP(&uc.dest, "destination", "d", ".", "specify the path to the project directory")
 	f.StringVarP(&uc.provider, "provider", "p", "azure", "cloud provider")
 	f.StringVarP(&uc.addon, "addon", "a", "", "addon name")
-	f.StringArrayVarP(&uc.flagVariables, "variable", "", []string{}, "pass a variable non-interactively (ex: --variable foo=bar)")
+	f.StringArrayVarP(&uc.flagVariables, "variable", "", []string{}, "pass template variables (e.g. --variable ingress-tls-cert-keyvault-uri=test.uri ingress-host=host)")
 
 	uc.templateWriter = &writers.LocalFSWriter{}
 
@@ -59,15 +58,7 @@ func newUpdateCmd() *cobra.Command {
 }
 
 func (uc *updateCmd) run() error {
-	flagVariablesMap := make(map[string]string)
-	for _, flagVar := range uc.flagVariables {
-		flagVarName, flagVarValue, ok := strings.Cut(flagVar, "=")
-		if !ok {
-			return fmt.Errorf("invalid variable format: %s", flagVar)
-		}
-		flagVariablesMap[flagVarName] = flagVarValue
-		log.Debugf("flag variable %s=%s", flagVarName, flagVarValue)
-	}
+	flagVariablesMap = flagVariablesToMap(uc.flagVariables)
 
 	if uc.addon == "" {
 		addon, err := addons.PromptAddon(template.Addons, uc.provider)
@@ -82,22 +73,27 @@ func (uc *updateCmd) run() error {
 		return err
 	}
 
-	uc.userInputs, err = addons.PromptAddonValues(uc.dest, flagVariablesMap, addonConfig)
+	if addonConfig.DraftConfig == nil {
+		return errors.New("DraftConfig is nil")
+	} else {
+		addonConfig.DraftConfig.VariableMapToDraftConfig(flagVariablesMap)
+	}
+
+	err = addons.PromptAddonValues(uc.dest, &addonConfig)
 	if err != nil {
 		return err
 	}
-	log.Debugf("addonInputs is: %s", uc.userInputs)
 
 	if dryRun {
 		dryRunRecorder = dryrunpkg.NewDryRunRecorder()
 		uc.templateVariableRecorder = dryRunRecorder
 		uc.templateWriter = dryRunRecorder
-		for k, v := range uc.userInputs {
-			uc.templateVariableRecorder.Record(k, v)
+		for _, variable := range addonConfig.DraftConfig.Variables {
+			uc.templateVariableRecorder.Record(variable.Name, variable.Value)
 		}
 	}
 
-	err = addons.GenerateAddon(template.Addons, uc.provider, uc.addon, uc.dest, uc.userInputs, uc.templateWriter)
+	err = addons.GenerateAddon(template.Addons, uc.provider, uc.addon, uc.dest, addonConfig, uc.templateWriter)
 
 	if dryRun {
 		dryRunText, err := json.MarshalIndent(dryRunRecorder.DryRunInfo, "", TWO_SPACES)
diff --git a/example/deployment.go b/example/deployment.go
index 6ded008..42ed7d5 100644
--- a/example/deployment.go
+++ b/example/deployment.go
@@ -3,6 +3,7 @@ package example
 import (
 	"fmt"
 
+	"github.com/Azure/draft/pkg/config"
 	"github.com/Azure/draft/pkg/deployments"
 	"github.com/Azure/draft/pkg/templatewriter"
 	"github.com/Azure/draft/pkg/templatewriter/writers"
@@ -10,10 +11,10 @@ import (
 )
 
 // WriteDeploymentFiles generates Deployment Files using Draft, writing to a Draft TemplateWriter. See the corresponding draft.yaml file in templates/deployments/[deployType] for the template inputs.
-func WriteDeploymentFiles(w templatewriter.TemplateWriter, deploymentOutputPath string, deploymentInputs map[string]string, deploymentType string) error {
+func WriteDeploymentFiles(w templatewriter.TemplateWriter, deploymentOutputPath string, deployConfig *config.DraftConfig, deploymentType string) error {
 	d := deployments.CreateDeploymentsFromEmbedFS(template.Deployments, deploymentOutputPath)
 
-	err := d.CopyDeploymentFiles(deploymentType, deploymentInputs, w)
+	err := d.CopyDeploymentFiles(deploymentType, deployConfig, w)
 	if err != nil {
 		return fmt.Errorf("failed to generate manifest: %e", err)
 	}
@@ -33,21 +34,72 @@ func WriteDeploymentFilesExample() error {
 	// Select the deployment type to generate the files for (must correspond to a directory in the template/deployments directory)
 	deploymentType := "manifests"
 
-	// Create a map of inputs to the template (must correspond to the inputs in the template/deployments/<deploymentType>/draft.yaml file)
-	deploymentInputs := map[string]string{
-		"PORT":        "8080",
-		"APPNAME":     "example-app",
-		"SERVICEPORT": "8080",
-		"NAMESPACE":   "example-namespace",
-		"IMAGENAME":   "example-image",
-		"IMAGETAG":    "latest",
+	// Create a DraftConfig of inputs to the template (must correspond to the inputs in the template/deployments/<deploymentType>/draft.yaml files)
+	deployConfig := &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name: "PORT",
+				Default: config.BuilderVarDefault{
+					Value: "80",
+				},
+				Description: "the port exposed in the application",
+				Value:       "8080",
+			},
+			{
+				Name:        "APPNAME",
+				Description: "the name of the application",
+				Value:       "example-app",
+			},
+			{
+				Name: "SERVICEPORT",
+				Default: config.BuilderVarDefault{
+					ReferenceVar: "PORT",
+				},
+				Description: "the port the service uses to make the application accessible from outside the cluster",
+				Value:       "8080",
+			},
+			{
+				Name: "NAMESPACE",
+				Default: config.BuilderVarDefault{
+					Value: "default",
+				},
+				Description: "the name of the image to use in the deployment",
+				Value:       "example-namespace",
+			},
+			{
+				Name: "IMAGENAME",
+				Default: config.BuilderVarDefault{
+					IsPromptDisabled: true,
+					Value:            "the name of the image to use in the deployment",
+				},
+				Description: "the name of the image to use in the deployment",
+				Value:       "example-image",
+			},
+			{
+				Name: "IMAGETAG",
+				Default: config.BuilderVarDefault{
+					IsPromptDisabled: true,
+					Value:            "latest",
+				},
+				Description: "the tag of the image to use in the deployment",
+				Value:       "latest",
+			},
+			{
+				Name: "GENERATORLABEL",
+				Default: config.BuilderVarDefault{
+					IsPromptDisabled: true,
+					Value:            "draft",
+				},
+				Description: "the label to use to identify the deployment as generated by draft",
+			},
+		},
 	}
 
 	// Set the output path for the deployment files
 	outputPath := "./"
 
 	// Write the deployment files
-	err := WriteDeploymentFiles(&w, outputPath, deploymentInputs, deploymentType)
+	err := WriteDeploymentFiles(&w, outputPath, deployConfig, deploymentType)
 	if err != nil {
 		return err
 	}
diff --git a/example/dockerfile.go b/example/dockerfile.go
index c6cd678..0667028 100644
--- a/example/dockerfile.go
+++ b/example/dockerfile.go
@@ -3,6 +3,7 @@ package example
 import (
 	"fmt"
 
+	"github.com/Azure/draft/pkg/config"
 	"github.com/Azure/draft/pkg/languages"
 	"github.com/Azure/draft/pkg/templatewriter"
 	"github.com/Azure/draft/pkg/templatewriter/writers"
@@ -10,10 +11,10 @@ import (
 )
 
 // WriteDockerfile generates a Dockerfile and dockerignore using Draft, writing to a Draft TemplateWriter. See the corresponding draft.yaml file in templates/dockerfiles/[language] for the template inputs.
-func WriteDockerfile(w templatewriter.TemplateWriter, dockerfileOutputPath string, dockerfileInputs map[string]string, generationLanguage string) error {
+func WriteDockerfile(w templatewriter.TemplateWriter, dockerfileOutputPath string, langConfig *config.DraftConfig, generationLanguage string) error {
 	l := languages.CreateLanguagesFromEmbedFS(template.Dockerfiles, dockerfileOutputPath)
 
-	err := l.CreateDockerfileForLanguage(generationLanguage, dockerfileInputs, w)
+	err := l.CreateDockerfileForLanguage(generationLanguage, langConfig, w)
 	if err != nil {
 		return fmt.Errorf("failed to generate dockerfile: %e", err)
 	}
@@ -33,17 +34,25 @@ func WriteDockerfileExample() error {
 	// Select the language to generate the Dockerfile for (must correspond to a directory in the template/dockerfiles directory)
 	generationLanguage := "go"
 
-	// Create a map of inputs to the template (must correspond to the inputs in the template/dockerfiles/<language>/draft.yaml file)
-	dockerfileInputs := map[string]string{
-		"PORT":    "8080",
-		"VERSION": "1.20",
+	// Create a DraftConfig of inputs to the template (must correspond to the inputs in the template/dockerfiles/<language>/draft.yaml files)
+	langConfig := &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "PORT",
+				Value: "8080",
+			},
+			{
+				Name:  "VERSION",
+				Value: "1.20",
+			},
+		},
 	}
 
 	// Set the output path for the Dockerfile
 	outputPath := "./"
 
 	// Write the Dockerfile
-	err := WriteDockerfile(&w, outputPath, dockerfileInputs, generationLanguage)
+	err := WriteDockerfile(&w, outputPath, langConfig, generationLanguage)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/addons/addon_config.go b/pkg/addons/addon_config.go
index f98d9b3..2b13c78 100644
--- a/pkg/addons/addon_config.go
+++ b/pkg/addons/addon_config.go
@@ -21,7 +21,7 @@ import (
 // AddonConfig is a struct that extends the base DraftConfig to allow for the Referencing previously generated
 // k8s objects. This allows an addon creator to reference pre-entered data from the deployment files.
 type AddonConfig struct {
-	config.DraftConfig  `yaml:",inline"`
+	*config.DraftConfig `yaml:",inline"`
 	ReferenceComponents map[string][]referenceResource `yaml:"references"`
 
 	deployType string
diff --git a/pkg/addons/addons.go b/pkg/addons/addons.go
index 584e0d7..4292552 100644
--- a/pkg/addons/addons.go
+++ b/pkg/addons/addons.go
@@ -23,25 +23,24 @@ var (
 	parentDirName = "addons"
 )
 
-func GenerateAddon(addons embed.FS, provider, addon, dest string, userInputs map[string]string, templateWriter templatewriter.TemplateWriter) error {
-	addOnConfig, err := GetAddonConfig(addons, provider, addon)
-	if err != nil {
-		return err
-	}
-
+func GenerateAddon(addons embed.FS, provider, addon, dest string, addonConfig AddonConfig, templateWriter templatewriter.TemplateWriter) error {
 	selectedAddonPath, err := GetAddonPath(addons, provider, addon)
 	if err != nil {
 		return err
 	}
 
-	addonDestPath, err := addOnConfig.GetAddonDestPath(dest)
+	addonDestPath, err := addonConfig.GetAddonDestPath(dest)
 	if err != nil {
 		return err
 	}
 
-	addOnConfig.ApplyDefaultVariables(userInputs)
+	if addonConfig.DraftConfig == nil {
+		return errors.New("DraftConfig is nil")
+	} else {
+		addonConfig.DraftConfig.ApplyDefaultVariables()
+	}
 
-	if err = osutil.CopyDir(addons, selectedAddonPath, addonDestPath, &addOnConfig.DraftConfig, userInputs, templateWriter); err != nil {
+	if err = osutil.CopyDir(addons, selectedAddonPath, addonDestPath, addonConfig.DraftConfig, templateWriter); err != nil {
 		return err
 	}
 
@@ -71,19 +70,19 @@ func GetAddonConfig(addons embed.FS, provider, addon string) (AddonConfig, error
 		return AddonConfig{}, err
 	}
 
-	addOnConfigPath := path.Join(selectedAddonPath, "draft.yaml")
-	log.Debugf("addOnConfig is: %s", addOnConfigPath)
+	addonConfigPath := path.Join(selectedAddonPath, "draft.yaml")
+	log.Debugf("addonConfig is: %s", addonConfigPath)
 
-	configBytes, err := fs.ReadFile(addons, addOnConfigPath)
+	configBytes, err := fs.ReadFile(addons, addonConfigPath)
 	if err != nil {
 		return AddonConfig{}, err
 	}
-	var addOnConfig AddonConfig
-	if err = yaml.Unmarshal(configBytes, &addOnConfig); err != nil {
+	var addonConfig AddonConfig
+	if err = yaml.Unmarshal(configBytes, &addonConfig); err != nil {
 		return AddonConfig{}, err
 	}
 
-	return addOnConfig, nil
+	return addonConfig, nil
 }
 
 func PromptAddon(addons embed.FS, provider string) (string, error) {
@@ -106,38 +105,29 @@ func PromptAddon(addons embed.FS, provider string) (string, error) {
 	return addon, nil
 }
 
-func PromptAddonValues(dest string, userInputs map[string]string, addOnConfig AddonConfig) (map[string]string, error) {
-	log.Debugf("getAddonValues: %s", userInputs)
-	var err error
-
-	inputsToSkip := maps.Keys(userInputs)
-	log.Debugf("inputsToSkip: %s", inputsToSkip)
-	promptInputs, err := prompts.RunPromptsFromConfigWithSkips(&addOnConfig.DraftConfig, inputsToSkip)
+func PromptAddonValues(dest string, addonConfig *AddonConfig) error {
+	err := prompts.RunPromptsFromConfigWithSkips(addonConfig.DraftConfig)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	log.Debug("got user inputs")
-	for k, v := range promptInputs {
-		userInputs[k] = v
-	}
 
-	referenceMap, err := addOnConfig.GetReferenceValueMap(dest)
+	referenceMap, err := addonConfig.GetReferenceValueMap(dest)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	log.Debug("got reference map")
 	// merge maps
 	for refName, refVal := range referenceMap {
 		// check for key collision
-		if _, ok := userInputs[refName]; ok {
-			return nil, errors.New("variable name collision between references and userInputs")
+		if _, err := addonConfig.DraftConfig.GetVariable(refName); err == nil {
+			return errors.New("variable name collision between references and DraftConfig")
 		}
 		if strings.Contains(strings.ToLower(refName), "namespace") && refVal == "" {
 			refVal = "default" //hack here to have explicit namespacing, probably a better way to do this
 		}
-		userInputs[refName] = refVal
+		addonConfig.DraftConfig.SetVariable(refName, refVal)
 	}
 
-	log.Debugf("merged maps into: %s", userInputs)
-	return userInputs, nil
+	return nil
 }
diff --git a/pkg/config/draftconfig.go b/pkg/config/draftconfig.go
index 6d6a6eb..93aa0b4 100644
--- a/pkg/config/draftconfig.go
+++ b/pkg/config/draftconfig.go
@@ -11,7 +11,7 @@ import (
 type DraftConfig struct {
 	DisplayName   string             `yaml:"displayName"`
 	NameOverrides []FileNameOverride `yaml:"nameOverrides"`
-	Variables     []BuilderVar       `yaml:"variables"`
+	Variables     []*BuilderVar      `yaml:"variables"`
 
 	nameOverrideMap map[string]string
 }
@@ -68,28 +68,50 @@ func (d *DraftConfig) GetNameOverride(path string) string {
 	return prefix
 }
 
-// ApplyDefaultVariables will apply the defaults to variables that are not already set
-func (d *DraftConfig) ApplyDefaultVariables(customInputs map[string]string) error {
-	varIdxMap := VariableIdxMap(d.Variables)
+func (d *DraftConfig) GetVariable(name string) (*BuilderVar, error) {
+	for _, variable := range d.Variables {
+		if variable.Name == name {
+			return variable, nil
+		}
+	}
+
+	return nil, fmt.Errorf("variable %s not found", name)
+}
+
+func (d *DraftConfig) SetVariable(name, value string) {
+	if variable, err := d.GetVariable(name); err != nil {
+		d.Variables = append(d.Variables, &BuilderVar{
+			Name:  name,
+			Value: value,
+		})
+	} else {
+		variable.Value = value
+	}
+}
 
+// ApplyDefaultVariables will apply the defaults to variables that are not already set
+func (d *DraftConfig) ApplyDefaultVariables() error {
 	for _, variable := range d.Variables {
-		// handle where variable is not set or is set to an empty string from cli handling
-		if customInputs[variable.Name] == "" {
+		if variable.Value == "" {
 			if variable.Default.ReferenceVar != "" {
-				defaultVal, err := recurseReferenceVars(d.Variables, d.Variables[varIdxMap[variable.Default.ReferenceVar]], customInputs, varIdxMap, d.Variables[varIdxMap[variable.Default.ReferenceVar]], true)
+				referenceVar, err := d.GetVariable(variable.Default.ReferenceVar)
 				if err != nil {
 					return fmt.Errorf("apply default variables: %w", err)
 				}
-				log.Infof("Variable %s defaulting to value %s", variable.Name, customInputs[variable.Name])
-				customInputs[variable.Name] = defaultVal
+				defaultVal, err := d.recurseReferenceVars(referenceVar, referenceVar, true)
+				if err != nil {
+					return fmt.Errorf("apply default variables: %w", err)
+				}
+				log.Infof("Variable %s defaulting to value %s", variable.Name, defaultVal)
+				variable.Value = defaultVal
 			}
 
-			if customInputs[variable.Name] == "" {
+			if variable.Value == "" {
 				if variable.Default.Value != "" {
 					log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
-					customInputs[variable.Name] = variable.Default.Value
+					variable.Value = variable.Default.Value
 				} else {
-					return fmt.Errorf("variable %s has no default value", variable.Name)
+					return errors.New("variable " + variable.Name + " has no default value")
 				}
 			}
 		}
@@ -99,56 +121,35 @@ func (d *DraftConfig) ApplyDefaultVariables(customInputs map[string]string) erro
 }
 
 // recurseReferenceVars recursively checks each variable's ReferenceVar if it doesn't have a custom input. If there's no more ReferenceVars, it will return the default value of the last ReferenceVar.
-func recurseReferenceVars(variables []BuilderVar, variable BuilderVar, customInputs map[string]string, varIdxMap map[string]int, variableCheck BuilderVar, isFirst bool) (string, error) {
-	if !isFirst && variable.Name == variableCheck.Name {
+func (d *DraftConfig) recurseReferenceVars(referenceVar *BuilderVar, variableCheck *BuilderVar, isFirst bool) (string, error) {
+	if !isFirst && referenceVar.Name == variableCheck.Name {
 		return "", errors.New("cyclical reference detected")
 	}
 
-	if customInputs[variable.Name] != "" {
-		return customInputs[variable.Name], nil
-	} else if variable.Default.ReferenceVar != "" {
-		return recurseReferenceVars(variables, variables[varIdxMap[variable.Default.ReferenceVar]], customInputs, varIdxMap, variableCheck, false)
-	}
-
-	return variable.Default.Value, nil
-}
-
-func VariableIdxMap(variables []BuilderVar) map[string]int {
-	varIdxMap := make(map[string]int)
-
-	for i, variable := range variables {
-		varIdxMap[variable.Name] = i
-	}
-
-	return varIdxMap
-}
-
-func (d *DraftConfig) VariableMap() (map[string]string, error) {
-	envArgs := make(map[string]string)
-
-	for _, variable := range d.Variables {
-		envArgs[variable.Name] = variable.Value
-	}
+	// If referenceVar has a custom value, return it, else check its ReferenceVar, else return its default value
+	if referenceVar.Value != "" {
+		return referenceVar.Value, nil
+	} else if referenceVar.Default.ReferenceVar != "" {
+		referenceVar, err := d.GetVariable(referenceVar.Default.ReferenceVar)
+		if err != nil {
+			return "", fmt.Errorf("recurse reference vars: %w", err)
+		}
 
-	err := d.ApplyDefaultVariables(envArgs)
-	if err != nil {
-		return nil, fmt.Errorf("creating variable map: %w", err)
+		return d.recurseReferenceVars(referenceVar, variableCheck, false)
 	}
 
-	return envArgs, nil
+	return referenceVar.Default.Value, nil
 }
 
-func (d *DraftConfig) VariableIdxMap() map[string]int {
-	varIdxMap := make(map[string]int)
-
-	for i, variable := range d.Variables {
-		varIdxMap[variable.Name] = i
+// handles flags that are meant to represent template variables
+func (d *DraftConfig) VariableMapToDraftConfig(flagVariablesMap map[string]string) {
+	for flagName, flagValue := range flagVariablesMap {
+		log.Debugf("flag variable %s=%s", flagName, flagValue)
+		d.SetVariable(flagName, flagValue)
 	}
-
-	return varIdxMap
 }
 
-// TemplateVariableRecorder is an interface for recording variables that are used read using draft configs
+// TemplateVariableRecorder is an interface for recording variables that are read using draft configs
 type TemplateVariableRecorder interface {
 	Record(key, value string)
 }
diff --git a/pkg/deployments/deployments.go b/pkg/deployments/deployments.go
index f290e59..f5acba1 100644
--- a/pkg/deployments/deployments.go
+++ b/pkg/deployments/deployments.go
@@ -35,7 +35,7 @@ func (d *Deployments) DeployTypes() []string {
 	return names
 }
 
-func (d *Deployments) CopyDeploymentFiles(deployType string, customInputs map[string]string, templateWriter templatewriter.TemplateWriter) error {
+func (d *Deployments) CopyDeploymentFiles(deployType string, deployConfig *config.DraftConfig, templateWriter templatewriter.TemplateWriter) error {
 	val, ok := d.deploys[deployType]
 	if !ok {
 		return fmt.Errorf("deployment type: %s is not currently supported", deployType)
@@ -43,24 +43,21 @@ func (d *Deployments) CopyDeploymentFiles(deployType string, customInputs map[st
 
 	srcDir := path.Join(parentDirName, val.Name())
 
-	deployConfig, ok := d.configs[deployType]
-	if !ok {
-		deployConfig = nil
-	} else {
-		deployConfig.ApplyDefaultVariables(customInputs)
+	if err := deployConfig.ApplyDefaultVariables(); err != nil {
+		return fmt.Errorf("create deployment files for deployment type: %w", err)
 	}
 
-	if err := osutil.CopyDir(d.deploymentTemplates, srcDir, d.dest, deployConfig, customInputs, templateWriter); err != nil {
+	if err := osutil.CopyDir(d.deploymentTemplates, srcDir, d.dest, deployConfig, templateWriter); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-func (d *Deployments) loadConfig(lang string) (*config.DraftConfig, error) {
-	val, ok := d.deploys[lang]
+func (d *Deployments) loadConfig(deployType string) (*config.DraftConfig, error) {
+	val, ok := d.deploys[deployType]
 	if !ok {
-		return nil, fmt.Errorf("language %s unsupported", lang)
+		return nil, fmt.Errorf("deployment type %s unsupported", deployType)
 	}
 
 	configPath := path.Join(parentDirName, val.Name(), configFileName)
@@ -89,7 +86,7 @@ func (d *Deployments) PopulateConfigs() {
 	for deployType := range d.deploys {
 		draftConfig, err := d.loadConfig(deployType)
 		if err != nil {
-			log.Debugf("no draftConfig found for language %s", deployType)
+			log.Debugf("no draftConfig found for deployment type %s", deployType)
 			draftConfig = &config.DraftConfig{}
 		}
 		d.configs[deployType] = draftConfig
diff --git a/pkg/languages/languages.go b/pkg/languages/languages.go
index 6ef62e0..53e0325 100644
--- a/pkg/languages/languages.go
+++ b/pkg/languages/languages.go
@@ -41,7 +41,7 @@ func (l *Languages) ContainsLanguage(lang string) bool {
 	return ok
 }
 
-func (l *Languages) CreateDockerfileForLanguage(lang string, customInputs map[string]string, templateWriter templatewriter.TemplateWriter) error {
+func (l *Languages) CreateDockerfileForLanguage(lang string, langConfig *config.DraftConfig, templateWriter templatewriter.TemplateWriter) error {
 	val, ok := l.langs[lang]
 	if !ok {
 		return fmt.Errorf("language %s is not supported", lang)
@@ -49,14 +49,11 @@ func (l *Languages) CreateDockerfileForLanguage(lang string, customInputs map[st
 
 	srcDir := path.Join(parentDirName, val.Name())
 
-	draftConfig, ok := l.configs[lang]
-	if !ok {
-		draftConfig = nil
-	} else {
-		draftConfig.ApplyDefaultVariables(customInputs)
+	if err := langConfig.ApplyDefaultVariables(); err != nil {
+		return fmt.Errorf("create dockerfile for language: %w", err)
 	}
 
-	if err := osutil.CopyDir(l.dockerfileTemplates, srcDir, l.dest, draftConfig, customInputs, templateWriter); err != nil {
+	if err := osutil.CopyDir(l.dockerfileTemplates, srcDir, l.dest, langConfig, templateWriter); err != nil {
 		return err
 	}
 
diff --git a/pkg/osutil/osutil.go b/pkg/osutil/osutil.go
index 7e7ea8d..14e0ce8 100644
--- a/pkg/osutil/osutil.go
+++ b/pkg/osutil/osutil.go
@@ -81,8 +81,7 @@ func EnsureFile(file string) error {
 func CopyDir(
 	fileSys fs.FS,
 	src, dest string,
-	config *config.DraftConfig,
-	customInputs map[string]string,
+	draftConfig *config.DraftConfig,
 	templateWriter templatewriter.TemplateWriter) error {
 	files, err := fs.ReadDir(fileSys, src)
 	if err != nil {
@@ -103,16 +102,16 @@ func CopyDir(
 			if err = templateWriter.EnsureDirectory(destPath); err != nil {
 				return err
 			}
-			if err = CopyDir(fileSys, srcPath, destPath, config, customInputs, templateWriter); err != nil {
+			if err = CopyDir(fileSys, srcPath, destPath, draftConfig, templateWriter); err != nil {
 				return err
 			}
 		} else {
-			fileContent, err := ReplaceTemplateVariables(fileSys, srcPath, customInputs)
+			fileContent, err := replaceTemplateVariables(fileSys, srcPath, draftConfig)
 			if err != nil {
 				return err
 			}
 
-			if err = CheckAllVariablesSubstituted(string(fileContent)); err != nil {
+			if err = checkAllVariablesSubstituted(string(fileContent)); err != nil {
 				return fmt.Errorf("error substituting file %s: %w", srcPath, err)
 			}
 
@@ -131,7 +130,7 @@ If any draft variables are found, an error is returned.
 Draft variables are defined as a string of non-whitespace characters starting with a non-period character wrapped in double curly braces.
 The non-period first character constraint is used to avoid matching helm template functions.
 */
-func CheckAllVariablesSubstituted(fileContent string) error {
+func checkAllVariablesSubstituted(fileContent string) error {
 	if unsubstitutedVars := draftVariableRegex.FindAllString(fileContent, -1); len(unsubstitutedVars) > 0 {
 		unsubstitutedVarsString := strings.Join(unsubstitutedVars, ", ")
 		return fmt.Errorf("unsubstituted variable: %s", unsubstitutedVarsString)
@@ -139,7 +138,7 @@ func CheckAllVariablesSubstituted(fileContent string) error {
 	return nil
 }
 
-func ReplaceTemplateVariables(fileSys fs.FS, srcPath string, customInputs map[string]string) ([]byte, error) {
+func replaceTemplateVariables(fileSys fs.FS, srcPath string, draftConfig *config.DraftConfig) ([]byte, error) {
 	file, err := fs.ReadFile(fileSys, srcPath)
 	if err != nil {
 		return nil, err
@@ -147,9 +146,9 @@ func ReplaceTemplateVariables(fileSys fs.FS, srcPath string, customInputs map[st
 
 	fileString := string(file)
 
-	for oldString, newString := range customInputs {
-		log.Debugf("replacing %s with %s", oldString, newString)
-		fileString = strings.ReplaceAll(fileString, "{{"+oldString+"}}", newString)
+	for _, variable := range draftConfig.Variables {
+		log.Debugf("replacing %s with %s", variable.Name, variable.Value)
+		fileString = strings.ReplaceAll(fileString, "{{"+variable.Name+"}}", variable.Value)
 	}
 
 	return []byte(fileString), nil
diff --git a/pkg/prompts/prompts.go b/pkg/prompts/prompts.go
index d8d4705..f164bf9 100644
--- a/pkg/prompts/prompts.go
+++ b/pkg/prompts/prompts.go
@@ -20,39 +20,36 @@ const defaultAppName = "my-app"
 // Function to get current directory name
 var getCurrentDirNameFunc = getCurrentDirName
 
-func RunPromptsFromConfig(draftConfig *config.DraftConfig) (map[string]string, error) {
-	return RunPromptsFromConfigWithSkips(draftConfig, []string{})
+func RunPromptsFromConfig(draftConfig *config.DraftConfig) error {
+	return RunPromptsFromConfigWithSkips(draftConfig)
 }
 
-func RunPromptsFromConfigWithSkips(draftConfig *config.DraftConfig, varsToSkip []string) (map[string]string, error) {
-	return RunPromptsFromConfigWithSkipsIO(draftConfig, varsToSkip, nil, nil)
+func RunPromptsFromConfigWithSkips(draftConfig *config.DraftConfig) error {
+	return RunPromptsFromConfigWithSkipsIO(draftConfig, nil, nil)
 }
 
 // RunPromptsFromConfigWithSkipsIO runs the prompts for the given draftConfig
 // skipping any variables in varsToSkip or where the BuilderVar.IsPromptDisabled is true.
 // If Stdin or Stdout are nil, the default values will be used.
-func RunPromptsFromConfigWithSkipsIO(draftConfig *config.DraftConfig, varsToSkip []string, Stdin io.ReadCloser, Stdout io.WriteCloser) (map[string]string, error) {
-	skipMap := make(map[string]interface{})
-	for _, v := range varsToSkip {
-		skipMap[v] = interface{}(nil)
+func RunPromptsFromConfigWithSkipsIO(draftConfig *config.DraftConfig, Stdin io.ReadCloser, Stdout io.WriteCloser) error {
+	if draftConfig == nil {
+		return errors.New("draftConfig is nil")
 	}
 
-	inputs := make(map[string]string)
-
 	for _, variable := range draftConfig.Variables {
-		if val, ok := skipMap[variable.Name]; ok && val != "" {
+		if variable.Value != "" {
 			log.Debugf("Skipping prompt for %s", variable.Name)
 			continue
 		}
 
 		if variable.Default.IsPromptDisabled {
 			log.Debugf("Skipping prompt for %s as it has IsPromptDisabled=true", variable.Name)
-			noPromptDefaultValue := GetVariableDefaultValue(variable, inputs)
+			noPromptDefaultValue := GetVariableDefaultValue(draftConfig, variable)
 			if noPromptDefaultValue == "" {
-				return nil, fmt.Errorf("IsPromptDisabled is true for %s but no default value was found", variable.Name)
+				return fmt.Errorf("IsPromptDisabled is true for %s but no default value was found", variable.Name)
 			}
 			log.Debugf("Using default value %s for %s", noPromptDefaultValue, variable.Name)
-			inputs[variable.Name] = noPromptDefaultValue
+			variable.Value = noPromptDefaultValue
 			continue
 		}
 
@@ -60,25 +57,25 @@ func RunPromptsFromConfigWithSkipsIO(draftConfig *config.DraftConfig, varsToSkip
 		if variable.Type == "bool" {
 			input, err := RunBoolPrompt(variable, Stdin, Stdout)
 			if err != nil {
-				return nil, err
+				return err
 			}
-			inputs[variable.Name] = input
+			variable.Value = input
 		} else {
-			defaultValue := GetVariableDefaultValue(variable, inputs)
+			defaultValue := GetVariableDefaultValue(draftConfig, variable)
 
 			stringInput, err := RunDefaultableStringPrompt(defaultValue, variable, nil, Stdin, Stdout)
 			if err != nil {
-				return nil, err
+				return err
 			}
-			inputs[variable.Name] = stringInput
+			variable.Value = stringInput
 		}
 	}
 
-	return inputs, nil
+	return nil
 }
 
 // GetVariableDefaultValue returns the default value for a variable, if one is set in variableDefaults from a ReferenceVar or literal Variable.DefaultValue in that order.
-func GetVariableDefaultValue(variable config.BuilderVar, inputs map[string]string) string {
+func GetVariableDefaultValue(draftConfig *config.DraftConfig, variable *config.BuilderVar) string {
 	defaultValue := ""
 
 	if variable.Name == "APPNAME" {
@@ -93,15 +90,19 @@ func GetVariableDefaultValue(variable config.BuilderVar, inputs map[string]strin
 
 	defaultValue = variable.Default.Value
 	log.Debugf("setting default value for %s to %s from variable default rule", variable.Name, defaultValue)
-	if variable.Default.ReferenceVar != "" && inputs[variable.Default.ReferenceVar] != "" {
-		defaultValue = inputs[variable.Default.ReferenceVar]
-		log.Debugf("setting default value for %s to %s from referenceVar %s", variable.Name, defaultValue, variable.Default.ReferenceVar)
+	if variable.Default.ReferenceVar != "" {
+		if referenceVar, err := draftConfig.GetVariable(variable.Default.ReferenceVar); err != nil {
+			log.Errorf("Error getting reference variable %s: %s", variable.Default.ReferenceVar, err)
+		} else if referenceVar.Value != "" {
+			defaultValue = referenceVar.Value
+			log.Debugf("setting default value for %s to %s from referenceVar %s", variable.Name, defaultValue, variable.Default.ReferenceVar)
+		}
 	}
 
 	return defaultValue
 }
 
-func RunBoolPrompt(customPrompt config.BuilderVar, Stdin io.ReadCloser, Stdout io.WriteCloser) (string, error) {
+func RunBoolPrompt(customPrompt *config.BuilderVar, Stdin io.ReadCloser, Stdout io.WriteCloser) (string, error) {
 	newSelect := &promptui.Select{
 		Label:  "Please select " + customPrompt.Description,
 		Items:  []bool{true, false},
@@ -157,7 +158,7 @@ func appNameValidator(name string) error {
 }
 
 // RunDefaultableStringPrompt runs a prompt for a string variable, returning the user string input for the prompt
-func RunDefaultableStringPrompt(defaultValue string, customPrompt config.BuilderVar, validate func(string) error, Stdin io.ReadCloser, Stdout io.WriteCloser) (string, error) {
+func RunDefaultableStringPrompt(defaultValue string, customPrompt *config.BuilderVar, validate func(string) error, Stdin io.ReadCloser, Stdout io.WriteCloser) (string, error) {
 	if validate == nil {
 		validate = NoBlankStringValidator
 	}
diff --git a/pkg/workflows/charts/production.yaml b/pkg/workflows/charts/production.yaml
new file mode 100644
index 0000000..dc88797
--- /dev/null
+++ b/pkg/workflows/charts/production.yaml
@@ -0,0 +1,8 @@
+image:
+  repository: "test"
+  pullPolicy: Always
+  tag: "latest"
+service:
+  annotations: {}
+  type: LoadBalancer
+  port: 80
diff --git a/pkg/workflows/workflowconfig.go b/pkg/workflows/workflowconfig.go
deleted file mode 100644
index 5aace52..0000000
--- a/pkg/workflows/workflowconfig.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package workflows
-
-type WorkflowConfig struct {
-	WorkflowName         string
-	BranchName           string
-	AcrResourceGroup     string
-	AcrName              string
-	ContainerName        string
-	ClusterResourceGroup string
-	ClusterName          string
-	Dockerfile           string
-	BuildContextPath     string
-	Namespace            string
-	PrivateCluster       string
-}
-
-func (config *WorkflowConfig) SetFlagValuesToMap() map[string]string {
-	flagValuesMap := make(map[string]string)
-	if config.WorkflowName != "" {
-		flagValuesMap["WORKFLOWNAME"] = config.WorkflowName
-	}
-
-	if config.BranchName != "" {
-		flagValuesMap["BRANCHNAME"] = config.BranchName
-	}
-
-	if config.AcrResourceGroup != "" {
-		flagValuesMap["ACRRESOURCEGROUP"] = config.AcrResourceGroup
-	}
-
-	if config.AcrName != "" {
-		flagValuesMap["AZURECONTAINERREGISTRY"] = config.AcrName
-	}
-
-	if config.ContainerName != "" {
-		flagValuesMap["CONTAINERNAME"] = config.ContainerName
-	}
-
-	if config.ClusterResourceGroup != "" {
-		flagValuesMap["CLUSTERRESOURCEGROUP"] = config.ClusterResourceGroup
-	}
-
-	if config.ClusterName != "" {
-		flagValuesMap["CLUSTERNAME"] = config.ClusterName
-	}
-
-	if config.Dockerfile != "" {
-		flagValuesMap["DOCKERFILE"] = config.Dockerfile
-	}
-
-	if config.BuildContextPath != "" {
-		flagValuesMap["BUILDCONTEXTPATH"] = config.BuildContextPath
-	}
-
-	if config.Namespace != "" {
-		flagValuesMap["NAMESPACE"] = config.Namespace
-	}
-
-	if config.PrivateCluster != "" {
-		flagValuesMap["PRIVATECLUSTER"] = config.PrivateCluster
-	}
-
-	return flagValuesMap
-}
diff --git a/pkg/workflows/workflows.go b/pkg/workflows/workflows.go
index 1bc4b85..0b25996 100644
--- a/pkg/workflows/workflows.go
+++ b/pkg/workflows/workflows.go
@@ -22,20 +22,31 @@ import (
 	"github.com/Azure/draft/pkg/templatewriter"
 )
 
-const (
-	parentDirName  = "workflows"
-	configFileName = "/draft.yaml"
-)
-
 type Workflows struct {
 	workflows         map[string]fs.DirEntry
 	configs           map[string]*config.DraftConfig
-	dest              string
+	Dest              string
 	workflowTemplates fs.FS
 }
 
-func updateProductionDeployments(deployType, dest string, flagValuesMap map[string]string, templateWriter templatewriter.TemplateWriter) error {
-	productionImage := fmt.Sprintf("%s.azurecr.io/%s", flagValuesMap["AZURECONTAINERREGISTRY"], flagValuesMap["CONTAINERNAME"])
+const (
+	parentDirName         = "workflows"
+	configFileName        = "/draft.yaml"
+	emptyDefaultFlagValue = ""
+)
+
+func UpdateProductionDeployments(deployType, dest string, draftConfig *config.DraftConfig, templateWriter templatewriter.TemplateWriter) error {
+	acr, err := draftConfig.GetVariable("AZURECONTAINERREGISTRY")
+	if err != nil {
+		return fmt.Errorf("get variable: %w", err)
+	}
+
+	containerName, err := draftConfig.GetVariable("CONTAINERNAME")
+	if err != nil {
+		return fmt.Errorf("get variable: %w", err)
+	}
+
+	productionImage := fmt.Sprintf("%s.azurecr.io/%s", acr.Value, containerName.Value)
 	switch deployType {
 	case "helm":
 		return setHelmContainerImage(dest+"/charts/production.yaml", productionImage, templateWriter)
@@ -143,7 +154,7 @@ func CreateWorkflowsFromEmbedFS(workflowTemplates embed.FS, dest string) *Workfl
 
 	w := &Workflows{
 		workflows:         deployMap,
-		dest:              dest,
+		Dest:              dest,
 		configs:           make(map[string]*config.DraftConfig),
 		workflowTemplates: workflowTemplates,
 	}
@@ -163,25 +174,20 @@ func (w *Workflows) populateConfigs() {
 	}
 }
 
-func (w *Workflows) CreateWorkflowFiles(deployType string, customInputs map[string]string, templateWriter templatewriter.TemplateWriter) error {
+func (w *Workflows) CreateWorkflowFiles(deployType string, draftConfig *config.DraftConfig, templateWriter templatewriter.TemplateWriter) error {
 	val, ok := w.workflows[deployType]
 	if !ok {
 		return fmt.Errorf("deployment type: %s is not currently supported", deployType)
 	}
+
 	srcDir := path.Join(parentDirName, val.Name())
 	log.Debugf("source directory for workflow template: %s", srcDir)
-	workflowConfig, ok := w.configs[deployType]
-	if !ok {
-		workflowConfig = nil
-	} else {
-		workflowConfig.ApplyDefaultVariables(customInputs)
-	}
 
-	if err := updateProductionDeployments(deployType, w.dest, customInputs, templateWriter); err != nil {
-		return fmt.Errorf("update production deployments: %w", err)
+	if err := draftConfig.ApplyDefaultVariables(); err != nil {
+		return fmt.Errorf("create workflow files: %w", err)
 	}
 
-	if err := osutil.CopyDir(w.workflowTemplates, srcDir, w.dest, workflowConfig, customInputs, templateWriter); err != nil {
+	if err := osutil.CopyDir(w.workflowTemplates, srcDir, w.Dest, draftConfig, templateWriter); err != nil {
 		return err
 	}
 
diff --git a/template/workflows/helm/draft.yaml b/template/workflows/helm/draft.yaml
index 26c99b8..6b85ff1 100644
--- a/template/workflows/helm/draft.yaml
+++ b/template/workflows/helm/draft.yaml
@@ -44,7 +44,7 @@ variables:
     description: "the Kubernetes namespace"
   - name: "PRIVATECLUSTER"
     default:
-      value: "false"
+      value: false
     description: "true if the AKS cluster is private"
     type: "bool"
   
\ No newline at end of file


Test patch:
diff --git a/cmd/create_test.go b/cmd/create_test.go
index 5bdcd4e..a4550cc 100644
--- a/cmd/create_test.go
+++ b/cmd/create_test.go
@@ -23,29 +23,32 @@ import (
 func TestRun(t *testing.T) {
 	testCreateConfig := CreateConfig{LanguageVariables: []UserInputs{{Name: "PORT", Value: "8080"}}, DeployVariables: []UserInputs{{Name: "PORT", Value: "8080"}, {Name: "APPNAME", Value: "testingCreateCommand"}}}
 	flagVariablesMap = map[string]string{"PORT": "8080", "APPNAME": "testingCreateCommand", "VERSION": "1.18", "SERVICEPORT": "8080", "NAMESPACE": "testNamespace", "IMAGENAME": "testImage", "IMAGETAG": "latest"}
-	mockCC := createCmd{dest: "./..", createConfig: &testCreateConfig, templateWriter: &writers.LocalFSWriter{}}
+	mockCC := createCmd{
+		dest:           "./..",
+		createConfig:   &testCreateConfig,
+		templateWriter: &writers.LocalFSWriter{},
+	}
 	deployTypes := []string{"helm", "kustomize", "manifests"}
 	oldDockerfile, _ := ioutil.ReadFile("./../Dockerfile")
 	oldDockerignore, _ := ioutil.ReadFile("./../.dockerignore")
 
 	detectedLang, lowerLang, err := mockCC.mockDetectLanguage()
-
-	assert.False(t, detectedLang == nil)
+	assert.NotNil(t, detectedLang)
 	assert.False(t, lowerLang == "")
-	assert.True(t, err == nil)
+	assert.Nil(t, err)
 
 	err = mockCC.generateDockerfile(detectedLang, lowerLang)
-	assert.True(t, err == nil)
+	assert.Nil(t, err)
 
 	//when language variables are passed in --variable flag
 	mockCC.createConfig.LanguageVariables = nil
 	mockCC.lang = "go"
 	detectedLang, lowerLang, err = mockCC.mockDetectLanguage()
-	assert.False(t, detectedLang == nil)
+	assert.NotNil(t, detectedLang)
 	assert.False(t, lowerLang == "")
-	assert.True(t, err == nil)
+	assert.Nil(t, err)
 	err = mockCC.generateDockerfile(detectedLang, lowerLang)
-	assert.True(t, err == nil)
+	assert.Nil(t, err)
 
 	//Write back old Dockerfile
 	err = ioutil.WriteFile("./../Dockerfile", oldDockerfile, 0644)
@@ -62,13 +65,13 @@ func TestRun(t *testing.T) {
 		//deployment variables passed through --variable flag
 		mockCC.deployType = deployType
 		err = mockCC.createDeployment()
-		assert.True(t, err == nil)
+		assert.Nil(t, err)
 		//check if deployment files have been created
 		err, deploymentFiles := getAllDeploymentFiles(path.Join("../template/deployments", mockCC.deployType))
 		assert.Nil(t, err)
 		for _, fileName := range deploymentFiles {
 			_, err = os.Stat(fileName)
-			assert.True(t, err == nil)
+			assert.Nil(t, err)
 		}
 
 		os.RemoveAll("./../charts")
@@ -79,13 +82,13 @@ func TestRun(t *testing.T) {
 		//deployment variables passed through createConfig
 		mockCC.createConfig.DeployType = deployType
 		err = mockCC.createDeployment()
-		assert.True(t, err == nil)
+		assert.Nil(t, err)
 		//check if deployment files have been created
 		err, deploymentFiles = getAllDeploymentFiles(path.Join("../template/deployments", mockCC.createConfig.DeployType))
 		assert.Nil(t, err)
 		for _, fileName := range deploymentFiles {
 			_, err = os.Stat(fileName)
-			assert.True(t, err == nil)
+			assert.Nil(t, err)
 		}
 		mockCC.createConfig.DeployType = ""
 
@@ -107,12 +110,12 @@ func TestRunCreateDockerfileWithRepoReader(t *testing.T) {
 	mockCC := createCmd{createConfig: &testCreateConfig, repoReader: testRepoReader, templateWriter: &writers.LocalFSWriter{}}
 
 	detectedLang, lowerLang, err := mockCC.mockDetectLanguage()
-	assert.False(t, detectedLang == nil)
+	assert.NotNil(t, detectedLang)
 	assert.True(t, lowerLang == "python")
 	assert.Nil(t, err)
 
 	err = mockCC.generateDockerfile(detectedLang, lowerLang)
-	assert.True(t, err == nil)
+	assert.Nil(t, err)
 
 	dockerFileContent, err := ioutil.ReadFile("Dockerfile")
 	if err != nil {
@@ -137,13 +140,13 @@ func TestInitConfig(t *testing.T) {
 	mockCC.createConfigPath = "./../test/templates/config.yaml"
 
 	err := mockCC.initConfig()
-	assert.True(t, err == nil)
-	assert.True(t, mockCC.createConfig != nil)
+	assert.Nil(t, err)
+	assert.NotNil(t, mockCC.createConfig)
 }
 
 func TestValidateConfigInputsToPromptsPass(t *testing.T) {
 	required := config.DraftConfig{
-		Variables: []config.BuilderVar{
+		Variables: []*config.BuilderVar{
 			{
 				Name: "REQUIRED_PROVIDED",
 			},
@@ -159,14 +162,24 @@ func TestValidateConfigInputsToPromptsPass(t *testing.T) {
 		{Name: "REQUIRED_PROVIDED", Value: "PROVIDED_VALUE"},
 	}
 
-	vars, err := validateConfigInputsToPrompts(&required, provided)
-	assert.True(t, err == nil)
-	assert.Equal(t, vars["REQUIRED_DEFAULTED"], "DEFAULT_VALUE")
+	err := validateConfigInputsToPrompts(&required, provided)
+	assert.Nil(t, err)
+
+	err = required.ApplyDefaultVariables()
+	assert.Nil(t, err)
+
+	var1, err := required.GetVariable("REQUIRED_PROVIDED")
+	assert.Nil(t, err)
+	assert.Equal(t, var1.Value, "PROVIDED_VALUE")
+
+	var2, err := required.GetVariable("REQUIRED_DEFAULTED")
+	assert.Nil(t, err)
+	assert.Equal(t, var2.Value, "DEFAULT_VALUE")
 }
 
 func TestValidateConfigInputsToPromptsMissing(t *testing.T) {
 	required := config.DraftConfig{
-		Variables: []config.BuilderVar{
+		Variables: []*config.BuilderVar{
 			{
 				Name: "REQUIRED_PROVIDED",
 			},
@@ -179,7 +192,10 @@ func TestValidateConfigInputsToPromptsMissing(t *testing.T) {
 		{Name: "REQUIRED_PROVIDED"},
 	}
 
-	_, err := validateConfigInputsToPrompts(&required, provided)
+	err := validateConfigInputsToPrompts(&required, provided)
+	assert.Nil(t, err)
+
+	err = required.ApplyDefaultVariables()
 	assert.NotNil(t, err)
 }
 
diff --git a/example/deployment_test.go b/example/deployment_test.go
index d604093..ecb7bdc 100644
--- a/example/deployment_test.go
+++ b/example/deployment_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/Azure/draft/pkg/config"
 	"github.com/Azure/draft/pkg/templatewriter/writers"
 )
 
@@ -13,27 +14,129 @@ func TestWriteDeploymentFiles(t *testing.T) {
 
 	testCases := []struct {
 		name           string
-		inputVariables map[string]string
+		deployConfig   *config.DraftConfig
 		deploymentType string
 		expectError    bool
 	}{
 		{
 			name: "Test Valid Manifests Deployment Generation",
-			inputVariables: map[string]string{
-				"PORT":        "8080",
-				"APPNAME":     "testapp",
-				"SERVICEPORT": "8080",
-				"NAMESPACE":   "testnamespace",
-				"IMAGENAME":   "testimage",
-				"IMAGETAG":    "latest",
+			deployConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "PORT",
+						Default: config.BuilderVarDefault{
+							Value: "80",
+						},
+						Description: "the port exposed in the application",
+						Value:       "8080",
+					},
+					{
+						Name:        "APPNAME",
+						Description: "the name of the application",
+						Value:       "testapp",
+					},
+					{
+						Name: "SERVICEPORT",
+						Default: config.BuilderVarDefault{
+							ReferenceVar: "PORT",
+						},
+						Description: "the port the service uses to make the application accessible from outside the cluster",
+						Value:       "8080",
+					},
+					{
+						Name: "NAMESPACE",
+						Default: config.BuilderVarDefault{
+							Value: "default",
+						},
+						Description: "the name of the image to use in the deployment",
+						Value:       "testnamespace",
+					},
+					{
+						Name: "IMAGENAME",
+						Default: config.BuilderVarDefault{
+							IsPromptDisabled: true,
+							Value:            "the name of the image to use in the deployment",
+						},
+						Description: "the name of the image to use in the deployment",
+						Value:       "testimage",
+					},
+					{
+						Name: "IMAGETAG",
+						Default: config.BuilderVarDefault{
+							IsPromptDisabled: true,
+							Value:            "latest",
+						},
+						Description: "the tag of the image to use in the deployment",
+						Value:       "latest",
+					},
+					{
+						Name: "GENERATORLABEL",
+						Default: config.BuilderVarDefault{
+							IsPromptDisabled: true,
+							Value:            "draft",
+						},
+						Description: "the label to use to identify the deployment as generated by draft",
+					},
+				},
 			},
 			deploymentType: "manifests",
 			expectError:    false,
 		},
 		{
 			name: "Test Invalid Manifests Deployment Generation",
-			inputVariables: map[string]string{
-				"PORT": "8080",
+			deployConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "PORT",
+						Default: config.BuilderVarDefault{
+							Value: "80",
+						},
+						Description: "the port exposed in the application",
+						Value:       "8080",
+					},
+					{
+						Name:        "APPNAME",
+						Description: "the name of the application",
+					},
+					{
+						Name: "SERVICEPORT",
+						Default: config.BuilderVarDefault{
+							ReferenceVar: "PORT",
+						},
+						Description: "the port the service uses to make the application accessible from outside the cluster",
+					},
+					{
+						Name: "NAMESPACE",
+						Default: config.BuilderVarDefault{
+							Value: "default",
+						},
+						Description: "the name of the image to use in the deployment",
+					},
+					{
+						Name: "IMAGENAME",
+						Default: config.BuilderVarDefault{
+							IsPromptDisabled: true,
+							Value:            "the name of the image to use in the deployment",
+						},
+						Description: "the name of the image to use in the deployment",
+					},
+					{
+						Name: "IMAGETAG",
+						Default: config.BuilderVarDefault{
+							IsPromptDisabled: true,
+							Value:            "latest",
+						},
+						Description: "the tag of the image to use in the deployment",
+					},
+					{
+						Name: "GENERATORLABEL",
+						Default: config.BuilderVarDefault{
+							IsPromptDisabled: true,
+							Value:            "draft",
+						},
+						Description: "the label to use to identify the deployment as generated by draft",
+					},
+				},
 			},
 			deploymentType: "manifests",
 			expectError:    true,
@@ -42,7 +145,7 @@ func TestWriteDeploymentFiles(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			err := WriteDeploymentFiles(&filewriter, outputPath, tc.inputVariables, tc.deploymentType)
+			err := WriteDeploymentFiles(&filewriter, outputPath, tc.deployConfig, tc.deploymentType)
 			errored := err != nil
 			if err != nil {
 				fmt.Printf("WriteDeploymentFiles failed: %e\n", err)
diff --git a/example/dockerfile_test.go b/example/dockerfile_test.go
index fd74a5c..8fd2b6c 100644
--- a/example/dockerfile_test.go
+++ b/example/dockerfile_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/Azure/draft/pkg/config"
 	"github.com/Azure/draft/pkg/templatewriter/writers"
 )
 
@@ -13,33 +14,87 @@ func TestWriteDockerfile(t *testing.T) {
 
 	testCases := []struct {
 		name               string
-		inputVariables     map[string]string
+		langConfig         *config.DraftConfig
 		generationLanguage string
 		expectError        bool
 	}{
 
 		{
 			name: "Test Valid Go Dockerfile Generation",
-			inputVariables: map[string]string{
-				"PORT":    "8080",
-				"VERSION": "1.20",
+			langConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "PORT",
+						Default: config.BuilderVarDefault{
+							Value: "80",
+						},
+						Description: "the port exposed in the application",
+						Type:        "int",
+						Value:       "8080",
+					},
+					{
+						Name: "VERSION",
+						Default: config.BuilderVarDefault{
+							Value: "1.18",
+						},
+						Description:   "the version of go used by the application",
+						ExampleValues: []string{"1.16", "1.17", "1.18", "1.19"},
+						Value:         "1.20",
+					},
+				},
 			},
 			generationLanguage: "go",
 			expectError:        false,
 		},
 		{
-			name: "Test Valid Go Dockerfile Generation with deafult",
-			inputVariables: map[string]string{
-				"PORT": "8080",
+			name: "Test Valid Go Dockerfile Generation with default",
+			langConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "PORT",
+						Default: config.BuilderVarDefault{
+							Value: "80",
+						},
+						Description: "the port exposed in the application",
+						Type:        "int",
+						Value:       "8080",
+					},
+					{
+						Name: "VERSION",
+						Default: config.BuilderVarDefault{
+							Value: "1.18",
+						},
+						Description:   "the version of go used by the application",
+						ExampleValues: []string{"1.16", "1.17", "1.18", "1.19"},
+					},
+				},
 			},
 			generationLanguage: "go",
 			expectError:        false,
 		},
 		{
 			name: "Test Invalid GenerationLanguage",
-			inputVariables: map[string]string{
-				"PORT":    "8080",
-				"VERSION": "1.20",
+			langConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "PORT",
+						Default: config.BuilderVarDefault{
+							Value: "80",
+						},
+						Description: "the port exposed in the application",
+						Type:        "int",
+						Value:       "8080",
+					},
+					{
+						Name: "VERSION",
+						Default: config.BuilderVarDefault{
+							Value: "1.18",
+						},
+						Description:   "the version of go used by the application",
+						ExampleValues: []string{"1.16", "1.17", "1.18", "1.19"},
+						Value:         "1.20",
+					},
+				},
 			},
 			generationLanguage: "invalid",
 			expectError:        true,
@@ -48,7 +103,7 @@ func TestWriteDockerfile(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			err := WriteDockerfile(&templateWriter, outputPath, tc.inputVariables, tc.generationLanguage)
+			err := WriteDockerfile(&templateWriter, outputPath, tc.langConfig, tc.generationLanguage)
 			errored := err != nil
 			if err != nil {
 				fmt.Printf("WriteDockerfile failed: %e\n", err)
diff --git a/pkg/addons/addons_test.go b/pkg/addons/addons_test.go
index 7113728..27bd559 100644
--- a/pkg/addons/addons_test.go
+++ b/pkg/addons/addons_test.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
+	"github.com/Azure/draft/pkg/config"
 	"github.com/Azure/draft/pkg/osutil"
 	"github.com/Azure/draft/pkg/templatewriter/writers"
 	"github.com/Azure/draft/template"
@@ -15,34 +16,57 @@ import (
 const templatePath = "../../test/templates"
 
 func TestGenerateAddonErrors(t *testing.T) {
+	var addonConfig AddonConfig
 	templateWriter := &writers.LocalFSWriter{}
-	userInputs := map[string]string{
-		"test": "test",
-	}
-	err := GenerateAddon(template.Addons, "azure", "webapp_routing", "fakeDest", userInputs, templateWriter)
+	err := GenerateAddon(template.Addons, "azure", "webapp_routing", "fakeDest", addonConfig, templateWriter)
 	assert.NotNil(t, err, "should fail with fake destination")
 
-	err = GenerateAddon(template.Addons, "azure", "fakeAddon", "../../test/templates/helm", userInputs, templateWriter)
+	err = GenerateAddon(template.Addons, "azure", "fakeAddon", "../../test/templates/helm", addonConfig, templateWriter)
 	assert.NotNil(t, err, "should fail with fake addon name")
 
-	err = GenerateAddon(template.Addons, "fakeProvider", "fakeAddon", "../../test/templates/helm", userInputs, templateWriter)
+	err = GenerateAddon(template.Addons, "fakeProvider", "fakeAddon", "../../test/templates/helm", addonConfig, templateWriter)
 	assert.NotNil(t, err, "should fail with fake provider name")
 }
 
 func TestGenerateHelmAddonSuccess(t *testing.T) {
 	templateWriter := &writers.LocalFSWriter{}
-	var correctUserInputs = map[string]string{
-		"ingress-tls-cert-keyvault-uri": "test.uri",
-		"ingress-use-osm-mtls":          "false",
-		"ingress-host":                  "host",
-		"service-namespace":             "test-namespace",
-		"service-name":                  "test-service",
-		"service-port":                  "80",
+	addonConfig := AddonConfig{
+		DraftConfig: &config.DraftConfig{
+			Variables: []*config.BuilderVar{
+				{
+					Name:  "ingress-tls-cert-keyvault-uri",
+					Value: "test.uri",
+				},
+				{
+					Name:  "ingress-use-osm-mtls",
+					Value: "false",
+				},
+				{
+					Name:  "ingress-host",
+					Value: "host",
+				},
+				{
+					Name: "GENERATORLABEL",
+				},
+				{
+					Name:  "service-namespace",
+					Value: "test-namespace",
+				},
+				{
+					Name:  "service-name",
+					Value: "test-service",
+				},
+				{
+					Name:  "service-port",
+					Value: "80",
+				},
+			},
+		},
 	}
 	dir, remove, err := setUpTempDir("helm")
 	assert.Nil(t, err)
 
-	err = GenerateAddon(template.Addons, "azure", "webapp_routing", dir, correctUserInputs, templateWriter)
+	err = GenerateAddon(template.Addons, "azure", "webapp_routing", dir, addonConfig, templateWriter)
 	assert.Nil(t, err)
 
 	assert.Nil(t, remove())
@@ -50,25 +74,70 @@ func TestGenerateHelmAddonSuccess(t *testing.T) {
 
 func TestGenerateKustomizeAddonSuccess(t *testing.T) {
 	templateWriter := &writers.LocalFSWriter{}
-	var correctUserInputs = map[string]string{
-		"ingress-tls-cert-keyvault-uri": "test.uri",
-		"ingress-use-osm-mtls":          "false",
-		"ingress-host":                  "host",
-		"service-namespace":             "test-namespace",
-		"service-name":                  "test-service",
-		"service-port":                  "80",
+	addonConfig := AddonConfig{
+		DraftConfig: &config.DraftConfig{
+			Variables: []*config.BuilderVar{
+				{
+					Name:  "ingress-tls-cert-keyvault-uri",
+					Value: "test.uri",
+				},
+				{
+					Name:  "ingress-use-osm-mtls",
+					Value: "false",
+				},
+				{
+					Name:  "ingress-host",
+					Value: "host",
+				},
+				{
+					Name: "GENERATORLABEL",
+				},
+				{
+					Name:  "service-namespace",
+					Value: "test-namespace",
+				},
+				{
+					Name:  "service-name",
+					Value: "test-service",
+				},
+				{
+					Name:  "service-port",
+					Value: "80",
+				},
+			},
+		},
 	}
 	dir, remove, err := setUpTempDir("kustomize")
 	assert.Nil(t, err)
 
-	err = GenerateAddon(template.Addons, "azure", "webapp_routing", dir, correctUserInputs, templateWriter)
+	err = GenerateAddon(template.Addons, "azure", "webapp_routing", dir, addonConfig, templateWriter)
 	assert.Nil(t, err)
 
 	assert.Nil(t, remove())
 }
 
+func TestNilDraftConfig(t *testing.T) {
+	templateWriter := &writers.LocalFSWriter{}
+	addonConfig := AddonConfig{}
+	dir, remove, err := setUpTempDir("helm")
+	assert.Nil(t, err)
+
+	err = GenerateAddon(template.Addons, "azure", "webapp_routing", dir, addonConfig, templateWriter)
+	assert.NotNil(t, err, "should fail with nil DraftConfig")
+	assert.Equal(t, "DraftConfig is nil", err.Error())
+
+	err = PromptAddonValues(dir, &addonConfig)
+	assert.NotNil(t, err, "should fail with nil DraftConfig")
+	assert.Equal(t, "draftConfig is nil", err.Error())
+
+	assert.Nil(t, remove())
+}
+
 func setUpTempDir(deploy string) (dir string, close func() error, err error) {
 	templateWriter := &writers.LocalFSWriter{}
+	addonConfig := AddonConfig{
+		DraftConfig: &config.DraftConfig{},
+	}
 	dir, err = ioutil.TempDir("", "addonTest")
 	if err != nil {
 		return
@@ -77,7 +146,7 @@ func setUpTempDir(deploy string) (dir string, close func() error, err error) {
 		return os.RemoveAll(dir)
 	}
 	fs := os.DirFS(templatePath)
-	if err = osutil.CopyDir(fs, deploy, dir, nil, nil, templateWriter); err != nil {
+	if err = osutil.CopyDir(fs, deploy, dir, addonConfig.DraftConfig, templateWriter); err != nil {
 		return
 	}
 
diff --git a/pkg/config/draftconfig_test.go b/pkg/config/draftconfig_test.go
index 20bd1f2..fddf83c 100644
--- a/pkg/config/draftconfig_test.go
+++ b/pkg/config/draftconfig_test.go
@@ -15,25 +15,23 @@ func TestApplyDefaultVariables(t *testing.T) {
 		{
 			testName: "keepAllCustomInputs",
 			draftConfig: DraftConfig{
-				Variables: []BuilderVar{
+				Variables: []*BuilderVar{
 					{
-						Name: "var1",
+						Name:  "var1",
+						Value: "custom-value-1",
 						Default: BuilderVarDefault{
 							Value: "default-value-1",
 						},
 					},
 					{
-						Name: "var2",
+						Name:  "var2",
+						Value: "custom-value-2",
 						Default: BuilderVarDefault{
 							Value: "default-value-2",
 						},
 					},
 				},
 			},
-			customInputs: map[string]string{
-				"var1": "custom-value-1",
-				"var2": "custom-value-2",
-			},
 			want: map[string]string{
 				"var1": "custom-value-1",
 				"var2": "custom-value-2",
@@ -42,7 +40,7 @@ func TestApplyDefaultVariables(t *testing.T) {
 		{
 			testName: "applyDefaultToEmptyCustomInputs",
 			draftConfig: DraftConfig{
-				Variables: []BuilderVar{
+				Variables: []*BuilderVar{
 					{
 						Name: "var1",
 						Default: BuilderVarDefault{
@@ -57,7 +55,6 @@ func TestApplyDefaultVariables(t *testing.T) {
 					},
 				},
 			},
-			customInputs: map[string]string{},
 			want: map[string]string{
 				"var1": "default-value-1",
 				"var2": "default-value-2",
@@ -66,9 +63,10 @@ func TestApplyDefaultVariables(t *testing.T) {
 		{
 			testName: "applyDefaultToPartialCustomInputs",
 			draftConfig: DraftConfig{
-				Variables: []BuilderVar{
+				Variables: []*BuilderVar{
 					{
-						Name: "var1",
+						Name:  "var1",
+						Value: "custom-value-1",
 						Default: BuilderVarDefault{
 							Value: "default-value-1",
 						},
@@ -81,9 +79,6 @@ func TestApplyDefaultVariables(t *testing.T) {
 					},
 				},
 			},
-			customInputs: map[string]string{
-				"var1": "custom-value-1",
-			},
 			want: map[string]string{
 				"var1": "custom-value-1",
 				"var2": "default-value-2",
@@ -92,7 +87,7 @@ func TestApplyDefaultVariables(t *testing.T) {
 		{
 			testName: "variablesHaveNoInputOrDefault",
 			draftConfig: DraftConfig{
-				Variables: []BuilderVar{
+				Variables: []*BuilderVar{
 					{
 						Name: "var1",
 					},
@@ -101,14 +96,13 @@ func TestApplyDefaultVariables(t *testing.T) {
 					},
 				},
 			},
-			customInputs: map[string]string{},
-			want:         map[string]string{},
-			wantErrMsg:   "variable var1 has no default value",
+			want:       map[string]string{},
+			wantErrMsg: "variable var1 has no default value",
 		},
 		{
 			testName: "getDefaultFromReferenceVarCustomInputs",
 			draftConfig: DraftConfig{
-				Variables: []BuilderVar{
+				Variables: []*BuilderVar{
 					{
 						Name: "var1",
 						Default: BuilderVarDefault{
@@ -117,16 +111,14 @@ func TestApplyDefaultVariables(t *testing.T) {
 						},
 					},
 					{
-						Name: "var2",
+						Name:  "var2",
+						Value: "this-value",
 						Default: BuilderVarDefault{
 							Value: "not-this-value",
 						},
 					},
 				},
 			},
-			customInputs: map[string]string{
-				"var2": "this-value",
-			},
 			want: map[string]string{
 				"var1": "this-value",
 				"var2": "this-value",
@@ -135,7 +127,7 @@ func TestApplyDefaultVariables(t *testing.T) {
 		{
 			testName: "getDefaultFromReferenceVar",
 			draftConfig: DraftConfig{
-				Variables: []BuilderVar{
+				Variables: []*BuilderVar{
 					{
 						Name: "var1",
 						Default: BuilderVarDefault{
@@ -158,7 +150,6 @@ func TestApplyDefaultVariables(t *testing.T) {
 					},
 				},
 			},
-			customInputs: map[string]string{},
 			want: map[string]string{
 				"var1": "default-value-3",
 				"var2": "default-value-3",
@@ -168,7 +159,7 @@ func TestApplyDefaultVariables(t *testing.T) {
 		{
 			testName: "cyclicalReferenceVarsDetected",
 			draftConfig: DraftConfig{
-				Variables: []BuilderVar{
+				Variables: []*BuilderVar{
 					{
 						Name: "var1",
 						Default: BuilderVarDefault{
@@ -183,19 +174,23 @@ func TestApplyDefaultVariables(t *testing.T) {
 					},
 				},
 			},
-			customInputs: map[string]string{},
-			want:         map[string]string{},
-			wantErrMsg:   "apply default variables: cyclical reference detected",
+			want:       map[string]string{},
+			wantErrMsg: "apply default variables: cyclical reference detected",
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.testName, func(t *testing.T) {
-			if err := tt.draftConfig.ApplyDefaultVariables(tt.customInputs); err != nil && err.Error() != tt.wantErrMsg {
+			if err := tt.draftConfig.ApplyDefaultVariables(); err != nil && err.Error() != tt.wantErrMsg {
 				t.Error(err)
 			} else {
 				for k, v := range tt.want {
-					if tt.customInputs[k] != v {
-						t.Errorf("got: %s, want: %s", tt.customInputs[k], v)
+					variable, err := tt.draftConfig.GetVariable(k)
+					if err != nil {
+						t.Error(err)
+					}
+
+					if variable.Value != v {
+						t.Errorf("got: %s, want: %s, for test: %s", variable.Value, v, tt.testName)
 					}
 				}
 			}
diff --git a/pkg/languages/languages_test.go b/pkg/languages/languages_test.go
index 72b88ba..d041691 100644
--- a/pkg/languages/languages_test.go
+++ b/pkg/languages/languages_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
+	"github.com/Azure/draft/pkg/config"
 	"github.com/Azure/draft/pkg/templatewriter/writers"
 	"github.com/Azure/draft/template"
 )
@@ -12,9 +13,17 @@ import (
 func TestLanguagesCreateDockerfileFileMap(t *testing.T) {
 	templateWriter := &writers.FileMapWriter{}
 	l := CreateLanguagesFromEmbedFS(template.Dockerfiles, "/test/dest/dir")
-	err := l.CreateDockerfileForLanguage("go", map[string]string{
-		"PORT":    "8080",
-		"VERSION": "14",
+	err := l.CreateDockerfileForLanguage("go", &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "PORT",
+				Value: "8080",
+			},
+			{
+				Name:  "VERSION",
+				Value: "14",
+			},
+		},
 	}, templateWriter)
 
 	assert.Nil(t, err)
diff --git a/pkg/osutil/osutil_test.go b/pkg/osutil/osutil_test.go
index 8536a77..ed10806 100644
--- a/pkg/osutil/osutil_test.go
+++ b/pkg/osutil/osutil_test.go
@@ -110,7 +110,7 @@ func TestAllVariablesSubstituted(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.String, func(t *testing.T) {
-			err := CheckAllVariablesSubstituted(test.String)
+			err := checkAllVariablesSubstituted(test.String)
 			didError := err != nil
 			assert.Equal(t, test.ExpectError, didError)
 		})
diff --git a/pkg/prompts/prompts_test.go b/pkg/prompts/prompts_test.go
index ffc54d5..5f5d55c 100644
--- a/pkg/prompts/prompts_test.go
+++ b/pkg/prompts/prompts_test.go
@@ -9,80 +9,85 @@ import (
 
 func TestGetVariableDefaultValue(t *testing.T) {
 	tests := []struct {
-		testName  string
-		variables []config.BuilderVar
-		inputs    map[string]string
-		want      string
+		testName    string
+		draftConfig *config.DraftConfig
+		want        string
 	}{
 		{
 			testName: "basicLiteralExtractDefault",
-			variables: []config.BuilderVar{
-				{
-					Name: "var1",
-					Default: config.BuilderVarDefault{
-						Value: "default-value-1",
+			draftConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "var1",
+						Default: config.BuilderVarDefault{
+							Value: "default-value-1",
+						},
 					},
-				},
-				{
-					Name: "var2",
-					Default: config.BuilderVarDefault{
-						Value: "default-value-2",
+					{
+						Name: "var2",
+						Default: config.BuilderVarDefault{
+							Value: "default-value-2",
+						},
 					},
 				},
 			},
-			inputs: map[string]string{},
-			want:   "default-value-1",
+			want: "default-value-1",
 		},
 		{
 			testName: "noDefaultIsEmptyString",
-			variables: []config.BuilderVar{
-				{
-					Name: "var1",
+			draftConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "var1",
+					},
 				},
 			},
-			inputs: map[string]string{},
-			want:   "",
+			want: "",
 		},
 		{
 			testName: "referenceTakesPrecedenceOverLiteral",
-			variables: []config.BuilderVar{
-				{
-					Name: "var1",
-					Default: config.BuilderVarDefault{
-						ReferenceVar: "var2",
-						Value:        "not-this-value",
+			draftConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "var1",
+						Default: config.BuilderVarDefault{
+							ReferenceVar: "var2",
+							Value:        "not-this-value",
+						},
+					},
+					{
+						Name:  "var2",
+						Value: "this-value",
 					},
 				},
 			},
-			inputs: map[string]string{
-				"var2": "this-value",
-			},
 			want: "this-value",
 		},
 		{
 			testName: "forwardReferencesAreIgnored",
-			variables: []config.BuilderVar{
-				{
-					Name: "beforeVar",
-					Default: config.BuilderVarDefault{
-						ReferenceVar: "afterVar",
-						Value:        "before-default-value",
+			draftConfig: &config.DraftConfig{
+				Variables: []*config.BuilderVar{
+					{
+						Name: "beforeVar",
+						Default: config.BuilderVarDefault{
+							ReferenceVar: "afterVar",
+							Value:        "before-default-value",
+						},
 					},
-				},
-				{
-					Name: "afterVar",
-					Default: config.BuilderVarDefault{
-						Value: "not-this-value",
+					{
+						Name: "afterVar",
+						Default: config.BuilderVarDefault{
+							Value: "not-this-value",
+						},
 					},
 				},
 			},
-			inputs: map[string]string{},
-			want:   "before-default-value",
+			want: "before-default-value",
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.testName, func(t *testing.T) {
-			if got := GetVariableDefaultValue(tt.variables[0], tt.inputs); got != tt.want {
+			if got := GetVariableDefaultValue(tt.draftConfig, tt.draftConfig.Variables[0]); got != tt.want {
 				t.Errorf("GetVariableDefaultValue() = %v, want %v", got, tt.want)
 			}
 		})
@@ -170,7 +175,7 @@ func TestRunStringPrompt(t *testing.T) {
 					t.Errorf("Error closing inWriter: %v", err)
 				}
 			}()
-			got, err := RunDefaultableStringPrompt(tt.defaultValue, tt.prompt, nil, inReader, nil)
+			got, err := RunDefaultableStringPrompt(tt.defaultValue, &tt.prompt, nil, inReader, nil)
 
 			if (err != nil) != tt.wantErr {
 				t.Errorf("RunDefaultableStringPrompt() error = %v, wantErr %v", err, tt.wantErr)
@@ -186,7 +191,7 @@ func TestRunStringPrompt(t *testing.T) {
 func TestRunPromptsFromConfigWithSkipsIO(t *testing.T) {
 	tests := []struct {
 		testName     string
-		config       config.DraftConfig
+		draftConfig  config.DraftConfig
 		userInputs   []string
 		defaultValue string
 		want         map[string]string
@@ -194,8 +199,8 @@ func TestRunPromptsFromConfigWithSkipsIO(t *testing.T) {
 	}{
 		{
 			testName: "onlyNoPrompt",
-			config: config.DraftConfig{
-				Variables: []config.BuilderVar{
+			draftConfig: config.DraftConfig{
+				Variables: []*config.BuilderVar{
 					{
 						Name: "var1",
 						Default: config.BuilderVarDefault{
@@ -213,8 +218,8 @@ func TestRunPromptsFromConfigWithSkipsIO(t *testing.T) {
 			wantErr: false,
 		}, {
 			testName: "twoPromptTwoNoPrompt",
-			config: config.DraftConfig{
-				Variables: []config.BuilderVar{
+			draftConfig: config.DraftConfig{
+				Variables: []*config.BuilderVar{
 					{
 						Name: "var1-no-prompt",
 						Default: config.BuilderVarDefault{
@@ -273,16 +278,15 @@ func TestRunPromptsFromConfigWithSkipsIO(t *testing.T) {
 					t.Errorf("Error closing inWriter: %v", err)
 				}
 			}()
-			got, err := RunPromptsFromConfigWithSkipsIO(&tt.config, nil, inReader, nil)
+			err := RunPromptsFromConfigWithSkipsIO(&tt.draftConfig, inReader, nil)
 
 			if (err != nil) != tt.wantErr {
 				t.Errorf("TestRunPromptsFromConfigWithSkipsIO() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			for k, v := range got {
-				wantVal := tt.want[k]
-				if v != wantVal {
-					t.Errorf("TestRunPromptsFromConfigWithSkipsIO()  inputs [%s]=%s, want %s", k, v, wantVal)
+			for _, variable := range tt.draftConfig.Variables {
+				if got := variable.Value; got != tt.want[variable.Name] {
+					t.Errorf("TestRunPromptsFromConfigWithSkipsIO()  inputs [%s]=%s, want %s", variable.Name, got, tt.want[variable.Name])
 				}
 			}
 		})
diff --git a/pkg/templatewriter/writers/filemapwriter_test.go b/pkg/templatewriter/writers/filemapwriter_test.go
index 98d09e1..d184f6d 100644
--- a/pkg/templatewriter/writers/filemapwriter_test.go
+++ b/pkg/templatewriter/writers/filemapwriter_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
+	"github.com/Azure/draft/pkg/config"
 	"github.com/Azure/draft/pkg/osutil"
 	"github.com/Azure/draft/template"
 )
@@ -12,9 +13,17 @@ import (
 func TestCopyDirToFileMap(t *testing.T) {
 
 	templatewriter := &FileMapWriter{}
-	err := osutil.CopyDir(template.Dockerfiles, "dockerfiles/javascript", "/test/dir", nil, map[string]string{
-		"PORT":    "8080",
-		"VERSION": "14",
+	err := osutil.CopyDir(template.Dockerfiles, "dockerfiles/javascript", "/test/dir", &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "PORT",
+				Value: "8080",
+			},
+			{
+				Name:  "VERSION",
+				Value: "14",
+			},
+		},
 	}, templatewriter)
 	assert.Nil(t, err)
 	assert.NotNil(t, templatewriter.FileMap)
diff --git a/pkg/workflows/workflows_test.go b/pkg/workflows/workflows_test.go
index 621d55c..1f40540 100644
--- a/pkg/workflows/workflows_test.go
+++ b/pkg/workflows/workflows_test.go
@@ -23,15 +23,147 @@ import (
 
 func TestCreateWorkflows(t *testing.T) {
 	dest := "."
-	deployType := "helm"
 	templatewriter := &writers.LocalFSWriter{}
-	flagValuesMap := map[string]string{"WORKFLOWNAME": "testWorkflow", "BRANCHNAME": "testBranch", "ACRRESOURCEGROUP": "testAcrRG", "AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "CLUSTERRESOURCEGROUP": "testClusterRG", "CLUSTERNAME": "testCluster", "DOCKERFILE": "./Dockerfile", "BUILDCONTEXTPATH": ".", "NAMESPACE": "default", "PRIVATECLUSTER": "false"}
-	flagValuesMapNoRoot := map[string]string{"WORKFLOWNAME": "testWorkflow", "BRANCHNAME": "testBranch", "ACRRESOURCEGROUP": "testAcrRG", "AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "CLUSTERRESOURCEGROUP": "testClusterRG", "CLUSTERNAME": "testCluster", "DOCKERFILE": "./Dockerfile", "BUILDCONTEXTPATH": "test", "NAMESPACE": "default", "PRIVATECLUSTER": "false"}
+	draftConfig := &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "WORKFLOWNAME",
+				Value: "testWorkflow",
+			},
+			{
+				Name:  "BRANCHNAME",
+				Value: "testBranch",
+			},
+			{
+				Name:  "ACRRESOURCEGROUP",
+				Value: "testAcrRG",
+			},
+			{
+				Name:  "AZURECONTAINERREGISTRY",
+				Value: "testAcr",
+			},
+			{
+				Name:  "CONTAINERNAME",
+				Value: "testContainer",
+			},
+			{
+				Name:  "CLUSTERRESOURCEGROUP",
+				Value: "testClusterRG",
+			},
+			{
+				Name:  "CLUSTERNAME",
+				Value: "testCluster",
+			},
+			{
+				Name:  "KUSTOMIZEPATH",
+				Value: "./overlays/production",
+			},
+			{
+				Name:  "DEPLOYMENTMANIFESTPATH",
+				Value: "./manifests",
+			},
+			{
+				Name:  "DOCKERFILE",
+				Value: "./Dockerfile",
+			},
+			{
+				Name:  "BUILDCONTEXTPATH",
+				Value: ".",
+			},
+			{
+				Name:  "CHARTPATH",
+				Value: "testPath",
+			},
+			{
+				Name:  "CHARTOVERRIDEPATH",
+				Value: "testOverridePath",
+			},
+			{
+				Name:  "CHARTOVERRIDES",
+				Value: "replicas:2",
+			},
+			{
+				Name:  "NAMESPACE",
+				Value: "default",
+			},
+			{
+				Name:  "PRIVATECLUSTER",
+				Value: "false",
+			},
+		},
+	}
+	draftConfigNoRoot := &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "WORKFLOWNAME",
+				Value: "testWorkflow",
+			},
+			{
+				Name:  "BRANCHNAME",
+				Value: "testBranch",
+			},
+			{
+				Name:  "ACRRESOURCEGROUP",
+				Value: "testAcrRG",
+			},
+			{
+				Name:  "AZURECONTAINERREGISTRY",
+				Value: "testAcr",
+			},
+			{
+				Name:  "CONTAINERNAME",
+				Value: "testContainer",
+			},
+			{
+				Name:  "CLUSTERRESOURCEGROUP",
+				Value: "testClusterRG",
+			},
+			{
+				Name:  "CLUSTERNAME",
+				Value: "testCluster",
+			},
+			{
+				Name:  "KUSTOMIZEPATH",
+				Value: "./overlays/production",
+			},
+			{
+				Name:  "DEPLOYMENTMANIFESTPATH",
+				Value: "./manifests",
+			},
+			{
+				Name:  "DOCKERFILE",
+				Value: "./Dockerfile",
+			},
+			{
+				Name:  "BUILDCONTEXTPATH",
+				Value: "test",
+			},
+			{
+				Name:  "CHARTPATH",
+				Value: "testPath",
+			},
+			{
+				Name:  "CHARTOVERRIDEPATH",
+				Value: "testOverridePath",
+			},
+			{
+				Name:  "CHARTOVERRIDES",
+				Value: "replicas:2",
+			},
+			{
+				Name:  "NAMESPACE",
+				Value: "default",
+			},
+			{
+				Name:  "PRIVATECLUSTER",
+				Value: "false",
+			},
+		},
+	}
 
 	tests := []struct {
 		name         string
 		deployType   string
-		flagValues   map[string]string
 		shouldError  bool
 		tempDirPath  string
 		tempFileName string
@@ -41,7 +173,6 @@ func TestCreateWorkflows(t *testing.T) {
 		{
 			name:         "helm",
 			deployType:   "helm",
-			flagValues:   flagValuesMap,
 			shouldError:  false,
 			tempDirPath:  "charts",
 			tempFileName: "charts/production.yaml",
@@ -54,7 +185,6 @@ func TestCreateWorkflows(t *testing.T) {
 		{
 			name:         "kustomize",
 			deployType:   "kustomize",
-			flagValues:   flagValuesMap,
 			shouldError:  false,
 			tempDirPath:  "overlays/production",
 			tempFileName: "overlays/production/deployment.yaml",
@@ -67,7 +197,6 @@ func TestCreateWorkflows(t *testing.T) {
 		{
 			name:         "manifests",
 			deployType:   "manifests",
-			flagValues:   flagValuesMap,
 			shouldError:  false,
 			tempDirPath:  "manifests",
 			tempFileName: "manifests/deployment.yaml",
@@ -80,7 +209,6 @@ func TestCreateWorkflows(t *testing.T) {
 		{
 			name:         "missing manifest",
 			deployType:   "manifests",
-			flagValues:   flagValuesMap,
 			shouldError:  true,
 			tempDirPath:  "manifests",
 			tempFileName: "manifests/deployment.yaml",
@@ -93,7 +221,6 @@ func TestCreateWorkflows(t *testing.T) {
 		{
 			name:         "invalid deploy type",
 			deployType:   "invalid",
-			flagValues:   flagValuesMap,
 			shouldError:  true,
 			tempDirPath:  "manifests",
 			tempFileName: "manifests/deployment.yaml",
@@ -109,12 +236,12 @@ func TestCreateWorkflows(t *testing.T) {
 		assert.Nil(t, err)
 
 		workflows := CreateWorkflowsFromEmbedFS(template.Workflows, dest)
-		err = workflows.CreateWorkflowFiles(deployType, flagValuesMap, templatewriter)
-		if err != nil {
+		err = workflows.CreateWorkflowFiles(tt.deployType, draftConfig, templatewriter)
+		if err != nil && tt.shouldError == false {
 			t.Errorf("Default Build Context CreateWorkflows() error = %v, wantErr %v", err, tt.shouldError)
 		}
-		err = workflows.CreateWorkflowFiles(deployType, flagValuesMapNoRoot, templatewriter)
-		if err != nil {
+		err = workflows.CreateWorkflowFiles(tt.deployType, draftConfigNoRoot, templatewriter)
+		if err != nil && tt.shouldError == false {
 			t.Errorf("Custom Build Context CreateWorkflows() error = %v, wantErr %v", err, tt.shouldError)
 		}
 
@@ -180,10 +307,21 @@ func TestUpdateProductionDeploymentsInvalid(t *testing.T) {
 }
 
 func TestUpdateProductionDeploymentsMissing(t *testing.T) {
-	flagValuesMap := map[string]string{"AZURECONTAINERREGISTRY": "testRegistry", "CONTAINERNAME": "testContainer"}
+	draftConfig := &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "AZURECONTAINERREGISTRY",
+				Value: "testRegistry",
+			},
+			{
+				Name:  "CONTAINERNAME",
+				Value: "testContainer",
+			},
+		},
+	}
 	testTemplateWriter := &writers.LocalFSWriter{}
 	//test for missing deploy type
-	assert.Nil(t, updateProductionDeployments("", ".", flagValuesMap, testTemplateWriter))
+	assert.Nil(t, UpdateProductionDeployments("", ".", draftConfig, testTemplateWriter))
 
 	//test for missing helm deployment file
 	assert.NotNil(t, setHelmContainerImage("", "testImage", testTemplateWriter))
@@ -237,9 +375,127 @@ func TestPopulateConfigs(t *testing.T) {
 
 func TestCreateWorkflowFiles(t *testing.T) {
 	templatewriter := &writers.LocalFSWriter{}
-	customInputs := map[string]string{"WORKFLOWNAME": "testWorkflow", "BRANCHNAME": "testBranch", "ACRRESOURCEGROUP": "testAcrRG", "AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "CLUSTERRESOURCEGROUP": "testClusterRG", "CLUSTERNAME": "testCluster", "DOCKERFILE": "./Dockerfile", "BUILDCONTEXTPATH": ".", "CHARTPATH": "testPath", "CHARTOVERRIDEPATH": "testOverridePath", "CHARTOVERRIDES": "replicas:2", "NAMESPACE": "default", "PRIVATECLUSTER": "false"}
-	customInputsNoRoot := map[string]string{"WORKFLOWNAME": "testWorkflow", "BRANCHNAME": "testBranch", "ACRRESOURCEGROUP": "testAcrRG", "AZURECONTAINERREGISTRY": "testAcr", "CONTAINERNAME": "testContainer", "CLUSTERRESOURCEGROUP": "testClusterRG", "CLUSTERNAME": "testCluster", "DOCKERFILE": "./Dockerfile", "BUILDCONTEXTPATH": ".", "CHARTPATH": "testPath", "CHARTOVERRIDEPATH": "testOverridePath", "CHARTOVERRIDES": "replicas:2", "NAMESPACE": "default", "PRIVATECLUSTER": "false"}
-	badInputs := map[string]string{}
+	draftConfig := &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "WORKFLOWNAME",
+				Value: "testWorkflow",
+			},
+			{
+				Name:  "BRANCHNAME",
+				Value: "testBranch",
+			},
+			{
+				Name:  "ACRRESOURCEGROUP",
+				Value: "testAcrRG",
+			},
+			{
+				Name:  "AZURECONTAINERREGISTRY",
+				Value: "testAcr",
+			},
+			{
+				Name:  "CONTAINERNAME",
+				Value: "testContainer",
+			},
+			{
+				Name:  "CLUSTERRESOURCEGROUP",
+				Value: "testClusterRG",
+			},
+			{
+				Name:  "CLUSTERNAME",
+				Value: "testCluster",
+			},
+			{
+				Name:  "DOCKERFILE",
+				Value: "./Dockerfile",
+			},
+			{
+				Name:  "BUILDCONTEXTPATH",
+				Value: ".",
+			},
+			{
+				Name:  "CHARTPATH",
+				Value: "testPath",
+			},
+			{
+				Name:  "CHARTOVERRIDEPATH",
+				Value: "testOverridePath",
+			},
+			{
+				Name:  "CHARTOVERRIDES",
+				Value: "replicas:2",
+			},
+			{
+				Name:  "NAMESPACE",
+				Value: "default",
+			},
+			{
+				Name:  "PRIVATECLUSTER",
+				Value: "false",
+			},
+		},
+	}
+	draftConfigNoRoot := &config.DraftConfig{
+		Variables: []*config.BuilderVar{
+			{
+				Name:  "WORKFLOWNAME",
+				Value: "testWorkflow",
+			},
+			{
+				Name:  "BRANCHNAME",
+				Value: "testBranch",
+			},
+			{
+				Name:  "ACRRESOURCEGROUP",
+				Value: "testAcrRG",
+			},
+			{
+				Name:  "AZURECONTAINERREGISTRY",
+				Value: "testAcr",
+			},
+			{
+				Name:  "CONTAINERNAME",
+				Value: "testContainer",
+			},
+			{
+				Name:  "CLUSTERRESOURCEGROUP",
+				Value: "testClusterRG",
+			},
+			{
+				Name:  "CLUSTERNAME",
+				Value: "testCluster",
+			},
+			{
+				Name:  "DOCKERFILE",
+				Value: "./Dockerfile",
+			},
+			{
+				Name:  "BUILDCONTEXTPATH",
+				Value: "test",
+			},
+			{
+				Name:  "CHARTPATH",
+				Value: "testPath",
+			},
+			{
+				Name:  "CHARTOVERRIDEPATH",
+				Value: "testOverridePath",
+			},
+			{
+				Name:  "CHARTOVERRIDES",
+				Value: "replicas:2",
+			},
+			{
+				Name:  "NAMESPACE",
+				Value: "default",
+			},
+			{
+				Name:  "PRIVATECLUSTER",
+				Value: "false",
+			},
+		},
+	}
+	badDraftConfig := &config.DraftConfig{}
 
 	workflowTemplate, err := createMockWorkflowTemplatesFS()
 	assert.Nil(t, err)
@@ -249,18 +505,18 @@ func TestCreateWorkflowFiles(t *testing.T) {
 
 	mockWF.populateConfigs()
 
-	err = mockWF.CreateWorkflowFiles("fakeDeployType", customInputs, templatewriter)
+	err = mockWF.CreateWorkflowFiles("fakeDeployType", draftConfig, templatewriter)
 	assert.NotNil(t, err)
 
-	err = mockWF.CreateWorkflowFiles("helm", customInputs, templatewriter)
+	err = mockWF.CreateWorkflowFiles("helm", draftConfig, templatewriter)
 	assert.Nil(t, err)
 	os.RemoveAll(".github")
 
-	err = mockWF.CreateWorkflowFiles("helm", customInputsNoRoot, templatewriter)
+	err = mockWF.CreateWorkflowFiles("helm", draftConfigNoRoot, templatewriter)
 	assert.Nil(t, err)
 	os.RemoveAll(".github")
 
-	err = mockWF.CreateWorkflowFiles("helm", badInputs, templatewriter)
+	err = mockWF.CreateWorkflowFiles("helm", badDraftConfig, templatewriter)
 	assert.NotNil(t, err)
 	os.RemoveAll(".github")
 }
@@ -355,7 +611,7 @@ func createMockWorkflow(dirPath string, mockWorkflowTemplates fs.FS) (*Workflows
 
 	w := &Workflows{
 		workflows:         deployMap,
-		dest:              dest,
+		Dest:              dest,
 		configs:           make(map[string]*config.DraftConfig),
 		workflowTemplates: mockWorkflowTemplates,
 	}
@@ -373,7 +629,7 @@ func createTestWorkflowEmbed(dirPath string) (*Workflows, error) {
 
 	w := &Workflows{
 		workflows:         deployMap,
-		dest:              dest,
+		Dest:              dest,
 		configs:           make(map[string]*config.DraftConfig),
 		workflowTemplates: template.Workflows,
 	}
diff --git a/test/gen_integration.sh b/test/gen_integration.sh
index 24ffb4f..0ccfb1e 100755
--- a/test/gen_integration.sh
+++ b/test/gen_integration.sh
@@ -305,7 +305,7 @@ languageVariables:
           curl -m 3 \$SERVICEIP:$serviceport
           kill \$tunnelPID
       - run: |
-          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+          ./draft -v generate-workflow -d ./langtest/ --deploy-type helm --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
           pwd
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
@@ -455,7 +455,7 @@ languageVariables:
           echo 'Curling service IP'
           curl -m 3 \$SERVICEIP:$serviceport
           kill \$tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type kustomize --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1
@@ -596,7 +596,7 @@ languageVariables:
           echo 'Curling service IP'
           curl -m 3 \$SERVICEIP:$serviceport
           kill \$tunnelPID
-      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests -w someWorkflow -b main --acr-resource-group someAcrResourceGroup -r someRegistry --container-name someContainer -g someClusterResourceGroup -c someAksCluster --dockerfile ./Dockerfile --build-context-path . -n default --private-cluster false
+      - run: ./draft -v generate-workflow -d ./langtest/ --deploy-type manifests --variable WORKFLOWNAME=someWorkflow --variable BRANCHNAME=main --variable ACRRESOURCEGROUP=someAcrResourceGroup --variable AZURECONTAINERREGISTRY=someRegistry --variable CONTAINERNAME=someContainer --variable CLUSTERRESOURCEGROUP=someClusterResourceGroup --variable CLUSTERNAME=someAksCluster --variable DOCKERFILE=./Dockerfile --variable BUILDCONTEXTPATH=. --variable NAMESPACE=default --variable PRIVATECLUSTER=false
       # Validate generated workflow yaml
       - name: Install action-validator with asdf
         uses: asdf-vm/actions/install@v1