Repository: diggerhq/digger
Pull request #144 (2023-04-18 16:34:37)

feat: upload generated plans and use them when applying


Patch:
diff --git a/action.yml b/action.yml
index b5dc45e3..4d572ea7 100644
--- a/action.yml
+++ b/action.yml
@@ -112,6 +112,21 @@ runs:
         mv digger /usr/local/bin/digger
         cd ${{ github.workspace }}
         digger
+    - name: generate artifact name based on issue number or pr number
+      id: artifact
+      shell: bash
+      run: |
+          if [[ "${{ github.event_name }}" == "issue_comment" ]]; then
+            echo "::set-output name=artifact::plans-${{ github.event.issue.number }}"
+          else
+            echo "::set-output name=artifact::plans-${{ github.event.number }}"
+          fi
+    - name: upload plan
+      uses: actions/upload-artifact@v3
+      with:
+          name: ${{ steps.artifact.outputs.artifact }}
+          path: '*.tfplan'
+          retention-days: 14
 
 branding:
   icon: globe
diff --git a/go.mod b/go.mod
index 38a297a5..cf6010e0 100644
--- a/go.mod
+++ b/go.mod
@@ -18,13 +18,16 @@ require (
 	cloud.google.com/go/compute v1.18.0 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/iam v0.12.0 // indirect
+	github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
 	github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect
 	github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
+	github.com/cloudflare/circl v1.1.0 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
+	github.com/google/go-github/v51 v51.0.0 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
 	github.com/googleapis/gax-go/v2 v2.7.1 // indirect
@@ -32,7 +35,7 @@ require (
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	golang.org/x/crypto v0.5.0 // indirect
+	golang.org/x/crypto v0.7.0 // indirect
 	golang.org/x/net v0.8.0 // indirect
 	golang.org/x/oauth2 v0.6.0 // indirect
 	golang.org/x/sys v0.6.0 // indirect
@@ -48,7 +51,6 @@ require (
 
 require (
 	github.com/davecgh/go-spew v1.1.1
-	github.com/google/go-github/v50 v50.1.0
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 )
diff --git a/go.sum b/go.sum
index a02f60aa..82ee8486 100644
--- a/go.sum
+++ b/go.sum
@@ -21,31 +21,46 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLC
 github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c=
 github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
+github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
+github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
+github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
+github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
+github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
+github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
+github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
 github.com/aws/aws-sdk-go v1.44.219 h1:YOFxTUQZvdRzgwb6XqLFRwNHxoUdKBuunITC7IFhvbc=
 github.com/aws/aws-sdk-go v1.44.219/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
 github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
+github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
+github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
+github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
 github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
 github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
-github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@@ -68,6 +83,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v50 v50.1.0 h1:hMUpkZjklC5GJ+c3GquSqOP/T4BNsB7XohaPhtMOzRk=
 github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA=
+github.com/google/go-github/v51 v51.0.0 h1:KCjsbgPV28VoRftdP+K2mQL16jniUsLAJknsOVKwHyU=
+github.com/google/go-github/v51 v51.0.0/go.mod h1:kZj/rn/c1lSUbr/PFWl2hhusPV7a5XNYKcwPrd5L3Us=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
@@ -79,17 +96,32 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9
 github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
 github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
 github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/hc-install v0.5.0 h1:D9bl4KayIYKEeJ4vUDe9L5huqxZXczKaykSRcmQ0xY0=
+github.com/hashicorp/terraform-exec v0.18.1 h1:LAbfDvNQU1l0NOQlTuudjczVhHj061fNX5H8XZxHlH4=
+github.com/hashicorp/terraform-exec v0.18.1/go.mod h1:58wg4IeuAJ6LVsLUeD2DWZZoc/bYi6dzhLHzxM41980=
+github.com/hashicorp/terraform-json v0.15.0 h1:/gIyNtR6SFw6h5yzlbDbACyGvIhKtQi8mTsbkNd79lE=
+github.com/hashicorp/terraform-json v0.15.0/go.mod h1:+L1RNzjDU5leLFZkHTFTbJXaoqUC6TqXlFgDoOXrtvk=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
 github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI=
 github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
@@ -97,15 +129,27 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
+github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
+github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
+github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
+github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
+github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
+github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
+github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -113,18 +157,23 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -145,6 +194,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -156,6 +206,7 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
@@ -175,6 +226,7 @@ google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
 google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -205,8 +257,10 @@ google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/configuration/digger_config.go b/pkg/configuration/digger_config.go
index f07fb5d3..0a4e017b 100644
--- a/pkg/configuration/digger_config.go
+++ b/pkg/configuration/digger_config.go
@@ -175,8 +175,7 @@ func NewDiggerConfig(workingDir string, walker DirWalker) (*DiggerConfig, error)
 		}
 	}
 
-	data, err := os.ReadFile(fileName)
-	if err != nil {
+	if fileName == "" {
 		config.Projects = make([]Project, 1)
 		config.Projects[0] = defaultProject()
 		config.Workflows = make(map[string]Workflow)
@@ -212,10 +211,19 @@ func NewDiggerConfig(workingDir string, walker DirWalker) (*DiggerConfig, error)
 		return c, nil
 	}
 
+	data, err := os.ReadFile(fileName)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read config file %s: %v", fileName, err)
+	}
+
 	if err := yaml.Unmarshal(data, config); err != nil {
 		return nil, fmt.Errorf("error parsing '%s': %v", fileName, err)
 	}
 
+	if (config.Projects == nil || len(config.Projects) == 0) && config.GenerateProjectsConfig == nil {
+		return nil, fmt.Errorf("no projects configuration found in '%s'", fileName)
+	}
+
 	c, err := ConvertDiggerYamlToConfig(config, workingDir, walker)
 	if err != nil {
 		return nil, err
diff --git a/pkg/digger/digger.go b/pkg/digger/digger.go
index 45936b10..35e10567 100644
--- a/pkg/digger/digger.go
+++ b/pkg/digger/digger.go
@@ -75,11 +75,14 @@ func RunCommandsPerProject(commandsPerProject []ProjectCommand, repoOwner string
 			}
 
 			commandRunner := CommandRunner{}
+			zipManager := &utils.Zipper{}
 
 			diggerExecutor := DiggerExecutor{
+				projectCommands.ProjectName,
 				projectCommands.ApplyStage,
 				projectCommands.PlanStage,
 				commandRunner,
+				zipManager,
 				terraformExecutor,
 				prManager,
 				projectLock,
@@ -283,9 +286,11 @@ func parseProjectName(comment string) string {
 }
 
 type DiggerExecutor struct {
+	projectName       string
 	applyStage        configuration.Stage
 	planStage         configuration.Stage
 	commandRunner     CommandRun
+	zipManager        utils.Zip
 	terraformExecutor terraform.TerraformExecutor
 	prManager         github.PullRequestManager
 	lock              utils.ProjectLock
@@ -318,6 +323,10 @@ func (c CommandRunner) Run(command string) (string, string, error) {
 	return stdout.String(), stderr.String(), err
 }
 
+func (d DiggerExecutor) planFileName() string {
+	return d.projectName + ".tfplan"
+}
+
 func (d DiggerExecutor) Plan(prNumber int) error {
 
 	res, err := d.lock.Lock(prNumber)
@@ -334,6 +343,8 @@ func (d DiggerExecutor) Plan(prNumber int) error {
 				}
 			}
 			if step.Action == "plan" {
+				planArgs := []string{"-out", d.planFileName()}
+				planArgs = append(planArgs, step.ExtraArgs...)
 				isNonEmptyPlan, stdout, stderr, err := d.terraformExecutor.Plan(step.ExtraArgs)
 				if err != nil {
 					return fmt.Errorf("error executing plan: %v", err)
@@ -356,6 +367,26 @@ func (d DiggerExecutor) Plan(prNumber int) error {
 }
 
 func (d DiggerExecutor) Apply(prNumber int) error {
+	plansFilename, err := d.prManager.DownloadLatestPlans(prNumber)
+
+	if err != nil {
+		return fmt.Errorf("error downloading plan: %v", err)
+	}
+
+	if plansFilename == "" {
+		return fmt.Errorf("no plans found for this PR")
+	}
+
+	plansFilename, err = d.zipManager.GetFileFromZip(plansFilename, d.planFileName())
+
+	if err != nil {
+		return fmt.Errorf("error extracting plan: %v", err)
+	}
+
+	if plansFilename == "" {
+		return fmt.Errorf("no plans found for this project")
+	}
+
 	isMergeable, _, err := d.prManager.IsMergeable(prNumber)
 	if err != nil {
 		return fmt.Errorf("error validating is PR is mergeable: %v", err)
@@ -376,7 +407,7 @@ func (d DiggerExecutor) Apply(prNumber int) error {
 					}
 				}
 				if step.Action == "apply" {
-					stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs)
+					stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs, plansFilename)
 					applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
 					comment := "Apply for **" + d.lock.LockId() + "**\n" + applyOutput
 					d.prManager.PublishComment(prNumber, comment)
diff --git a/pkg/github/github.go b/pkg/github/github.go
index 4e737efe..8a5c292c 100644
--- a/pkg/github/github.go
+++ b/pkg/github/github.go
@@ -2,21 +2,27 @@ package github
 
 import (
 	"context"
+	"fmt"
+	"io"
 	"log"
+	"net/http"
+	"net/url"
+	"os"
+	"strconv"
 
-	"github.com/google/go-github/v50/github"
+	"github.com/google/go-github/v51/github"
 )
 
 func NewGithubPullRequestService(ghToken string, repoName string, owner string) PullRequestManager {
 	client := github.NewTokenClient(context.Background(), ghToken)
-	return &GithubPullRequestService{
+	return &GithubService{
 		Client:   client,
 		RepoName: repoName,
 		Owner:    owner,
 	}
 }
 
-type GithubPullRequestService struct {
+type GithubService struct {
 	Client   *github.Client
 	RepoName string
 	Owner    string
@@ -29,9 +35,86 @@ type PullRequestManager interface {
 	GetCombinedPullRequestStatus(prNumber int) (string, error)
 	MergePullRequest(prNumber int) error
 	IsMergeable(prNumber int) (bool, string, error)
+	DownloadLatestPlans(prNumber int) (string, error)
 }
 
-func (svc *GithubPullRequestService) GetChangedFiles(prNumber int) ([]string, error) {
+func (svc *GithubService) DownloadLatestPlans(prNumber int) (string, error) {
+	artifacts, _, err := svc.Client.Actions.ListArtifacts(context.Background(), svc.Owner, svc.RepoName, &github.ListOptions{
+		PerPage: 100,
+	})
+
+	if err != nil {
+		return "", err
+	}
+
+	latestPlans := getLatestArtifactWithName(artifacts.Artifacts, "plans-"+strconv.Itoa(prNumber))
+
+	if latestPlans == nil {
+		return "", nil
+	}
+
+	downloadUrl, _, err := svc.Client.Actions.DownloadArtifact(context.Background(), svc.Owner, svc.RepoName, *latestPlans.ID, true)
+
+	if err != nil {
+		return "", err
+	}
+	filename := "plans-" + strconv.Itoa(prNumber) + ".zip"
+
+	err = downloadArtifactIntoFile(svc.Client.Client(), downloadUrl, filename)
+
+	if err != nil {
+		return "", err
+	}
+	return filename, nil
+}
+
+func downloadArtifactIntoFile(client *http.Client, artifactUrl *url.URL, outputFile string) error {
+
+	req, err := http.NewRequest("GET", artifactUrl.String(), nil)
+	if err != nil {
+		return err
+	}
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("failed to download artifact, status code: %d", resp.StatusCode)
+	}
+
+	out, err := os.Create(outputFile)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	_, err = io.Copy(out, resp.Body)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func getLatestArtifactWithName(artifacts []*github.Artifact, name string) *github.Artifact {
+	var latest *github.Artifact
+
+	for _, item := range artifacts {
+		if *item.Name != name {
+			continue
+		}
+		if item.UpdatedAt.Time.After(latest.UpdatedAt.Time) {
+			latest = item
+		}
+	}
+
+	return latest
+}
+
+func (svc *GithubService) GetChangedFiles(prNumber int) ([]string, error) {
 	files, _, err := svc.Client.PullRequests.ListFiles(context.Background(), svc.Owner, svc.RepoName, prNumber, nil)
 	if err != nil {
 		log.Fatalf("error getting pull request: %v", err)
@@ -45,14 +128,14 @@ func (svc *GithubPullRequestService) GetChangedFiles(prNumber int) ([]string, er
 	return fileNames, nil
 }
 
-func (svc *GithubPullRequestService) PublishComment(prNumber int, comment string) {
+func (svc *GithubService) PublishComment(prNumber int, comment string) {
 	_, _, err := svc.Client.Issues.CreateComment(context.Background(), svc.Owner, svc.RepoName, prNumber, &github.IssueComment{Body: &comment})
 	if err != nil {
 		log.Fatalf("error publishing comment: %v", err)
 	}
 }
 
-func (svc *GithubPullRequestService) SetStatus(prNumber int, status string, statusContext string) error {
+func (svc *GithubService) SetStatus(prNumber int, status string, statusContext string) error {
 	pr, _, err := svc.Client.PullRequests.Get(context.Background(), svc.Owner, svc.RepoName, prNumber)
 	if err != nil {
 		log.Fatalf("error getting pull request: %v", err)
@@ -66,7 +149,7 @@ func (svc *GithubPullRequestService) SetStatus(prNumber int, status string, stat
 	return err
 }
 
-func (svc *GithubPullRequestService) GetCombinedPullRequestStatus(prNumber int) (string, error) {
+func (svc *GithubService) GetCombinedPullRequestStatus(prNumber int) (string, error) {
 	pr, _, err := svc.Client.PullRequests.Get(context.Background(), svc.Owner, svc.RepoName, prNumber)
 	if err != nil {
 		log.Fatalf("error getting pull request: %v", err)
@@ -80,7 +163,7 @@ func (svc *GithubPullRequestService) GetCombinedPullRequestStatus(prNumber int)
 	return *statuses.State, nil
 }
 
-func (svc *GithubPullRequestService) MergePullRequest(prNumber int) error {
+func (svc *GithubService) MergePullRequest(prNumber int) error {
 	pr, _, err := svc.Client.PullRequests.Get(context.Background(), svc.Owner, svc.RepoName, prNumber)
 	if err != nil {
 		log.Fatalf("error getting pull request: %v", err)
@@ -93,7 +176,7 @@ func (svc *GithubPullRequestService) MergePullRequest(prNumber int) error {
 	return err
 }
 
-func (svc *GithubPullRequestService) IsMergeable(prNumber int) (bool, string, error) {
+func (svc *GithubService) IsMergeable(prNumber int) (bool, string, error) {
 	pr, _, err := svc.Client.PullRequests.Get(context.Background(), svc.Owner, svc.RepoName, prNumber)
 	if err != nil {
 		log.Fatalf("error getting pull request: %v", err)
diff --git a/pkg/github/pullrequestmanager_mock.go b/pkg/github/pullrequestmanager_mock.go
index 69979f4a..4484b34b 100644
--- a/pkg/github/pullrequestmanager_mock.go
+++ b/pkg/github/pullrequestmanager_mock.go
@@ -32,3 +32,8 @@ func (mockGithubPullrequestManager *MockGithubPullrequestManager) IsMergeable(pr
 	mockGithubPullrequestManager.commands = append(mockGithubPullrequestManager.commands, "IsMergeable")
 	return true, "", nil
 }
+
+func (mockGithubPullrequestManager *MockGithubPullrequestManager) DownloadLatestPlans(prNumber int) (string, error) {
+	mockGithubPullrequestManager.commands = append(mockGithubPullrequestManager.commands, "DownloadLatestPlans")
+	return "", nil
+}
diff --git a/pkg/terraform/tf.go b/pkg/terraform/tf.go
index d21e1934..124c2009 100644
--- a/pkg/terraform/tf.go
+++ b/pkg/terraform/tf.go
@@ -11,7 +11,7 @@ import (
 
 type TerraformExecutor interface {
 	Init([]string) (string, string, error)
-	Apply([]string) (string, string, error)
+	Apply([]string, string) (string, string, error)
 	Plan([]string) (bool, string, string, error)
 }
 
@@ -29,9 +29,10 @@ func (terragrunt Terragrunt) Init(params []string) (string, string, error) {
 
 }
 
-func (terragrunt Terragrunt) Apply(params []string) (string, string, error) {
+func (terragrunt Terragrunt) Apply(params []string, plan string) (string, string, error) {
 	params = append(params, "--auto-approve")
 	params = append(params, "--terragrunt-non-interactive")
+	params = append(params, plan)
 	stdout, stderr, err := terragrunt.runTerragruntCommand("apply", params...)
 	return stdout, stderr, err
 }
@@ -74,7 +75,7 @@ func (tf Terraform) Init(params []string) (string, string, error) {
 	return stdout, stderr, err
 }
 
-func (tf Terraform) Apply(params []string) (string, string, error) {
+func (tf Terraform) Apply(params []string, plan string) (string, string, error) {
 	workspace, _, _, err := tf.runTerraformCommand("workspace", "show")
 
 	if err != nil {
@@ -88,6 +89,7 @@ func (tf Terraform) Apply(params []string) (string, string, error) {
 		}
 	}
 	params = append(append(append(params, "-input=false"), "-no-color"), "-auto-approve")
+	params = append(params, plan)
 	stdout, stderr, _, err := tf.runTerraformCommand("apply", params...)
 	if err != nil {
 		return "", "", err
@@ -151,7 +153,7 @@ func (tf Terraform) Plan(params []string) (bool, string, string, error) {
 			return false, "", "", err
 		}
 	}
-	params = append(append(append(params, "-input=false"), "-no-color"), "-out=tfplan")
+	params = append(append(params, "-input=false"), "-no-color")
 	stdout, stderr, statusCode, err := tf.runTerraformCommand("plan", params...)
 	if err != nil {
 		return false, "", "", err
diff --git a/pkg/utils/io.go b/pkg/utils/io.go
new file mode 100644
index 00000000..52c3cc5b
--- /dev/null
+++ b/pkg/utils/io.go
@@ -0,0 +1,67 @@
+package utils
+
+import (
+	"archive/zip"
+	"bytes"
+	"io"
+	"io/ioutil"
+)
+
+type Zip interface {
+	GetFileFromZip(zipFile string, filename string) (string, error)
+}
+
+type Zipper struct {
+}
+
+func (z *Zipper) GetFileFromZip(zipFile string, filename string) (string, error) {
+	reader, err := zip.OpenReader(zipFile)
+	if err != nil {
+		return "", err
+	}
+	defer reader.Close()
+
+	for _, file := range reader.File {
+		if file.FileInfo().IsDir() {
+			continue
+		}
+		if file.Name == filename {
+			rc, err := file.Open()
+			if err != nil {
+				return "", err
+			}
+			defer rc.Close()
+
+			// Create a buffer to write our archive to.
+			buf := new(bytes.Buffer)
+			_, err = io.Copy(buf, rc)
+			if err != nil {
+				return "", err
+			}
+
+			// Create a temporary file within our temp-images directory that follows
+			// a particular naming pattern
+			tempFile, err := ioutil.TempFile("", "digger-*.tfplan")
+			if err != nil {
+				return "", err
+			}
+			defer tempFile.Close()
+
+			// Read all of the contents of our archive into a byte array.
+			contents, err := ioutil.ReadAll(buf)
+			if err != nil {
+				return "", err
+			}
+
+			// Write the contents of the archive, from the byte array, to our temporary file.
+			_, err = tempFile.Write(contents)
+			if err != nil {
+				return "", err
+			}
+
+			return tempFile.Name(), nil
+		}
+	}
+
+	return "", nil
+}
diff --git a/pkg/utils/mocks.go b/pkg/utils/mocks.go
index acb40570..8ff5742f 100644
--- a/pkg/utils/mocks.go
+++ b/pkg/utils/mocks.go
@@ -65,3 +65,7 @@ func (t MockPullRequestManager) MergePullRequest(prNumber int) error {
 func (t MockPullRequestManager) IsMergeable(prNumber int) (bool, string, error) {
 	return true, "", nil
 }
+
+func (t MockPullRequestManager) DownloadLatestPlans(prNumber int) (string, error) {
+	return "", nil
+}


Test patch:
diff --git a/pkg/configuration/digger_config_test.go b/pkg/configuration/digger_config_test.go
index d72a370a..5fe0e794 100644
--- a/pkg/configuration/digger_config_test.go
+++ b/pkg/configuration/digger_config_test.go
@@ -97,6 +97,8 @@ func TestDefaultValuesForWorkflowConfiguration(t *testing.T) {
 	defer teardown()
 
 	diggerCfg := `
+projects:
+- name: dev
 workflows:
   default:
     plan:
@@ -201,6 +203,20 @@ generate_projects:
 	assert.Equal(t, 1, len(dg.Projects))
 }
 
+func TestMissingProjectsReturnsError(t *testing.T) {
+	tempDir, teardown := setUp()
+	defer teardown()
+
+	diggerCfg := `
+`
+	deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg)
+	defer deleteFile()
+	walker := &MockDirWalker{}
+
+	_, err := NewDiggerConfig(tempDir, walker)
+	assert.ErrorContains(t, err, "no projects configuration found")
+}
+
 func createTempDir() string {
 	dir, err := os.MkdirTemp("", "tmp")
 	if err != nil {
diff --git a/pkg/digger/digger_test.go b/pkg/digger/digger_test.go
index 6a56a591..ef428f67 100644
--- a/pkg/digger/digger_test.go
+++ b/pkg/digger/digger_test.go
@@ -34,8 +34,8 @@ func (m *MockTerraformExecutor) Init(params []string) (string, string, error) {
 	return "", "", nil
 }
 
-func (m *MockTerraformExecutor) Apply(params []string) (string, string, error) {
-	m.Commands = append(m.Commands, RunInfo{"Apply", strings.Join(params, " "), time.Now()})
+func (m *MockTerraformExecutor) Apply(params []string, plan string) (string, string, error) {
+	m.Commands = append(m.Commands, RunInfo{"Apply", strings.Join(params, " ") + " " + plan, time.Now()})
 	return "", "", nil
 }
 
@@ -77,6 +77,11 @@ func (m *MockPRManager) IsMergeable(prNumber int) (bool, string, error) {
 	return true, "", nil
 }
 
+func (m *MockPRManager) DownloadLatestPlans(prNumber int) (string, error) {
+	m.Commands = append(m.Commands, RunInfo{"DownloadLatestPlans", strconv.Itoa(prNumber), time.Now()})
+	return "plan", nil
+}
+
 type MockProjectLock struct {
 	Commands []RunInfo
 }
@@ -101,12 +106,22 @@ func (m *MockProjectLock) LockId() string {
 	return ""
 }
 
+type MockZipper struct {
+	Commands []RunInfo
+}
+
+func (m *MockZipper) GetFileFromZip(zipFile string, filename string) (string, error) {
+	m.Commands = append(m.Commands, RunInfo{"GetFileFromZip", zipFile + " " + filename, time.Now()})
+	return "plan", nil
+}
+
 func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 
 	commandRunner := &MockCommandRunner{}
 	terraformExecutor := &MockTerraformExecutor{}
 	prManager := &MockPRManager{}
 	lock := &MockProjectLock{}
+	zipper := &MockZipper{}
 
 	executor := DiggerExecutor{
 		applyStage: configuration.Stage{
@@ -129,6 +144,7 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 			},
 		},
 		planStage:         configuration.Stage{},
+		zipManager:        zipper,
 		commandRunner:     commandRunner,
 		terraformExecutor: terraformExecutor,
 		prManager:         prManager,
@@ -139,7 +155,7 @@ func TestCorrectCommandExecutionWhenApplying(t *testing.T) {
 
 	commandStrings := allCommandsInOrderWithParams(terraformExecutor, commandRunner, prManager, lock)
 
-	assert.Equal(t, []string{"IsMergeable 1", "Lock 1", "Init ", "Apply ", "LockId ", "PublishComment 1 Apply for ****\n```terraform\n\n```", "Unlock 1", "Run echo", "LockId ", "PublishComment 1 Running echo for ****\n"}, commandStrings)
+	assert.Equal(t, []string{"DownloadLatestPlans 1", "IsMergeable 1", "Lock 1", "Init ", "Apply  plan", "LockId ", "PublishComment 1 Apply for ****\n```terraform\n\n```", "Unlock 1", "Run echo", "LockId ", "PublishComment 1 Running echo for ****\n"}, commandStrings)
 }
 
 func TestCorrectCommandExecutionWhenPlanning(t *testing.T) {
diff --git a/pkg/terraform/tf_test.go b/pkg/terraform/tf_test.go
index 5c48c481..40f8b54e 100644
--- a/pkg/terraform/tf_test.go
+++ b/pkg/terraform/tf_test.go
@@ -54,6 +54,10 @@ func TestExecuteTerraformApplyDefaultWorkspace(t *testing.T) {
 
 	tf := Terraform{WorkingDir: dir, Workspace: "default"}
 	tf.Init([]string{})
-	_, _, err := tf.Apply([]string{})
+	var planArgs []string
+	planArgs = append(planArgs, "-out", "plan.tfplan")
+	tf.Plan(planArgs)
+
+	_, _, err := tf.Apply([]string{}, "plan.tfplan")
 	assert.NoError(t, err)
 }
diff --git a/pkg/utils/io_test.go b/pkg/utils/io_test.go
new file mode 100644
index 00000000..23f91155
--- /dev/null
+++ b/pkg/utils/io_test.go
@@ -0,0 +1,79 @@
+package utils
+
+import (
+	"archive/zip"
+	"bytes"
+	"github.com/stretchr/testify/assert"
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func TestGetFileFromZip(t *testing.T) {
+	buf := new(bytes.Buffer)
+
+	zipWriter := zip.NewWriter(buf)
+
+	fileNames := []string{"file1.txt", "file2.txt"}
+	for _, fileName := range fileNames {
+		_, err := zipWriter.Create(fileName)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+	err := zipWriter.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	zipFileName := "test.zip"
+	err = ioutil.WriteFile(zipFileName, buf.Bytes(), 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+	zipper := &Zipper{}
+	retrievedFile, err := zipper.GetFileFromZip(zipFileName, "file1.txt")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assert.NotEmpty(t, retrievedFile)
+
+	// Clean up the test files.
+	os.Remove(zipFileName)
+}
+
+func TestGetFileFromZipNoFileExists(t *testing.T) {
+	buf := new(bytes.Buffer)
+
+	zipWriter := zip.NewWriter(buf)
+
+	fileNames := []string{"file1.txt", "file2.txt"}
+	for _, fileName := range fileNames {
+		_, err := zipWriter.Create(fileName)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+	err := zipWriter.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	zipFileName := "test.zip"
+	err = ioutil.WriteFile(zipFileName, buf.Bytes(), 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	zipper := &Zipper{}
+	retrievedFile, err := zipper.GetFileFromZip(zipFileName, "file3.txt")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assert.Empty(t, retrievedFile)
+
+	// Clean up the test files.
+	os.Remove(zipFileName)
+}