├── .circleci └── config.yml ├── .github ├── CODEOWNERS ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── api ├── deploy.go └── deploy_test.go ├── example └── v1 │ ├── main.json │ └── vars │ └── count.json ├── install.sh ├── lib ├── ecs.go ├── ecs_test.go ├── iface │ └── ecs.go ├── kms_vault.go └── setting │ ├── loader.go │ ├── prototype │ ├── embedde │ │ └── embedde.go │ ├── fileList_test.go │ ├── isEncrypt_test.go │ ├── kms │ │ └── kms.go │ └── setting.go │ ├── setting.go │ ├── setting_test.go │ └── v1 │ └── setting.go ├── main.go ├── subcmd ├── deploy │ ├── deploy.go │ ├── deploy_test.go │ ├── mergeParams_test.go │ ├── parseDockerImage_test.go │ ├── parseEnv_test.go │ └── parseargs_test.go ├── update │ └── update.go └── vault │ ├── decrypt │ ├── decrypt.go │ ├── prototype │ │ └── prototype.go │ └── v1 │ │ ├── iface │ │ └── iface.go │ │ ├── v1.go │ │ └── v1_test.go │ ├── edit │ ├── edit.go │ ├── prototype │ │ └── prototype.go │ ├── util │ │ ├── util.go │ │ └── util_test.go │ └── v1 │ │ ├── iface │ │ └── iface.go │ │ ├── v1.go │ │ └── v1_test.go │ ├── encrypt │ ├── encrypt.go │ ├── prototype │ │ └── prototype.go │ └── v1 │ │ ├── iface │ │ └── iface.go │ │ ├── v1.go │ │ └── v1_test.go │ ├── vault.go │ ├── vault_test.go │ └── view │ └── view.go ├── test ├── dummy_editor │ └── main.go ├── key │ ├── private-key.pem │ └── public-key.pem ├── lib │ └── setting │ │ ├── 0.json │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ └── 5.json └── subcmd │ ├── deploy │ ├── mergeParams │ │ └── mergeUseDotFile │ │ │ ├── 0.env │ │ │ ├── 1.env │ │ │ ├── 2.env │ │ │ ├── 3.env │ │ │ ├── 4.env │ │ │ ├── 5.env │ │ │ └── 6.env │ └── testFileList │ │ ├── 0 │ │ ├── 0.json │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ └── 9.json │ │ ├── 1 │ │ └── .keep │ │ ├── 2 │ │ ├── 0.json │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ ├── 9.json │ │ ├── a.json │ │ ├── b.json │ │ ├── c.json │ │ ├── d.json │ │ ├── e.json │ │ ├── f.json │ │ ├── g.json │ │ ├── h.json │ │ ├── i.json │ │ ├── j.json │ │ ├── k.json │ │ ├── l.json │ │ ├── m.json │ │ ├── n.json │ │ ├── o.json │ │ ├── p.json │ │ ├── q.json │ │ ├── r.json │ │ ├── s.json │ │ ├── t.json │ │ ├── u.json │ │ ├── v.json │ │ ├── w.json │ │ ├── x.json │ │ ├── y.json │ │ └── z.json │ │ └── 3 │ │ ├── 0.json │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ ├── 9.json │ │ └── json.hoge │ └── vault │ ├── decrypt │ └── v1 │ │ ├── .gitignore │ │ ├── 0.json │ │ └── 1.json │ ├── edit │ └── v1 │ │ ├── 0.json │ │ └── 1.json │ └── encrypt │ └── v1 │ └── 0.json └── vars └── vars.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | go1_10: &go1_10 4 | working_directory: /go/src/github.com/jobtalk/pnzr 5 | docker: 6 | - image: golang:1.10.2 7 | go1_9: &go1_9 8 | working_directory: /go/src/github.com/jobtalk/pnzr 9 | docker: 10 | - image: golang:1.9 11 | go1_8: &go1_8 12 | working_directory: /go/src/github.com/jobtalk/pnzr 13 | docker: 14 | - image: golang:1.8 15 | 16 | test_steps: &test_steps 17 | steps: 18 | - checkout 19 | - run: 20 | name: "setup" 21 | command: | 22 | go get github.com/Masterminds/glide 23 | go install github.com/Masterminds/glide 24 | go get -u github.com/golang/dep/cmd/dep 25 | - run: 26 | name: "Install packages" 27 | command: | 28 | dep ensure 29 | - run: 30 | name: "run test" 31 | command: | 32 | go test -cover $(glide novendor) 33 | check_fmt: &check_fmt 34 | steps: 35 | - checkout 36 | - run: 37 | name: "setup" 38 | command: | 39 | go get github.com/Masterminds/glide 40 | go install github.com/Masterminds/glide 41 | - run: 42 | name: "check fmt" 43 | command: | 44 | if [ $(go fmt $(glide nv) | wc -l) -eq 0 ]; then 45 | exit 0 46 | fi 47 | echo "Go fmt is not applied." 48 | exit 1 49 | jobs: 50 | check_fmt: 51 | <<: *go1_10 52 | <<: *check_fmt 53 | 54 | test_1_10: 55 | <<: *go1_10 56 | <<: *test_steps 57 | test_1_9: 58 | <<: *go1_9 59 | <<: *test_steps 60 | test_1_8: 61 | <<: *go1_8 62 | <<: *test_steps 63 | 64 | build: 65 | <<: *go1_10 66 | steps: 67 | - checkout 68 | - run: 69 | name: "setup" 70 | command: | 71 | go get github.com/Masterminds/glide 72 | go install github.com/Masterminds/glide 73 | go get -u github.com/golang/dep/cmd/dep 74 | 75 | - run: 76 | name: "Install github-release" 77 | command: | 78 | go get github.com/aktau/github-release 79 | go install github.com/aktau/github-release 80 | - run: 81 | name: "Install packages" 82 | command: | 83 | dep ensure 84 | - run: 85 | name: "build pnzr" 86 | command: | 87 | if [ "${CIRCLE_BRANCH}" == "master" ]; then 88 | make 89 | elif [[ "${CIRCLE_BRANCH}" =~ ^release-.* ]]; then 90 | VERSION=${CIRCLE_BRANCH#release-} make 91 | fi 92 | - deploy: 93 | name: "release release branch" 94 | command: | 95 | if [[ "$CIRCLE_BRANCH" =~ ^release-.* ]]; then 96 | VERSION=${CIRCLE_BRANCH#release-} make 97 | VERSION=${CIRCLE_BRANCH#release-} 98 | github-release release -u jobtalk -r pnzr -t "${VERSION}" -n "${VERSION}" -d "${VERSION}" 99 | github-release upload -u jobtalk -r pnzr -t "${VERSION}" --name "pnzr-darwin-amd64" --file bin/darwin/pnzr 100 | github-release upload -u jobtalk -r pnzr -t "${VERSION}" --name "pnzr-linux-amd64" --file bin/linux/pnzr 101 | fi 102 | workflows: 103 | version: 2 104 | test_and_build: 105 | jobs: 106 | - check_fmt 107 | - test_1_10 108 | - test_1_9 109 | - test_1_8 110 | - build: 111 | requires: 112 | - check_fmt 113 | - test_1_10 114 | - test_1_9 115 | - test_1_8 -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @ieee0824 @mi-bear -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Why 2 | * 3 | 4 | ## Issue 5 | * 6 | 7 | ## Note 8 | * 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Why 2 | * 3 | 4 | ## Issue 5 | * 6 | 7 | ## Influence 8 | * 9 | 10 | ## Note 11 | * 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/go,vim,macos 2 | 3 | ### Go ### 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 17 | .glide/ 18 | 19 | ### macOS ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | 28 | # Thumbnails 29 | ._* 30 | 31 | # Files that might appear in the root of a volume 32 | .DocumentRevisions-V100 33 | .fseventsd 34 | .Spotlight-V100 35 | .TemporaryItems 36 | .Trashes 37 | .VolumeIcon.icns 38 | .com.apple.timemachine.donotpresent 39 | 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | 47 | ### Vim ### 48 | # swap 49 | [._]*.s[a-v][a-z] 50 | [._]*.sw[a-p] 51 | [._]s[a-v][a-z] 52 | [._]sw[a-p] 53 | # session 54 | Session.vim 55 | # temporary 56 | .netrwhist 57 | *~ 58 | # auto-generated tag files 59 | tags 60 | 61 | # End of https://www.gitignore.io/api/go,vim,macos 62 | vendor/ 63 | dev/ 64 | 65 | .idea/ 66 | .pnzr -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:9850f24c7656d8a11d6634c433fae31f2ffd5e5513daf453f51c7f345bc53a38" 6 | name = "github.com/armon/go-radix" 7 | packages = ["."] 8 | pruneopts = "UT" 9 | revision = "4239b77079c7b5d1243b7b4736304ce8ddb6f0f2" 10 | 11 | [[projects]] 12 | digest = "1:28ea07434d0d4379522f1f487603c1a9144ad572f055bc7dceaa097fba14acd5" 13 | name = "github.com/aws/aws-sdk-go" 14 | packages = [ 15 | "aws", 16 | "aws/awserr", 17 | "aws/awsutil", 18 | "aws/client", 19 | "aws/client/metadata", 20 | "aws/corehandlers", 21 | "aws/credentials", 22 | "aws/credentials/ec2rolecreds", 23 | "aws/credentials/endpointcreds", 24 | "aws/credentials/stscreds", 25 | "aws/csm", 26 | "aws/defaults", 27 | "aws/ec2metadata", 28 | "aws/endpoints", 29 | "aws/request", 30 | "aws/session", 31 | "aws/signer/v4", 32 | "internal/sdkio", 33 | "internal/sdkrand", 34 | "internal/sdkuri", 35 | "internal/shareddefaults", 36 | "private/protocol", 37 | "private/protocol/json/jsonutil", 38 | "private/protocol/jsonrpc", 39 | "private/protocol/query", 40 | "private/protocol/query/queryutil", 41 | "private/protocol/rest", 42 | "private/protocol/xml/xmlutil", 43 | "service/ecs", 44 | "service/ecs/ecsiface", 45 | "service/kms", 46 | "service/kms/kmsiface", 47 | "service/sts", 48 | ] 49 | pruneopts = "UT" 50 | revision = "71a2a92b0063297b055b6f5a014d441c142da2ce" 51 | version = "v1.15.32" 52 | 53 | [[projects]] 54 | digest = "1:c8982287c8e7569c515f405828fa045065fc0e03e25c9432e12ad4d75e6e3c8c" 55 | name = "github.com/bgentry/speakeasy" 56 | packages = ["."] 57 | pruneopts = "UT" 58 | revision = "675b82c74c0ed12283ee81ba8a534c8982c07b85" 59 | 60 | [[projects]] 61 | digest = "1:2cd7915ab26ede7d95b8749e6b1f933f1c6d5398030684e6505940a10f31cfda" 62 | name = "github.com/ghodss/yaml" 63 | packages = ["."] 64 | pruneopts = "UT" 65 | revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7" 66 | version = "v1.0.0" 67 | 68 | [[projects]] 69 | branch = "master" 70 | digest = "1:36fe9527deed01d2a317617e59304eb2c4ce9f8a24115bcc5c2e37b3aee5bae4" 71 | name = "github.com/gin-contrib/sse" 72 | packages = ["."] 73 | pruneopts = "UT" 74 | revision = "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae" 75 | 76 | [[projects]] 77 | digest = "1:489e108f21464371ebf9cb5c30b1eceb07c6dd772dff073919267493dd9d04ea" 78 | name = "github.com/gin-gonic/gin" 79 | packages = [ 80 | ".", 81 | "binding", 82 | "render", 83 | ] 84 | pruneopts = "UT" 85 | revision = "d459835d2b077e44f7c9b453505ee29881d5d12d" 86 | version = "v1.2" 87 | 88 | [[projects]] 89 | digest = "1:f779e6a6b3a143d9fd6cbc8e1c3ddfbe42df2cbab0ecabe66669a609a56460b8" 90 | name = "github.com/go-ini/ini" 91 | packages = ["."] 92 | pruneopts = "UT" 93 | revision = "e7fea39b01aea8d5671f6858f0532f56e8bff3a5" 94 | version = "v1.27.0" 95 | 96 | [[projects]] 97 | digest = "1:9b0e71863f18fc5de645a263184c8a6409ae731e847b35b25da4be818f1975fa" 98 | name = "github.com/golang/protobuf" 99 | packages = ["proto"] 100 | pruneopts = "UT" 101 | revision = "748d386b5c1ea99658fd69fe9f03991ce86a90c1" 102 | 103 | [[projects]] 104 | digest = "1:7ed04e304baf9cd04ba6e1b0726148bbdf7ef581e3f6c2c45a0a475b8715749b" 105 | name = "github.com/ieee0824/cryptex" 106 | packages = [ 107 | ".", 108 | "encryptor", 109 | "kms", 110 | "rsa", 111 | ] 112 | pruneopts = "UT" 113 | revision = "8031f3a97f8dc0ab6c574ec70c928d40521b4685" 114 | version = "v0.2.6" 115 | 116 | [[projects]] 117 | digest = "1:054bce5d5e8f686f1157ab39b5ac993d7dd2797632966ba998a6105ab60829dd" 118 | name = "github.com/ieee0824/getenv" 119 | packages = ["."] 120 | pruneopts = "UT" 121 | revision = "c0967c2a6018ada0a7537084588ee1e5084b09ac" 122 | version = "v0.2" 123 | 124 | [[projects]] 125 | digest = "1:5f70ee8b9de2cfbc00d9466dd44d0a6cc5a97d9cf47d2a0ee9648f07982c0784" 126 | name = "github.com/ieee0824/jec" 127 | packages = ["."] 128 | pruneopts = "UT" 129 | revision = "2c90ef2a8c1fb51436bc11dea21b0e87214244b3" 130 | version = "v1.0.1" 131 | 132 | [[projects]] 133 | digest = "1:e22af8c7518e1eab6f2eab2b7d7558927f816262586cd6ed9f349c97a6c285c4" 134 | name = "github.com/jmespath/go-jmespath" 135 | packages = ["."] 136 | pruneopts = "UT" 137 | revision = "0b12d6b5" 138 | 139 | [[projects]] 140 | digest = "1:70e697d67ccaec45e16bac3a32380ebcd9e7e071079c60d0171d42cf1cf9748a" 141 | name = "github.com/joho/godotenv" 142 | packages = ["."] 143 | pruneopts = "UT" 144 | revision = "a79fa1e548e2c689c241d10173efd51e5d689d5b" 145 | version = "v1.2.0" 146 | 147 | [[projects]] 148 | digest = "1:fa610f9fe6a93f4a75e64c83673dfff9bf1a34bbb21e6102021b6bc7850834a3" 149 | name = "github.com/mattn/go-isatty" 150 | packages = ["."] 151 | pruneopts = "UT" 152 | revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe" 153 | version = "v0.0.2" 154 | 155 | [[projects]] 156 | digest = "1:778be55b0ed9f92c9258628fc8a61eaaff715c18b8bf968acb8658d02ced0935" 157 | name = "github.com/mitchellh/cli" 158 | packages = ["."] 159 | pruneopts = "UT" 160 | revision = "ee8578a9c12a5bb9d55303b9665cc448772c81b8" 161 | 162 | [[projects]] 163 | digest = "1:4692f16a37b264e3b69e741506428c50a00db32f9a2f600c1dd8f1f2936c5347" 164 | name = "github.com/ugorji/go" 165 | packages = ["codec"] 166 | pruneopts = "UT" 167 | revision = "5efa3251c7f7d05e5d9704a69a984ec9f1386a40" 168 | 169 | [[projects]] 170 | digest = "1:bbf324989601c1c64369916ecc2aee2c0e7249f616c1720ebc7c449735692dc2" 171 | name = "golang.org/x/sys" 172 | packages = ["unix"] 173 | pruneopts = "UT" 174 | revision = "f3918c30c5c2cb527c0b071a27c35120a6c0719a" 175 | 176 | [[projects]] 177 | digest = "1:cbc72c4c4886a918d6ab4b95e347ffe259846260f99ebdd8a198c2331cf2b2e9" 178 | name = "gopkg.in/go-playground/validator.v8" 179 | packages = ["."] 180 | pruneopts = "UT" 181 | revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" 182 | version = "v8.18.2" 183 | 184 | [[projects]] 185 | digest = "1:1883ec914995921bfc6c1c39a46d1ed153f37ddc6f5348ace363725917dcf476" 186 | name = "gopkg.in/yaml.v2" 187 | packages = ["."] 188 | pruneopts = "UT" 189 | revision = "25c4ec802a7d637f88d584ab26798e94ad14c13b" 190 | 191 | [solve-meta] 192 | analyzer-name = "dep" 193 | analyzer-version = 1 194 | input-imports = [ 195 | "github.com/aws/aws-sdk-go/aws", 196 | "github.com/aws/aws-sdk-go/aws/credentials", 197 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds", 198 | "github.com/aws/aws-sdk-go/aws/session", 199 | "github.com/aws/aws-sdk-go/service/ecs", 200 | "github.com/aws/aws-sdk-go/service/ecs/ecsiface", 201 | "github.com/aws/aws-sdk-go/service/kms", 202 | "github.com/aws/aws-sdk-go/service/kms/kmsiface", 203 | "github.com/gin-gonic/gin", 204 | "github.com/ieee0824/cryptex", 205 | "github.com/ieee0824/cryptex/kms", 206 | "github.com/ieee0824/cryptex/rsa", 207 | "github.com/ieee0824/getenv", 208 | "github.com/ieee0824/jec", 209 | "github.com/joho/godotenv", 210 | "github.com/mitchellh/cli", 211 | ] 212 | solver-name = "gps-cdcl" 213 | solver-version = 1 214 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/aws/aws-sdk-go" 30 | version = "1.12.3" 31 | 32 | [[constraint]] 33 | name = "github.com/gin-gonic/gin" 34 | version = "1.2.0" 35 | 36 | [[constraint]] 37 | name = "github.com/ieee0824/cryptex" 38 | version = "0.2.0" 39 | 40 | [[constraint]] 41 | name = "github.com/ieee0824/getenv" 42 | version = "0.2.0" 43 | 44 | [[constraint]] 45 | name = "github.com/ieee0824/jec" 46 | version = "1.0.0" 47 | 48 | [[constraint]] 49 | name = "github.com/joho/godotenv" 50 | version = "1.2.0" 51 | 52 | [prune] 53 | go-tests = true 54 | unused-packages = true 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HASH := $(shell git rev-parse --short HEAD) 2 | GOVERSION := $(shell go version) 3 | VERSION ?= $(HASH) 4 | DATE := $(shell LC_ALL=c date) 5 | BUILD_OS := $(shell uname) 6 | 7 | build: 8 | @mkdir -p bin/darwin 9 | @mkdir -p bin/linux 10 | @echo "build linux binary" 11 | @GOOS=linux GOARCH=amd64 go build -ldflags '-X "main.VERSION=$(VERSION)" -X "main.BUILD_DATE=$(DATE)" -X "main.BUILD_OS=$(BUILD_OS)"' -o bin/linux/pnzr 12 | @echo "build darwin binary" 13 | @GOOS=darwin GOARCH=amd64 go build -ldflags '-X "main.VERSION=$(VERSION)" -X "main.BUILD_DATE=$(DATE)" -X "main.BUILD_OS=$(BUILD_OS)"' -o bin/darwin/pnzr 14 | .PHONY: build 15 | 16 | 17 | # Clean build artifacts. 18 | clean: 19 | @git clean -f 20 | @rm -rf bin 21 | .PHONY: clean 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pnzr 2 | ecs deploy docker container 3 | 4 | ``` 5 |                         r/j======、、 6 |                 =========〔' / ,i!uij   i 7! 7 |                   _f/_ __{日}___!__i,j 8 |              -r。' 二〔´/´ ̄ ̄`,___, ̄ ̄`¬f`{!、 9 |            /´ ̄ ̄ ̄ ̄ ̄f´〔ロ〕 j! ̄`'''¬─--'' 、j、 10 |            ,r‐j====!------、 'ー---‐'r============ヽj 11 |           (´tj〕ソv'v'、ftj!ソv'!ソv'v'v'v'、ftj!ソv'v'v'(◎) 12 |           '、ヾ'´、_,fjj、___,fjj、__,,fjj、___ソ´7 13 |            \  ,(◎X◎)   ,(◎X◎)  ,(◎X◎)  '/ 14 |              `゙"""""""""""""""""""""""""""""゙´ 15 | ``` 16 | 17 | [![CircleCI](https://circleci.com/gh/jobtalk/pnzr.svg?style=shield)](https://circleci.com/gh/jobtalk/pnzr) 18 | [![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0) 19 | 20 | The pnzr package works on Go versions: 21 | * 1.8.x and greater 22 | * 1.9.x and greater 23 | * 1.10.x and greater 24 | 25 | ## Installation 26 | Can be installed in either way. 27 | 28 | ### Use install script 29 | On macOS, or Linux run the following: 30 | ``` 31 | $ curl https://raw.githubusercontent.com/jobtalk/pnzr/master/install.sh | sh 32 | ``` 33 | 34 | Note that you may need to run the sudo version below, or alternatively chown /usr/local: 35 | ``` 36 | $ curl https://raw.githubusercontent.com/jobtalk/pnzr/master/install.sh | sudo sh 37 | ``` 38 | 39 | ### Use Go get 40 | ``` 41 | $ go get -u github.com/jobtalk/pnzr 42 | ``` 43 | 44 | ## Detailed instructions 45 | Please read the [wiki](https://github.com/jobtalk/pnzr/wiki). 46 | 47 | ## Update latest version 48 | 49 | ``` 50 | $ pnzr update 51 | ``` 52 | 53 | ## Examples 54 | 55 | ### Deploy ecs 56 | 57 | ``` 58 | $ pnzr deploy -f setting.json 59 | $ pnzr deploy -profile aws/profile -f setting.json 60 | ``` 61 | 62 | ### Encrypt setting 63 | 64 | ``` 65 | $ pnzr vault encrypt -f target.json 66 | $ pnzr vault encrypt -key_id ${KMS_KEY_ID} -f target.json 67 | ``` 68 | 69 | ### Decrypt setting 70 | 71 | ``` 72 | $ pnzr vault decrypt -f target.json 73 | ``` 74 | 75 | ### Viewer mode of encrypted setting file 76 | 77 | #### latest config version 78 | ``` 79 | $ pnzr vault view -f target.json 80 | ``` 81 | 82 | #### choose config version 83 | ``` 84 | $ pnzr vault view -v prototype -а target.json 85 | ``` 86 | 87 | #### check default config version 88 | ``` 89 | $pnzr vault view -h 90 | -v string 91 | config version (default "1.0") 92 | ``` 93 | 94 | ### Edit mode of encrypted file 95 | 96 | ``` 97 | $ pnzr vault edit -f target.json 98 | ``` 99 | 100 | ### Support Multi-Factor Authentication(MFA) 101 | 102 | ``` 103 | $ pnzr vault view -profile use-mfa-user -f deploy-config.json 104 | Assume Role MFA token code: ****** 105 | ``` 106 | 107 | # use any editor in edit mode 108 | It run when assigning editor name to EDITOR. 109 | 110 | ## vim 111 | ``` 112 | $ EDITOR=vim pnzr vault edit -f /path/to/target 113 | ``` 114 | 115 | ## vscode 116 | ``` 117 | $ EDITOR="code --wait" pnzr vault edit -f /path/to/target 118 | ``` 119 | 120 | ## atom 121 | ``` 122 | $ EDITOR="atom --wait" pnzr vault edit -f /path/to/target 123 | ``` 124 | -------------------------------------------------------------------------------- /api/deploy.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | "github.com/jobtalk/pnzr/lib" 6 | "github.com/jobtalk/pnzr/lib/iface" 7 | "github.com/jobtalk/pnzr/lib/setting" 8 | ) 9 | 10 | type DeployDeps struct { 11 | ecs iface.ECSAPI 12 | } 13 | 14 | // serviceが存在しない時はサービスを作る 15 | // 存在するときはアップデートする 16 | func (d *DeployDeps) Deploy(s *setting.Setting) (interface{}, error) { 17 | var result = []interface{}{} 18 | if s != nil && s.TaskDefinition != nil { 19 | resultTaskDefinition, err := d.ecs.RegisterTaskDefinition(s.TaskDefinition) 20 | if err != nil { 21 | return nil, err 22 | } 23 | result = append(result, resultTaskDefinition) 24 | } 25 | if s != nil && s.Service != nil { 26 | resultUpsert, err := d.ecs.UpsertService(s.Service) 27 | if err != nil { 28 | return nil, err 29 | } 30 | result = append(result, resultUpsert) 31 | } 32 | 33 | return result, nil 34 | } 35 | 36 | func Deploy(sess *session.Session, s *setting.Setting) (interface{}, error) { 37 | return (&DeployDeps{ecs: lib.NewECS(sess)}).Deploy(s) 38 | } 39 | -------------------------------------------------------------------------------- /api/deploy_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | _ "fmt" 5 | "testing" 6 | 7 | "github.com/aws/aws-sdk-go/service/ecs" 8 | "github.com/jobtalk/pnzr/lib/iface" 9 | "github.com/jobtalk/pnzr/lib/setting" 10 | ) 11 | 12 | func TestDeploy(t *testing.T) { 13 | family := "taskdef-a" 14 | taskDefinition := &ecs.RegisterTaskDefinitionInput{Family: &family} 15 | serviceName := "service-a" 16 | service := &ecs.CreateServiceInput{ServiceName: &serviceName} 17 | 18 | // Settings 19 | onlyService := setting.Setting{ 20 | Service: service, 21 | } 22 | onlyTaskDefinition := setting.Setting{ 23 | TaskDefinition: taskDefinition, 24 | } 25 | both := setting.Setting{ 26 | Service: service, 27 | TaskDefinition: taskDefinition, 28 | } 29 | 30 | // UpsertService のみが呼ばれる 31 | { 32 | deploy, fnArgs := mockDeploy() 33 | deploy.Deploy(&onlyService) 34 | if fnArgs.RegisterTaskDefinitionInput != nil { 35 | t.Fatalf("RegisterTaskDefinition should not be called") 36 | } 37 | if *fnArgs.UpsertServiceInput.ServiceName != serviceName { 38 | t.Log(*fnArgs.UpsertServiceInput.ServiceName) 39 | t.Fatalf("UpsertService should be called with %s", serviceName) 40 | } 41 | } 42 | 43 | // RegisterTaskDefinition のみが呼ばれる 44 | { 45 | deploy, fnArgs := mockDeploy() 46 | deploy.Deploy(&onlyTaskDefinition) 47 | if *fnArgs.RegisterTaskDefinitionInput.Family != family { 48 | t.Log(*fnArgs.RegisterTaskDefinitionInput.Family) 49 | t.Fatalf("RegisterTaskDefinition should be called with %s", family) 50 | } 51 | if fnArgs.UpsertServiceInput != nil { 52 | t.Fatalf("UpsertService should not be called") 53 | } 54 | } 55 | 56 | // UpsertService, RegisterTaskDefinition 両方が呼ばれる 57 | { 58 | deploy, fnArgs := mockDeploy() 59 | deploy.Deploy(&both) 60 | if *fnArgs.RegisterTaskDefinitionInput.Family != family { 61 | t.Log(*fnArgs.RegisterTaskDefinitionInput.Family) 62 | t.Fatalf("RegisterTaskDefinition should be called with %s", family) 63 | } 64 | if *fnArgs.UpsertServiceInput.ServiceName != serviceName { 65 | t.Log(*fnArgs.UpsertServiceInput.ServiceName) 66 | t.Fatalf("UpsertService should be called with %s", serviceName) 67 | } 68 | } 69 | } 70 | 71 | type mockedFnArgs struct { 72 | RegisterTaskDefinitionInput *ecs.RegisterTaskDefinitionInput 73 | UpsertServiceInput *ecs.CreateServiceInput 74 | } 75 | 76 | func mockDeploy() (*DeployDeps, *mockedFnArgs) { 77 | fnArgs := mockedFnArgs{RegisterTaskDefinitionInput: nil, UpsertServiceInput: nil} 78 | ecs := mockedECS{ 79 | registerTaskDefinition: func(in *ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) { 80 | fnArgs = mockedFnArgs{RegisterTaskDefinitionInput: in, UpsertServiceInput: fnArgs.UpsertServiceInput} 81 | return nil, nil 82 | }, 83 | upsertService: func(in *ecs.CreateServiceInput) (interface{}, error) { 84 | fnArgs = mockedFnArgs{RegisterTaskDefinitionInput: fnArgs.RegisterTaskDefinitionInput, UpsertServiceInput: in} 85 | return nil, nil 86 | }, 87 | } 88 | return &DeployDeps{ecs: ecs}, &fnArgs 89 | } 90 | 91 | type mockedECS struct { 92 | iface.ECSAPI 93 | registerTaskDefinition func(*ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) 94 | upsertService func(*ecs.CreateServiceInput) (interface{}, error) 95 | } 96 | 97 | func (m mockedECS) RegisterTaskDefinition(in *ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) { 98 | m.registerTaskDefinition(in) 99 | return nil, nil 100 | } 101 | 102 | func (m mockedECS) UpsertService(in *ecs.CreateServiceInput) (interface{}, error) { 103 | m.upsertService(in) 104 | return nil, nil 105 | } 106 | -------------------------------------------------------------------------------- /example/v1/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "Service": { 3 | "Cluster": "test-cluster", 4 | "DeploymentConfiguration": { 5 | "MaximumPercent": 200, 6 | "MinimumHealthyPercent": 100 7 | }, 8 | "DesiredCount": "$count", 9 | "LoadBalancers": [ 10 | { 11 | "ContainerName": "app", 12 | "ContainerPort": 8080, 13 | "TargetGroupArn": "targetgroup arn" 14 | } 15 | ], 16 | "Role": "ecsServiceRole", 17 | "ServiceName": "dummy-app", 18 | "TaskDefinition": "dummy-app" 19 | }, 20 | "TaskDefinition": { 21 | "ContainerDefinitions": [ 22 | { 23 | "Cpu": 0, 24 | "Essential": true, 25 | "Image": "ieee0824/dummy-app:$tag", 26 | "Memory": 128, 27 | "Name": "app", 28 | "PortMappings": [ 29 | { 30 | "ContainerPort": 8080, 31 | "HostPort": 0, 32 | "Protocol": "tcp" 33 | } 34 | ] 35 | } 36 | ], 37 | "Family": "dummy-app", 38 | "NetworkMode": "bridge", 39 | "TaskRoleArn": "task role arn" 40 | }, 41 | "Version": 1 42 | } -------------------------------------------------------------------------------- /example/v1/vars/count.json: -------------------------------------------------------------------------------- 1 | { 2 | "encryption_type": "kms", 3 | "values": { 4 | "array": "AQECAHh5q0tFgkoZemC6czjL/QJ6+DlDwjLL6N3YmGIcYUKyuwAAAHcwdQYJKoZIhvcNAQcGoGgwZgIBADBhBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDIfJ4JuWea2MQ+TfjQIBEIA0qy84d8KDtpLsm3MGxtbFzQYmLN9MaW0KUuN9MXbFqwEMSvr0ZpZp0kIIhQdgSvv+tj0SKg==", 5 | "count": "AQECAHh5q0tFgkoZemC6czjL/QJ6+DlDwjLL6N3YmGIcYUKyuwAAAGcwZQYJKoZIhvcNAQcGoFgwVgIBADBRBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDEZAFJxwNJtHGcXTxAIBEIAk7FwlkxxQFnO/2IdSsdf8pUAYcevOylxNHXk2RcYSQhW8794P" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | install() { 5 | set -eu 6 | UNAME=$(uname) 7 | 8 | if [ "$UNAME" != "Linux" -a "$UNAME" != "Darwin" ] ; then 9 | echo "Sorry, OS not supported: ${UNAME}." 10 | exit 1 11 | fi 12 | 13 | if [ "$UNAME" = "Darwin" ] ; then 14 | OSX_ARCH=$(uname -m) 15 | if [ "${OSX_ARCH}" = "x86_64" ] ; then 16 | PLATFORM="darwin-amd64" 17 | else 18 | echo "Sorry, architecture not supported: ${OSX_ARCH}." 19 | exit 1 20 | fi 21 | elif [ "$UNAME" = "Linux" ] ; then 22 | LINUX_ARCH=$(uname -m) 23 | if [ "${LINUX_ARCH}" = "x86_64" ] ; then 24 | PLATFORM="linux-amd64" 25 | else 26 | echo "Sorry, architecture not supported: ${LINUX_ARCH}." 27 | exit 1 28 | fi 29 | fi 30 | TAGS=$(curl -s https://api.github.com/repos/jobtalk/pnzr/tags) 31 | LATEST=$(echo "${TAGS}" | grep -Eo '"name":.*[^\\]",' | head -n 1 | sed 's/[," ]//g' | cut -d ':' -f 2) 32 | VERSION=${VERSION:-$LATEST} 33 | 34 | if [ ! -z $(which pnzr) ]; then 35 | NOW_VERSION=$(pnzr -v 2>&1 >/dev/null | grep 'Build version' | cut -d " " -f 3) 36 | 37 | if [ ${VERSION} = ${NOW_VERSION} ]; then 38 | echo "${VERSION} is already installed." 39 | 40 | exit 0 41 | fi 42 | fi 43 | 44 | 45 | 46 | URL="https://github.com/jobtalk/pnzr/releases/download/$VERSION/pnzr-$PLATFORM" 47 | DEST=${DEST:-/usr/local/bin/pnzr} 48 | 49 | if [ -z $VERSION ] ; then 50 | echo "Error requesting. Download binary from https://github.com/jobtalk/pnzr/releases" 51 | exit 1 52 | else 53 | echo "Downloading pnzr binary from https://github.com/jobtalk/pnzr/releases/download/$VERSION/pnzr-$PLATFORM to $DEST" 54 | if curl -sL https://github.com/jobtalk/pnzr/releases/download/$VERSION/pnzr-$PLATFORM -o $DEST; then 55 | chmod +x $DEST 56 | echo "pnzr installation was successful" 57 | else 58 | echo "Installation failed. You may need elevated permissions." 59 | exit 1 60 | fi 61 | fi 62 | } 63 | 64 | install 65 | -------------------------------------------------------------------------------- /lib/ecs.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/ecs" 10 | "github.com/aws/aws-sdk-go/service/ecs/ecsiface" 11 | ) 12 | 13 | type Service struct { 14 | Name *string 15 | } 16 | 17 | type ECS struct { 18 | svc ecsiface.ECSAPI 19 | } 20 | 21 | func NewECS(sess *session.Session) *ECS { 22 | return &ECS{ 23 | svc: ecs.New(sess), 24 | } 25 | } 26 | 27 | func (e *ECS) RegisterTaskDefinition(registerTaskDefinitionInput *ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) { 28 | return e.svc.RegisterTaskDefinition(registerTaskDefinitionInput) 29 | } 30 | 31 | func (e *ECS) UpsertService(createServiceInput *ecs.CreateServiceInput) (interface{}, error) { 32 | if createServiceInput.Cluster == nil { 33 | createServiceInput.Cluster = aws.String("default") 34 | } 35 | ok, err := e.serviceExists(*createServiceInput.Cluster, *createServiceInput.ServiceName) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if !ok { 40 | return e.svc.CreateService(createServiceInput) 41 | } 42 | 43 | updateServiceInput := &ecs.UpdateServiceInput{} 44 | updateServiceInput.Cluster = createServiceInput.Cluster 45 | updateServiceInput.DeploymentConfiguration = createServiceInput.DeploymentConfiguration 46 | updateServiceInput.DesiredCount = createServiceInput.DesiredCount 47 | updateServiceInput.Service = createServiceInput.ServiceName 48 | updateServiceInput.TaskDefinition = createServiceInput.TaskDefinition 49 | return e.svc.UpdateService(updateServiceInput) 50 | } 51 | 52 | func (e *ECS) ListServices(params *ecs.ListServicesInput) (*ecs.ListServicesOutput, error) { 53 | var ( 54 | ret = &ecs.ListServicesOutput{} 55 | pageNum int 56 | ) 57 | 58 | err := e.svc.ListServicesPages(params, func(page *ecs.ListServicesOutput, lastPage bool) bool { 59 | pageNum++ 60 | ret.ServiceArns = append(ret.ServiceArns, page.ServiceArns...) 61 | return pageNum <= 1000 62 | }) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return ret, nil 67 | } 68 | 69 | func (e *ECS) serviceExists(clusetrName string, serviceName string) (bool, error) { 70 | listInput := &ecs.ListServicesInput{ 71 | Cluster: aws.String(clusetrName), 72 | } 73 | result, err := e.ListServices(listInput) 74 | if err != nil { 75 | return false, err 76 | } 77 | for _, v := range result.ServiceArns { 78 | s, err := parseArn(v) 79 | if err != nil { 80 | return false, err 81 | } 82 | if *s.Name == serviceName { 83 | return true, nil 84 | } 85 | } 86 | return false, nil 87 | } 88 | 89 | func parseArn(arn *string) (*Service, error) { 90 | splitStr := strings.Split(*arn, "service/") 91 | if len(splitStr) != 2 { 92 | return nil, errors.New("illegal arn string") 93 | } 94 | return &Service{&splitStr[1]}, nil 95 | } 96 | -------------------------------------------------------------------------------- /lib/ecs_test.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | _ "fmt" 5 | "testing" 6 | 7 | ecssdk "github.com/aws/aws-sdk-go/service/ecs" 8 | "github.com/aws/aws-sdk-go/service/ecs/ecsiface" 9 | ) 10 | 11 | func TestListServices(t *testing.T) { 12 | ecs, _ := mockECS() 13 | res, _ := ecs.ListServices(&ecssdk.ListServicesInput{}) 14 | t.Log(res) 15 | for i, arn := range res.ServiceArns { 16 | if *testServiceArns[i] != *arn { 17 | t.Fatalf("Expected: %s, Got: %s", *testServiceArns[i], *arn) 18 | } 19 | } 20 | } 21 | 22 | func TestUpsertService(t *testing.T) { 23 | // 存在するサービスは Update が呼ばれる 24 | { 25 | svcName := "some-service-a" 26 | svc := &ecssdk.CreateServiceInput{ServiceName: &svcName} 27 | ecs, fnArgs := mockECS() 28 | ecs.UpsertService(svc) 29 | if *fnArgs.UpdateServiceInput.Service != svcName { 30 | t.Log(*fnArgs.UpdateServiceInput.Service) 31 | t.Fatalf("UpdateService should be called with %s", svcName) 32 | } 33 | if fnArgs.CreateServiceInput != nil { 34 | t.Fatalf("CreateService should not be called") 35 | } 36 | } 37 | 38 | // 存在しないサービスは Create が呼ばれる 39 | { 40 | svcName := "some-service-d" 41 | svc := &ecssdk.CreateServiceInput{ServiceName: &svcName} 42 | ecs, fnArgs := mockECS() 43 | ecs.UpsertService(svc) 44 | if *fnArgs.CreateServiceInput.ServiceName != svcName { 45 | t.Log(*fnArgs.CreateServiceInput.ServiceName) 46 | t.Fatalf("CreateService should be called with %s", svcName) 47 | } 48 | if fnArgs.UpdateServiceInput != nil { 49 | t.Fatalf("UpdateService should not be called") 50 | } 51 | } 52 | } 53 | 54 | type mockedFnArgs struct { 55 | CreateServiceInput *ecssdk.CreateServiceInput 56 | UpdateServiceInput *ecssdk.UpdateServiceInput 57 | } 58 | 59 | func mockECS() (*ECS, *mockedFnArgs) { 60 | fnArgs := mockedFnArgs{CreateServiceInput: nil, UpdateServiceInput: nil} 61 | svc := &mockedECS{ 62 | listServicesOutput: &ecssdk.ListServicesOutput{ServiceArns: testServiceArns}, 63 | createService: func(in *ecssdk.CreateServiceInput) (*ecssdk.CreateServiceOutput, error) { 64 | fnArgs = mockedFnArgs{CreateServiceInput: in, UpdateServiceInput: fnArgs.UpdateServiceInput} 65 | return nil, nil 66 | }, 67 | updateService: func(in *ecssdk.UpdateServiceInput) (*ecssdk.UpdateServiceOutput, error) { 68 | fnArgs = mockedFnArgs{CreateServiceInput: fnArgs.CreateServiceInput, UpdateServiceInput: in} 69 | return nil, nil 70 | }, 71 | } 72 | return &ECS{svc: svc}, &fnArgs 73 | } 74 | 75 | type mockedECS struct { 76 | ecsiface.ECSAPI 77 | listServicesOutput *ecssdk.ListServicesOutput 78 | createService func(*ecssdk.CreateServiceInput) (*ecssdk.CreateServiceOutput, error) 79 | updateService func(*ecssdk.UpdateServiceInput) (*ecssdk.UpdateServiceOutput, error) 80 | } 81 | 82 | func (m mockedECS) CreateService(in *ecssdk.CreateServiceInput) (*ecssdk.CreateServiceOutput, error) { 83 | m.createService(in) 84 | return nil, nil 85 | } 86 | 87 | func (m mockedECS) UpdateService(in *ecssdk.UpdateServiceInput) (*ecssdk.UpdateServiceOutput, error) { 88 | m.updateService(in) 89 | return nil, nil 90 | } 91 | 92 | func (m mockedECS) ListServicesPages(in *ecssdk.ListServicesInput, f func(*ecssdk.ListServicesOutput, bool) bool) error { 93 | f(m.listServicesOutput, true) 94 | return nil 95 | } 96 | 97 | // 既存サービスのモック 98 | var testServiceArns = make([]*string, 3) 99 | 100 | func init() { 101 | s0 := "arn:aws:ecs:xx-someregion-1:01234567890123:service/some-service-a" 102 | s1 := "arn:aws:ecs:xx-someregion-1:01234567890123:service/some-service-b" 103 | s2 := "arn:aws:ecs:xx-someregion-1:01234567890123:service/some-service-c" 104 | testServiceArns[0] = &s0 105 | testServiceArns[1] = &s1 106 | testServiceArns[2] = &s2 107 | } 108 | -------------------------------------------------------------------------------- /lib/iface/ecs.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/service/ecs" 5 | ) 6 | 7 | type ECSAPI interface { 8 | RegisterTaskDefinition(*ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) 9 | UpsertService(*ecs.CreateServiceInput) (interface{}, error) 10 | } 11 | -------------------------------------------------------------------------------- /lib/kms_vault.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/aws/aws-sdk-go/service/kms" 9 | "github.com/aws/aws-sdk-go/service/kms/kmsiface" 10 | ) 11 | 12 | type KMS struct { 13 | keyID *string 14 | svc kmsiface.KMSAPI 15 | Type *string `json:"type"` 16 | Cipher []byte `json:"cipher"` 17 | } 18 | 19 | func NewKMS(sess *session.Session) *KMS { 20 | return &KMS{ 21 | svc: kms.New(sess), 22 | Type: aws.String("kms"), 23 | } 24 | } 25 | 26 | func NewKMSFromBinary(bin []byte, sess *session.Session) *KMS { 27 | var ret = KMS{} 28 | err := json.Unmarshal(bin, &ret) 29 | if err != nil { 30 | return nil 31 | } 32 | ret.svc = kms.New(sess) 33 | return &ret 34 | } 35 | 36 | func (k *KMS) Encrypt(plainText []byte) ([]byte, error) { 37 | params := &kms.EncryptInput{ 38 | KeyId: k.keyID, 39 | Plaintext: plainText, 40 | } 41 | resp, err := k.svc.Encrypt(params) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | k.Cipher = resp.CiphertextBlob 47 | return resp.CiphertextBlob, nil 48 | } 49 | 50 | func (k *KMS) Decrypt() ([]byte, error) { 51 | params := &kms.DecryptInput{ 52 | CiphertextBlob: k.Cipher, 53 | } 54 | resp, err := k.svc.Decrypt(params) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return resp.Plaintext, nil 59 | } 60 | 61 | func (k *KMS) SetKeyID(keyID string) *KMS { 62 | k.keyID = &keyID 63 | return k 64 | } 65 | 66 | func (k *KMS) String() string { 67 | bin, err := json.Marshal(k) 68 | if err != nil { 69 | return "" 70 | } 71 | return string(bin) 72 | } 73 | -------------------------------------------------------------------------------- /lib/setting/loader.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | type Loader interface { 4 | Load(basePath, varsPath, outerVals string) (*Setting, error) 5 | } 6 | -------------------------------------------------------------------------------- /lib/setting/prototype/embedde/embedde.go: -------------------------------------------------------------------------------- 1 | package embedde 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func Embedde(base, val string) (string, error) { 10 | // 埋め込み用の値をjsonからデコードする 11 | var v = map[string]interface{}{} 12 | if err := json.Unmarshal([]byte(val), &v); err != nil { 13 | return "", err 14 | } 15 | 16 | // 埋め込み用の値をkeyとvalに分ける 17 | for k, v := range v { 18 | // 値を再びjsonに戻す 19 | valJSON, err := json.Marshal(v) 20 | if err != nil { 21 | return "", err 22 | } 23 | 24 | base = strings.Replace(base, fmt.Sprintf("$%s", k), string(valJSON), -1) 25 | } 26 | 27 | return base, nil 28 | } 29 | -------------------------------------------------------------------------------- /lib/setting/prototype/fileList_test.go: -------------------------------------------------------------------------------- 1 | package prototype 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jobtalk/pnzr/vars" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestFileList(t *testing.T) { 11 | testDataDir := vars.TEST_DATA_DIR_ROOT + "/subcmd/deploy/testFileList" 12 | 13 | tests := []struct { 14 | want []string 15 | err bool 16 | }{ 17 | { 18 | want: func() []string { 19 | ret := []string{} 20 | for i := 0; i < 10; i++ { 21 | ret = append(ret, fmt.Sprintf("%d.json", i)) 22 | } 23 | return ret 24 | }(), 25 | err: false, 26 | }, 27 | { 28 | want: []string{}, 29 | err: false, 30 | }, 31 | { 32 | want: func() []string { 33 | ret := []string{} 34 | for i := 0; i < 10; i++ { 35 | ret = append(ret, fmt.Sprintf("%d.json", i)) 36 | } 37 | for i := 0; i <= int(byte('z')-byte('a')); i++ { 38 | ret = append(ret, fmt.Sprintf("%s.json", string([]rune{rune(byte('a') + byte(i))}))) 39 | } 40 | return ret 41 | }(), 42 | err: false, 43 | }, 44 | { 45 | want: func() []string { 46 | ret := []string{} 47 | for i := 0; i < 10; i++ { 48 | ret = append(ret, fmt.Sprintf("%d.json", i)) 49 | } 50 | return ret 51 | }(), 52 | err: false, 53 | }, 54 | } 55 | 56 | for i, test := range tests { 57 | got, err := fileList(fmt.Sprintf("%s/%d", testDataDir, i)) 58 | if !test.err && err != nil { 59 | t.Fatalf("should not be error for %v but: %v, dir: %v", i, err, fmt.Sprintf("%s/%d", testDataDir, i)) 60 | } 61 | if test.err && err == nil { 62 | t.Fatalf("should be error for %v but not:", i) 63 | } 64 | if !reflect.DeepEqual(got, test.want) { 65 | t.Fatalf("want: %q, but: %q", test.want, got) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/setting/prototype/isEncrypt_test.go: -------------------------------------------------------------------------------- 1 | package prototype 2 | 3 | import "testing" 4 | 5 | func TestIsEncrypted(t *testing.T) { 6 | tests := []struct { 7 | input string 8 | want bool 9 | }{ 10 | { 11 | ``, 12 | false, 13 | }, 14 | { 15 | `{}`, 16 | false, 17 | }, 18 | { 19 | `{"cipher"}`, 20 | false, 21 | }, 22 | { 23 | `{"cipher": "hoge"}`, 24 | true, 25 | }, 26 | { 27 | `{"cipher":{}}`, 28 | false, 29 | }, 30 | { 31 | `{"hoge":"huga"}`, 32 | false, 33 | }, 34 | { 35 | `{"hoge":"huga", "cipher": "hoge"}`, 36 | true, 37 | }, 38 | } 39 | 40 | for _, test := range tests { 41 | got := NewLoader(nil, nil).isEncrypted([]byte(test.input)) 42 | if got != test.want { 43 | t.Fatalf("want %v, but %v:", test.want, got) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/setting/prototype/kms/kms.go: -------------------------------------------------------------------------------- 1 | package kms 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/aws/aws-sdk-go/service/kms" 9 | "github.com/aws/aws-sdk-go/service/kms/kmsiface" 10 | ) 11 | 12 | type KMS struct { 13 | keyID *string 14 | svc kmsiface.KMSAPI 15 | Type *string `json:"type"` 16 | Cipher []byte `json:"cipher"` 17 | } 18 | 19 | func NewKMS(sess *session.Session) *KMS { 20 | return &KMS{ 21 | svc: kms.New(sess), 22 | Type: aws.String("kms"), 23 | } 24 | } 25 | 26 | func NewKMSFromBinary(bin []byte, sess *session.Session) *KMS { 27 | var ret = KMS{} 28 | err := json.Unmarshal(bin, &ret) 29 | if err != nil { 30 | return nil 31 | } 32 | ret.svc = kms.New(sess) 33 | return &ret 34 | } 35 | 36 | func (k *KMS) Encrypt(plainText []byte) ([]byte, error) { 37 | params := &kms.EncryptInput{ 38 | KeyId: k.keyID, 39 | Plaintext: plainText, 40 | } 41 | resp, err := k.svc.Encrypt(params) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | k.Cipher = resp.CiphertextBlob 47 | return resp.CiphertextBlob, nil 48 | } 49 | 50 | func (k *KMS) Decrypt() ([]byte, error) { 51 | params := &kms.DecryptInput{ 52 | CiphertextBlob: k.Cipher, 53 | } 54 | resp, err := k.svc.Decrypt(params) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return resp.Plaintext, nil 59 | } 60 | 61 | func (k *KMS) SetKeyID(keyID string) *KMS { 62 | k.keyID = &keyID 63 | return k 64 | } 65 | 66 | func (k *KMS) String() string { 67 | bin, err := json.Marshal(k) 68 | if err != nil { 69 | return "" 70 | } 71 | return string(bin) 72 | } 73 | -------------------------------------------------------------------------------- /lib/setting/prototype/setting.go: -------------------------------------------------------------------------------- 1 | package prototype 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/aws/aws-sdk-go/service/ecs" 9 | intermediate "github.com/jobtalk/pnzr/lib/setting" 10 | "github.com/jobtalk/pnzr/lib/setting/prototype/embedde" 11 | "github.com/jobtalk/pnzr/lib/setting/prototype/kms" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "regexp" 16 | "strings" 17 | ) 18 | 19 | var re = regexp.MustCompile(`.*\.json$`) 20 | 21 | var ( 22 | BadReqKMS = errors.New("bad request: kms error") 23 | ) 24 | 25 | type ECS struct { 26 | Service *ecs.CreateServiceInput 27 | TaskDefinition *ecs.RegisterTaskDefinitionInput 28 | } 29 | 30 | func fileList(root string) ([]string, error) { 31 | if root == "" { 32 | return nil, nil 33 | } 34 | ret := []string{} 35 | err := filepath.Walk(root, 36 | func(path string, info os.FileInfo, err error) error { 37 | if info == nil { 38 | return errors.New("file info is nil") 39 | } 40 | if info.IsDir() { 41 | return nil 42 | } 43 | 44 | rel, err := filepath.Rel(root, path) 45 | if re.MatchString(rel) { 46 | ret = append(ret, rel) 47 | } 48 | 49 | return nil 50 | }) 51 | 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return ret, nil 57 | } 58 | 59 | type setting struct { 60 | ECS *ECS 61 | } 62 | 63 | func (s *setting) version() float64 { 64 | return 0.0 65 | } 66 | 67 | type SettingLoader struct { 68 | sess *session.Session 69 | kmsKeyID *string 70 | } 71 | 72 | func NewLoader(sess *session.Session, kmsKeyID *string) *SettingLoader { 73 | return &SettingLoader{ 74 | sess: sess, 75 | kmsKeyID: kmsKeyID, 76 | } 77 | } 78 | 79 | func (s *SettingLoader) Load(basePath, varsPath, outerVals string) (*intermediate.Setting, error) { 80 | varsFileList, err := fileList(varsPath) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | baseConfBinary, err := ioutil.ReadFile(basePath) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | if outerVals != "" { 91 | baseStr, err := embedde.Embedde(string(baseConfBinary), outerVals) 92 | if err == nil { 93 | baseConfBinary = []byte(baseStr) 94 | } 95 | } 96 | 97 | if len(varsFileList) != 0 { 98 | result, err := s.loadConf(baseConfBinary, varsPath, varsFileList) 99 | if err != nil { 100 | return nil, err 101 | } 102 | var ret = intermediate.Setting{} 103 | ret.Version = result.version() 104 | ret.Service = result.ECS.Service 105 | ret.TaskDefinition = result.ECS.TaskDefinition 106 | 107 | return &ret, nil 108 | } 109 | 110 | var result = &setting{} 111 | if err := json.Unmarshal(baseConfBinary, result); err != nil { 112 | return nil, err 113 | } 114 | 115 | var ret = intermediate.Setting{} 116 | ret.Version = result.version() 117 | ret.Service = result.ECS.Service 118 | ret.TaskDefinition = result.ECS.TaskDefinition 119 | return &ret, nil 120 | } 121 | 122 | func (s *SettingLoader) loadConf(base []byte, varsRoot string, varsFileNameList []string) (*setting, error) { 123 | var ( 124 | ret = &setting{} 125 | baseStr = string(base) 126 | ) 127 | varsRoot = strings.TrimSuffix(varsRoot, "/") 128 | 129 | for _, varsFileName := range varsFileNameList { 130 | varsBinary, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", varsRoot, varsFileName)) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | if s.isEncrypted(varsBinary) { 136 | plain, err := s.decrypt(varsBinary) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | varsBinary = plain 142 | } 143 | baseStr, err = embedde.Embedde(baseStr, string(varsBinary)) 144 | if err != nil { 145 | return nil, err 146 | } 147 | } 148 | 149 | if err := json.Unmarshal([]byte(baseStr), ret); err != nil { 150 | return nil, err 151 | } 152 | 153 | return ret, nil 154 | } 155 | 156 | func (*SettingLoader) isEncrypted(data []byte) bool { 157 | var buffer = map[string]interface{}{} 158 | if err := json.Unmarshal(data, &buffer); err != nil { 159 | return false 160 | } 161 | elem, ok := buffer["cipher"] 162 | if !ok { 163 | return false 164 | } 165 | str, ok := elem.(string) 166 | if !ok { 167 | return false 168 | } 169 | 170 | return len(str) != 0 171 | } 172 | 173 | func (s *SettingLoader) decrypt(bin []byte) ([]byte, error) { 174 | k := kms.NewKMSFromBinary(bin, s.sess) 175 | if k == nil { 176 | return nil, BadReqKMS 177 | } 178 | 179 | plainText, err := k.SetKeyID(*s.kmsKeyID).Decrypt() 180 | if err != nil { 181 | return nil, err 182 | } 183 | return plainText, nil 184 | } 185 | -------------------------------------------------------------------------------- /lib/setting/setting.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | 7 | "github.com/aws/aws-sdk-go/service/ecs" 8 | ) 9 | 10 | type Setting struct { 11 | Version float64 12 | Service *ecs.CreateServiceInput 13 | TaskDefinition *ecs.RegisterTaskDefinitionInput 14 | } 15 | 16 | func IsV1Setting(path string) bool { 17 | bin, err := ioutil.ReadFile(path) 18 | if err != nil { 19 | return false 20 | } 21 | m := map[string]interface{}{} 22 | if err := json.Unmarshal(bin, &m); err != nil { 23 | return false 24 | } 25 | 26 | v, ok := m["Version"].(float64) 27 | if !ok { 28 | return false 29 | } 30 | 31 | return v == 1.0 32 | } 33 | -------------------------------------------------------------------------------- /lib/setting/setting_test.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/jobtalk/pnzr/vars" 8 | ) 9 | 10 | var ( 11 | TEST_DATA_DIR = vars.TEST_DATA_DIR_ROOT + "/lib/setting" 12 | ) 13 | 14 | func TestIsV1Setting(t *testing.T) { 15 | tests := []struct { 16 | want bool 17 | }{ 18 | {false}, 19 | {false}, 20 | {true}, 21 | {true}, 22 | {false}, 23 | {false}, 24 | } 25 | 26 | for i, test := range tests { 27 | got := IsV1Setting(fmt.Sprintf("%s/%d.json", TEST_DATA_DIR, i)) 28 | 29 | if got != test.want { 30 | t.Fatalf("want %v, but %v:", test.want, got) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/setting/v1/setting.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | "github.com/aws/aws-sdk-go/service/ecs" 8 | "github.com/ieee0824/cryptex" 9 | "github.com/ieee0824/cryptex/kms" 10 | "github.com/ieee0824/jec" 11 | "github.com/jobtalk/pnzr/lib/setting" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "regexp" 16 | "strings" 17 | ) 18 | 19 | var ( 20 | FileNotFoundError = errors.New("file info is nil") 21 | ) 22 | 23 | var re = regexp.MustCompile(`.*\.json$`) 24 | 25 | type v1Setting struct { 26 | Version float64 27 | Service *ecs.CreateServiceInput 28 | TaskDefinition *ecs.RegisterTaskDefinitionInput 29 | } 30 | 31 | func (s *v1Setting) Convert() *setting.Setting { 32 | return &setting.Setting{ 33 | s.Version, 34 | s.Service, 35 | s.TaskDefinition, 36 | } 37 | } 38 | 39 | func fileList(root string) ([]string, error) { 40 | if root == "" { 41 | return nil, nil 42 | } 43 | ret := []string{} 44 | err := filepath.Walk(root, 45 | func(path string, info os.FileInfo, err error) error { 46 | if info == nil { 47 | return FileNotFoundError 48 | } 49 | if info.IsDir() { 50 | return nil 51 | } 52 | 53 | rel, err := filepath.Rel(root, path) 54 | if re.MatchString(rel) { 55 | ret = append(ret, rel) 56 | } 57 | 58 | return nil 59 | }) 60 | 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return ret, nil 66 | } 67 | 68 | type SettingLoader struct { 69 | sess *session.Session 70 | kmsKeyID *string 71 | reg []*regexp.Regexp 72 | } 73 | 74 | func NewLoader(sess *session.Session, kmsKeyID *string) *SettingLoader { 75 | return &SettingLoader{ 76 | sess: sess, 77 | kmsKeyID: kmsKeyID, 78 | } 79 | } 80 | 81 | func (s *SettingLoader) Load(basePath, varsPath, outerVals string) (*setting.Setting, error) { 82 | var setting = v1Setting{} 83 | varFileList, err := fileList(varsPath) 84 | if err != nil { 85 | return nil, err 86 | } 87 | baseConf, err := ioutil.ReadFile(basePath) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | for _, path := range varFileList { 93 | bin, err := ioutil.ReadFile(strings.TrimSuffix(varsPath, "/") + "/" + path) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | if b, err := s.decrypt(bin); err != nil { 99 | return nil, err 100 | } else { 101 | bin = b 102 | } 103 | 104 | baseConf, err = jec.Embed(baseConf, bin) 105 | if err != nil { 106 | return nil, err 107 | } 108 | } 109 | 110 | if err := json.Unmarshal(baseConf, &setting); err != nil { 111 | return nil, err 112 | } 113 | 114 | return setting.Convert(), nil 115 | } 116 | 117 | func (s *SettingLoader) isEncrypt(bin []byte) bool { 118 | var buffer = cryptex.Container{} 119 | if err := json.Unmarshal(bin, &buffer); err != nil { 120 | return false 121 | } 122 | return buffer.EncryptionType == "kms" 123 | } 124 | 125 | func (s *SettingLoader) decrypt(bin []byte) ([]byte, error) { 126 | if !s.isEncrypt(bin) { 127 | return bin, nil 128 | } 129 | 130 | kmsClient := kms.New(s.sess) 131 | kmsClient.SetKey(*s.kmsKeyID) 132 | 133 | var buffer = cryptex.Container{} 134 | if err := json.Unmarshal(bin, &buffer); err != nil { 135 | return nil, err 136 | } 137 | plain, err := cryptex.New(kmsClient).Decrypt(&buffer) 138 | if err != nil { 139 | return nil, err 140 | } 141 | return json.Marshal(plain) 142 | } 143 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/jobtalk/pnzr/subcmd/deploy" 10 | "github.com/jobtalk/pnzr/subcmd/update" 11 | "github.com/jobtalk/pnzr/subcmd/vault" 12 | "github.com/jobtalk/pnzr/vars" 13 | "github.com/joho/godotenv" 14 | "github.com/mitchellh/cli" 15 | ) 16 | 17 | var ( 18 | VERSION string 19 | BUILD_DATE string 20 | BUILD_OS string 21 | ) 22 | 23 | func generateBuildInfo() string { 24 | ret := fmt.Sprintf("Build version: %s\n", VERSION) 25 | ret += fmt.Sprintf("Go version: %s\n", runtime.Version()) 26 | ret += fmt.Sprintf("Build Date: %s\n", BUILD_DATE) 27 | ret += fmt.Sprintf("Build OS: %s\n", BUILD_OS) 28 | return ret 29 | } 30 | 31 | func init() { 32 | if VERSION == "" { 33 | VERSION = "unknown" 34 | } 35 | vars.VERSION = VERSION 36 | vars.BUILD_DATE = BUILD_DATE 37 | vars.BUILD_OS = BUILD_OS 38 | 39 | log.SetFlags(log.Llongfile) 40 | godotenv.Load("~/.pnzr") 41 | godotenv.Load(".pnzr") 42 | } 43 | 44 | func main() { 45 | c := cli.NewCLI("pnzr", generateBuildInfo()) 46 | c.Args = os.Args[1:] 47 | c.Commands = map[string]cli.CommandFactory{ 48 | "deploy": func() (cli.Command, error) { 49 | return &deploy.DeployCommand{}, nil 50 | }, 51 | "vault": func() (cli.Command, error) { 52 | return vault.New(os.Args[1:]), nil 53 | }, 54 | "update": func() (cli.Command, error) { 55 | return &update.UpdateCommand{}, nil 56 | }, 57 | } 58 | exitCode, err := c.Run() 59 | if err != nil { 60 | log.Fatalln(err) 61 | } 62 | os.Exit(exitCode) 63 | } 64 | -------------------------------------------------------------------------------- /subcmd/deploy/deploy.go: -------------------------------------------------------------------------------- 1 | package deploy 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "bytes" 13 | 14 | "github.com/aws/aws-sdk-go/aws" 15 | "github.com/aws/aws-sdk-go/aws/credentials" 16 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 17 | "github.com/aws/aws-sdk-go/aws/session" 18 | "github.com/ieee0824/getenv" 19 | "github.com/jobtalk/pnzr/api" 20 | "github.com/jobtalk/pnzr/lib/setting" 21 | "github.com/jobtalk/pnzr/lib/setting/prototype" 22 | "github.com/jobtalk/pnzr/lib/setting/v1" 23 | ) 24 | 25 | type DeployCommand struct { 26 | dryRun *bool 27 | sess *session.Session 28 | config *aws.Config 29 | credentialFileName string 30 | paramsFromArgs *params 31 | paramsFromEnvs *params 32 | mergedParams *params 33 | } 34 | 35 | type params struct { 36 | kmsKeyID *string 37 | file *string 38 | profile *string 39 | region *string 40 | varsPath *string 41 | overrideTag *string 42 | awsAccessKey *string 43 | awsSecretKey *string 44 | } 45 | 46 | type DryRun struct { 47 | Region string 48 | Config setting.Setting 49 | } 50 | 51 | func (d DryRun) String() string { 52 | structJSON, err := json.MarshalIndent(d, "", " ") 53 | if err != nil { 54 | panic(err) 55 | } 56 | return fmt.Sprintf("%s", string(structJSON)) 57 | } 58 | 59 | var ( 60 | DockerImageParseErr = errors.New("parse error") 61 | IllegalAccessKeyOptionErr = errors.New("There was an illegal input in '-aws-access-key-id' or '-aws-secret-key-id '") 62 | ) 63 | 64 | func parseDockerImage(image string) (url, tag string, err error) { 65 | r := strings.Split(image, ":") 66 | if 3 <= len(r) { 67 | return "", "", DockerImageParseErr 68 | } 69 | if len(r) == 2 { 70 | return r[0], r[1], nil 71 | } 72 | return r[0], "", nil 73 | } 74 | 75 | func stringIsEmpty(s *string) bool { 76 | if s == nil { 77 | return true 78 | } 79 | 80 | return len(*s) == 0 81 | } 82 | 83 | func (d *DeployCommand) parseArgs(args []string) (helpString string) { 84 | p := params{} 85 | flagSet := new(flag.FlagSet) 86 | var f *string 87 | 88 | buffer := new(bytes.Buffer) 89 | flagSet.SetOutput(buffer) 90 | 91 | p.kmsKeyID = flagSet.String("key_id", "", "Amazon KMS key ID") 92 | p.file = flagSet.String("file", "", "target file") 93 | f = flagSet.String("f", "", "target file") 94 | p.profile = flagSet.String("profile", "", "aws credentials profile name") 95 | p.region = flagSet.String("region", "", "aws region") 96 | p.varsPath = flagSet.String("vars_path", "", "external conf path") 97 | p.overrideTag = flagSet.String("t", "", "tag override param") 98 | p.awsAccessKey = flagSet.String("aws-access-key-id", "", "aws access key id") 99 | p.awsSecretKey = flagSet.String("aws-secret-key-id", "", "aws secret key id") 100 | d.dryRun = flagSet.Bool("dry-run", false, "dry run mode") 101 | 102 | if err := flagSet.Parse(args); err != nil { 103 | if err == flag.ErrHelp { 104 | return buffer.String() 105 | } 106 | panic(err) 107 | } 108 | 109 | if *f == "" && *p.file == "" && len(flagSet.Args()) != 0 { 110 | targetName := flagSet.Args()[0] 111 | p.file = &targetName 112 | } 113 | 114 | if *p.file == "" { 115 | p.file = f 116 | } 117 | 118 | d.paramsFromArgs = &p 119 | 120 | return 121 | } 122 | 123 | func (d *DeployCommand) parseEnv() { 124 | p := params{} 125 | 126 | p.kmsKeyID = aws.String(getenv.String("KMS_KEY_ID")) 127 | p.profile = aws.String(getenv.String("AWS_PROFILE_NAME", "default")) 128 | p.overrideTag = aws.String(getenv.String("DOCKER_DEFAULT_DEPLOY_TAG", "latest")) 129 | p.region = aws.String(getenv.String("AWS_REGION")) 130 | p.awsAccessKey = aws.String(getenv.String("AWS_ACCESS_KEY_ID")) 131 | p.awsSecretKey = aws.String(getenv.String("AWS_SECRET_ACCESS_KEY")) 132 | 133 | d.paramsFromEnvs = &p 134 | } 135 | 136 | func (d *DeployCommand) mergeParams() { 137 | result := params{} 138 | 139 | if d.paramsFromArgs == nil { 140 | d.mergedParams = d.paramsFromEnvs 141 | return 142 | } 143 | if d.paramsFromEnvs == nil { 144 | d.mergedParams = d.paramsFromArgs 145 | return 146 | } 147 | 148 | if stringIsEmpty(d.paramsFromArgs.kmsKeyID) && !stringIsEmpty(d.paramsFromEnvs.kmsKeyID) { 149 | result.kmsKeyID = d.paramsFromEnvs.kmsKeyID 150 | } else { 151 | result.kmsKeyID = d.paramsFromArgs.kmsKeyID 152 | } 153 | 154 | if stringIsEmpty(d.paramsFromArgs.file) && !stringIsEmpty(d.paramsFromEnvs.file) { 155 | result.file = d.paramsFromEnvs.file 156 | } else { 157 | result.file = d.paramsFromArgs.file 158 | } 159 | 160 | if stringIsEmpty(d.paramsFromArgs.varsPath) && !stringIsEmpty(d.paramsFromEnvs.varsPath) { 161 | result.varsPath = d.paramsFromEnvs.varsPath 162 | } else { 163 | result.varsPath = d.paramsFromArgs.varsPath 164 | } 165 | 166 | if stringIsEmpty(d.paramsFromArgs.profile) && !stringIsEmpty(d.paramsFromEnvs.profile) { 167 | result.profile = d.paramsFromEnvs.profile 168 | } else { 169 | result.profile = d.paramsFromArgs.profile 170 | } 171 | 172 | if stringIsEmpty(d.paramsFromArgs.overrideTag) && !stringIsEmpty(d.paramsFromEnvs.overrideTag) { 173 | result.overrideTag = d.paramsFromEnvs.overrideTag 174 | } else { 175 | result.overrideTag = d.paramsFromArgs.overrideTag 176 | } 177 | 178 | if stringIsEmpty(d.paramsFromArgs.region) && !stringIsEmpty(d.paramsFromEnvs.region) { 179 | result.region = d.paramsFromEnvs.region 180 | } else { 181 | result.region = d.paramsFromArgs.region 182 | } 183 | 184 | if stringIsEmpty(d.paramsFromArgs.awsAccessKey) && !stringIsEmpty(d.paramsFromEnvs.awsAccessKey) { 185 | result.awsAccessKey = d.paramsFromEnvs.awsAccessKey 186 | } else { 187 | result.awsAccessKey = d.paramsFromArgs.awsAccessKey 188 | } 189 | 190 | if stringIsEmpty(d.paramsFromArgs.awsSecretKey) && !stringIsEmpty(d.paramsFromEnvs.awsSecretKey) { 191 | result.awsSecretKey = d.paramsFromEnvs.awsSecretKey 192 | } else { 193 | result.awsSecretKey = d.paramsFromArgs.awsSecretKey 194 | } 195 | 196 | d.mergedParams = &result 197 | } 198 | 199 | func (d *DeployCommand) loadCredentials() error { 200 | if stringIsEmpty(d.mergedParams.awsAccessKey) != stringIsEmpty(d.mergedParams.awsSecretKey) { 201 | return IllegalAccessKeyOptionErr 202 | } 203 | 204 | if !stringIsEmpty(d.mergedParams.awsAccessKey) && !stringIsEmpty(d.mergedParams.awsSecretKey) { 205 | d.config = new(aws.Config) 206 | d.config.Credentials = credentials.NewStaticCredentials(*d.mergedParams.awsAccessKey, *d.mergedParams.awsSecretKey, "") 207 | return nil 208 | } 209 | 210 | return nil 211 | } 212 | 213 | func (d *DeployCommand) generateSession() { 214 | if d.config != nil { 215 | d.sess = session.Must(session.NewSessionWithOptions(session.Options{ 216 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 217 | SharedConfigState: session.SharedConfigEnable, 218 | Config: *d.config, 219 | })) 220 | } else if !stringIsEmpty(d.mergedParams.profile) { 221 | d.sess = session.Must(session.NewSessionWithOptions(session.Options{ 222 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 223 | SharedConfigState: session.SharedConfigEnable, 224 | Profile: *d.mergedParams.profile, 225 | })) 226 | } else { 227 | d.sess = session.Must(session.NewSessionWithOptions(session.Options{ 228 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 229 | SharedConfigState: session.SharedConfigEnable, 230 | Profile: "default", 231 | })) 232 | } 233 | 234 | if !stringIsEmpty(d.mergedParams.region) { 235 | d.sess.Config.Region = d.mergedParams.region 236 | } 237 | } 238 | 239 | func (d *DeployCommand) Run(args []string) int { 240 | d.parseArgs(args) 241 | d.parseEnv() 242 | d.mergeParams() 243 | if err := d.loadCredentials(); err != nil { 244 | panic(err) 245 | } 246 | d.generateSession() 247 | 248 | var loader setting.Loader 249 | if setting.IsV1Setting(*d.mergedParams.file) { 250 | loader = v1.NewLoader(d.sess, d.mergedParams.kmsKeyID) 251 | } else { 252 | loader = prototype.NewLoader(d.sess, d.mergedParams.kmsKeyID) 253 | } 254 | 255 | deploySetting, err := loader.Load(*d.mergedParams.file, *d.mergedParams.varsPath, "") 256 | if err != nil { 257 | panic(err) 258 | } 259 | 260 | for i, containerDefinition := range deploySetting.TaskDefinition.ContainerDefinitions { 261 | imageName, tag, err := parseDockerImage(*containerDefinition.Image) 262 | if err != nil { 263 | panic(err) 264 | } 265 | 266 | if tag == "$tag" { 267 | image := imageName + ":" + *d.mergedParams.overrideTag 268 | deploySetting.TaskDefinition.ContainerDefinitions[i].Image = &image 269 | } else if tag == "" { 270 | image := imageName + ":" + "latest" 271 | deploySetting.TaskDefinition.ContainerDefinitions[i].Image = &image 272 | } 273 | } 274 | 275 | if *d.dryRun { 276 | dryRunFormat := &DryRun{ 277 | *d.mergedParams.region, 278 | *deploySetting, 279 | } 280 | f, err := os.Open("/dev/stderr") 281 | if err != nil { 282 | panic(err) 283 | } 284 | fmt.Fprintf(f, "******** DRY RUN ********\n%s\n", *dryRunFormat) 285 | f.Close() 286 | return 0 287 | } 288 | 289 | result, err := api.Deploy(d.sess, deploySetting) 290 | if err != nil { 291 | log.Fatalln(err) 292 | } 293 | resultJSON, err := json.MarshalIndent(result, "", " ") 294 | if err != nil { 295 | log.Fatalln(err) 296 | } 297 | fmt.Println(string(resultJSON)) 298 | return 0 299 | } 300 | 301 | func (c *DeployCommand) Synopsis() string { 302 | return "Deploy docker on ecs." 303 | } 304 | 305 | func (c *DeployCommand) Help() string { 306 | return c.parseArgs([]string{"-h"}) 307 | } 308 | -------------------------------------------------------------------------------- /subcmd/deploy/deploy_test.go: -------------------------------------------------------------------------------- 1 | package deploy 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | ) 10 | 11 | var ( 12 | TEST_DIR = os.Getenv("GOPATH") + "/src/github.com/jobtalk/pnzr/test" 13 | ) 14 | 15 | func init() { 16 | } 17 | 18 | func TestCompareStringPointer(t *testing.T) { 19 | tests := []struct { 20 | inS1 *string 21 | inS2 *string 22 | want bool 23 | }{ 24 | { 25 | nil, 26 | nil, 27 | true, 28 | }, 29 | { 30 | aws.String("hoge"), 31 | aws.String("hoge"), 32 | true, 33 | }, 34 | { 35 | nil, 36 | aws.String("hoge"), 37 | false, 38 | }, 39 | { 40 | aws.String("hoge"), 41 | nil, 42 | false, 43 | }, 44 | { 45 | aws.String("hgoe"), 46 | aws.String("huga"), 47 | false, 48 | }, 49 | { 50 | aws.String(""), 51 | nil, 52 | true, 53 | }, 54 | { 55 | nil, 56 | aws.String(""), 57 | true, 58 | }, 59 | } 60 | 61 | for _, test := range tests { 62 | got := compareStringPointer(test.inS1, test.inS2) 63 | 64 | if got != test.want { 65 | t.Fatalf("want %v, but %v:", test.want, got) 66 | } 67 | } 68 | } 69 | 70 | func compareStringPointer(s1, s2 *string) bool { 71 | if s1 == nil && s2 != nil { 72 | return *s2 == "" 73 | } else if s1 != nil && s2 == nil { 74 | return *s1 == "" 75 | } else if s1 == nil && s2 == nil { 76 | return true 77 | } 78 | return *s1 == *s2 79 | } 80 | 81 | func compareParam(p1, p2 *params) bool { 82 | if p1 == nil && p2 != nil { 83 | return false 84 | } else if p1 != nil && p2 == nil { 85 | return false 86 | } 87 | if !compareStringPointer(p1.kmsKeyID, p2.kmsKeyID) { 88 | fmt.Println("kms key is not match") 89 | return false 90 | } 91 | if !compareStringPointer(p1.file, p2.file) { 92 | fmt.Println("file name is not match") 93 | return false 94 | } 95 | if !compareStringPointer(p1.profile, p2.profile) { 96 | fmt.Println("profile name is not match") 97 | return false 98 | } 99 | if !compareStringPointer(p1.region, p2.region) { 100 | fmt.Println("region name is not match") 101 | return false 102 | } 103 | if !compareStringPointer(p1.varsPath, p2.varsPath) { 104 | fmt.Println("vars path name is not match") 105 | return false 106 | } 107 | if !compareStringPointer(p1.overrideTag, p2.overrideTag) { 108 | fmt.Println("tag name is not match") 109 | return false 110 | } 111 | if !compareStringPointer(p1.awsAccessKey, p2.awsAccessKey) { 112 | fmt.Println("access key is not match") 113 | return false 114 | } 115 | if !compareStringPointer(p1.awsSecretKey, p2.awsSecretKey) { 116 | fmt.Println("secret key is not match") 117 | return false 118 | } 119 | return true 120 | } 121 | -------------------------------------------------------------------------------- /subcmd/deploy/mergeParams_test.go: -------------------------------------------------------------------------------- 1 | package deploy 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/jobtalk/pnzr/vars" 10 | "github.com/joho/godotenv" 11 | ) 12 | 13 | func TestStringIsEmpty(t *testing.T) { 14 | tests := []struct { 15 | input *string 16 | want bool 17 | }{ 18 | { 19 | nil, 20 | true, 21 | }, 22 | { 23 | aws.String(""), 24 | true, 25 | }, 26 | { 27 | aws.String("hoge"), 28 | false, 29 | }, 30 | } 31 | 32 | for _, test := range tests { 33 | got := stringIsEmpty(test.input) 34 | if test.want != got { 35 | t.Fatalf("want %v, but %v:", test.want, got) 36 | } 37 | } 38 | } 39 | 40 | func TestDeployCommand_MergeParams(t *testing.T) { 41 | tests := []struct { 42 | argsParams *params 43 | envParams *params 44 | want *params 45 | }{ 46 | { 47 | ¶ms{}, 48 | ¶ms{}, 49 | ¶ms{}, 50 | }, 51 | { 52 | ¶ms{}, 53 | nil, 54 | ¶ms{}, 55 | }, 56 | { 57 | nil, 58 | ¶ms{}, 59 | ¶ms{}, 60 | }, 61 | { 62 | nil, 63 | nil, 64 | nil, 65 | }, 66 | { 67 | ¶ms{ 68 | aws.String(""), 69 | aws.String(""), 70 | aws.String(""), 71 | aws.String(""), 72 | aws.String(""), 73 | aws.String(""), 74 | aws.String(""), 75 | aws.String(""), 76 | }, 77 | nil, 78 | ¶ms{ 79 | aws.String(""), 80 | aws.String(""), 81 | aws.String(""), 82 | aws.String(""), 83 | aws.String(""), 84 | aws.String(""), 85 | aws.String(""), 86 | aws.String(""), 87 | }, 88 | }, 89 | { 90 | ¶ms{ 91 | aws.String("hoge"), 92 | aws.String("huga"), 93 | aws.String("foo"), 94 | aws.String("bar"), 95 | aws.String("baz"), 96 | aws.String("fizz"), 97 | aws.String("bazz"), 98 | aws.String("fizzbazz"), 99 | }, 100 | nil, 101 | ¶ms{ 102 | aws.String("hoge"), 103 | aws.String("huga"), 104 | aws.String("foo"), 105 | aws.String("bar"), 106 | aws.String("baz"), 107 | aws.String("fizz"), 108 | aws.String("bazz"), 109 | aws.String("fizzbazz"), 110 | }, 111 | }, 112 | { 113 | nil, 114 | ¶ms{ 115 | aws.String(""), 116 | aws.String(""), 117 | aws.String(""), 118 | aws.String(""), 119 | aws.String(""), 120 | aws.String(""), 121 | aws.String(""), 122 | aws.String(""), 123 | }, 124 | ¶ms{ 125 | aws.String(""), 126 | aws.String(""), 127 | aws.String(""), 128 | aws.String(""), 129 | aws.String(""), 130 | aws.String(""), 131 | aws.String(""), 132 | aws.String(""), 133 | }, 134 | }, 135 | { 136 | nil, 137 | ¶ms{ 138 | aws.String("hoge"), 139 | aws.String("huga"), 140 | aws.String("foo"), 141 | aws.String("bar"), 142 | aws.String("baz"), 143 | aws.String("fizz"), 144 | aws.String("bazz"), 145 | aws.String("fizzbazz"), 146 | }, 147 | ¶ms{ 148 | aws.String("hoge"), 149 | aws.String("huga"), 150 | aws.String("foo"), 151 | aws.String("bar"), 152 | aws.String("baz"), 153 | aws.String("fizz"), 154 | aws.String("bazz"), 155 | aws.String("fizzbazz"), 156 | }, 157 | }, 158 | { 159 | ¶ms{ 160 | aws.String("hoge"), 161 | aws.String("huga"), 162 | aws.String("foo"), 163 | aws.String("bar"), 164 | aws.String("baz"), 165 | aws.String("fizz"), 166 | aws.String("bazz"), 167 | aws.String("fizzbazz"), 168 | }, 169 | ¶ms{ 170 | aws.String("1"), 171 | aws.String("2"), 172 | aws.String("3"), 173 | aws.String("4"), 174 | aws.String("5"), 175 | aws.String("6"), 176 | aws.String("7"), 177 | aws.String("8"), 178 | }, 179 | ¶ms{ 180 | aws.String("hoge"), 181 | aws.String("huga"), 182 | aws.String("foo"), 183 | aws.String("bar"), 184 | aws.String("baz"), 185 | aws.String("fizz"), 186 | aws.String("bazz"), 187 | aws.String("fizzbazz"), 188 | }, 189 | }, 190 | { 191 | ¶ms{ 192 | aws.String(""), 193 | aws.String(""), 194 | aws.String(""), 195 | aws.String(""), 196 | aws.String(""), 197 | aws.String(""), 198 | aws.String(""), 199 | aws.String(""), 200 | }, 201 | ¶ms{ 202 | aws.String("1"), 203 | aws.String("2"), 204 | aws.String("3"), 205 | aws.String("4"), 206 | aws.String("5"), 207 | aws.String("6"), 208 | aws.String("7"), 209 | aws.String("8"), 210 | }, 211 | ¶ms{ 212 | aws.String("1"), 213 | aws.String("2"), 214 | aws.String("3"), 215 | aws.String("4"), 216 | aws.String("5"), 217 | aws.String("6"), 218 | aws.String("7"), 219 | aws.String("8"), 220 | }, 221 | }, 222 | { 223 | ¶ms{ 224 | aws.String(""), 225 | aws.String(""), 226 | aws.String(""), 227 | aws.String(""), 228 | aws.String(""), 229 | aws.String(""), 230 | aws.String(""), 231 | aws.String(""), 232 | }, 233 | ¶ms{}, 234 | ¶ms{ 235 | aws.String(""), 236 | aws.String(""), 237 | aws.String(""), 238 | aws.String(""), 239 | aws.String(""), 240 | aws.String(""), 241 | aws.String(""), 242 | aws.String(""), 243 | }, 244 | }, 245 | } 246 | 247 | for _, test := range tests { 248 | deployCmd := &DeployCommand{ 249 | paramsFromArgs: test.argsParams, 250 | paramsFromEnvs: test.envParams, 251 | } 252 | 253 | deployCmd.mergeParams() 254 | 255 | got := deployCmd.mergedParams 256 | 257 | if test.argsParams == nil && test.envParams == nil && got != nil { 258 | t.Fatalf("error prams is not nil") 259 | } 260 | if got != nil && !compareParam(got, test.want) { 261 | t.Fatalf("error prams is not match") 262 | } 263 | } 264 | } 265 | 266 | func TestDeployCommand_MergeUseDotFile(t *testing.T) { 267 | testDataDir := vars.TEST_DATA_DIR_ROOT + "/subcmd/deploy/mergeParams/mergeUseDotFile" 268 | tests := []struct { 269 | args []string 270 | want *params 271 | }{ 272 | { 273 | []string{}, 274 | ¶ms{ 275 | kmsKeyID: aws.String(""), 276 | file: aws.String(""), 277 | profile: aws.String("default"), 278 | varsPath: aws.String(""), 279 | overrideTag: aws.String("latest"), 280 | region: aws.String(""), 281 | awsAccessKey: aws.String(""), 282 | awsSecretKey: aws.String(""), 283 | }, 284 | }, 285 | { 286 | []string{ 287 | "-key_id", "hoge", 288 | }, 289 | ¶ms{ 290 | kmsKeyID: aws.String("hoge"), 291 | file: aws.String(""), 292 | profile: aws.String("default"), 293 | varsPath: aws.String(""), 294 | overrideTag: aws.String("latest"), 295 | region: aws.String(""), 296 | awsAccessKey: aws.String(""), 297 | awsSecretKey: aws.String(""), 298 | }, 299 | }, 300 | { 301 | []string{ 302 | "-key_id", "primary-id", 303 | }, 304 | ¶ms{ 305 | kmsKeyID: aws.String("primary-id"), 306 | file: aws.String(""), 307 | profile: aws.String("default"), 308 | varsPath: aws.String(""), 309 | overrideTag: aws.String("latest"), 310 | region: aws.String(""), 311 | awsAccessKey: aws.String(""), 312 | awsSecretKey: aws.String(""), 313 | }, 314 | }, 315 | { 316 | []string{}, 317 | ¶ms{ 318 | kmsKeyID: aws.String("secondary-id"), 319 | file: aws.String(""), 320 | profile: aws.String("default"), 321 | varsPath: aws.String(""), 322 | overrideTag: aws.String("latest"), 323 | region: aws.String(""), 324 | awsAccessKey: aws.String(""), 325 | awsSecretKey: aws.String(""), 326 | }, 327 | }, 328 | { 329 | []string{ 330 | "-profile", "primary-profile", 331 | }, 332 | ¶ms{ 333 | kmsKeyID: aws.String(""), 334 | file: aws.String(""), 335 | profile: aws.String("primary-profile"), 336 | varsPath: aws.String(""), 337 | overrideTag: aws.String("latest"), 338 | region: aws.String(""), 339 | awsAccessKey: aws.String(""), 340 | awsSecretKey: aws.String(""), 341 | }, 342 | }, 343 | { 344 | []string{ 345 | "-profile", "primary-profile", 346 | }, 347 | ¶ms{ 348 | kmsKeyID: aws.String(""), 349 | file: aws.String(""), 350 | profile: aws.String("primary-profile"), 351 | varsPath: aws.String(""), 352 | overrideTag: aws.String("latest"), 353 | region: aws.String(""), 354 | awsAccessKey: aws.String(""), 355 | awsSecretKey: aws.String(""), 356 | }, 357 | }, 358 | { 359 | []string{}, 360 | ¶ms{ 361 | kmsKeyID: aws.String(""), 362 | file: aws.String(""), 363 | profile: aws.String("secondary-profile"), 364 | varsPath: aws.String(""), 365 | overrideTag: aws.String("latest"), 366 | region: aws.String(""), 367 | awsAccessKey: aws.String(""), 368 | awsSecretKey: aws.String(""), 369 | }, 370 | }, 371 | } 372 | 373 | for i, test := range tests { 374 | func(test struct { 375 | args []string 376 | want *params 377 | }) { 378 | dotEnvFileName := fmt.Sprintf("%s/%d.env", testDataDir, i) 379 | envMap, err := godotenv.Read(dotEnvFileName) 380 | if err != nil { 381 | panic(err) 382 | } 383 | if err := godotenv.Load(dotEnvFileName); err != nil { 384 | panic(err) 385 | } 386 | defer func(m map[string]string) { 387 | for k := range m { 388 | os.Unsetenv(k) 389 | } 390 | }(envMap) 391 | 392 | deployCmd := &DeployCommand{} 393 | deployCmd.parseArgs(test.args) 394 | deployCmd.parseEnv() 395 | deployCmd.mergeParams() 396 | 397 | got := deployCmd.mergedParams 398 | 399 | if !compareParam(got, test.want) { 400 | t.Fatalf("want %q, but %q:", test.want, got) 401 | } 402 | 403 | }(test) 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /subcmd/deploy/parseDockerImage_test.go: -------------------------------------------------------------------------------- 1 | package deploy 2 | 3 | import "testing" 4 | 5 | func TestParseDockerImage(t *testing.T) { 6 | type wantSt struct { 7 | url string 8 | tag string 9 | } 10 | tests := []struct { 11 | in string 12 | want wantSt 13 | expectsErr bool 14 | }{ 15 | { 16 | "foo.bar.baz", 17 | wantSt{ 18 | url: "foo.bar.baz", 19 | }, 20 | false, 21 | }, 22 | { 23 | "foo.bar.baz:hoge", 24 | wantSt{ 25 | url: "foo.bar.baz", 26 | tag: "hoge", 27 | }, 28 | false, 29 | }, 30 | { 31 | in: "foo:bar:baz:hoge", 32 | expectsErr: true, 33 | }, 34 | } 35 | 36 | for _, test := range tests { 37 | url, tag, err := parseDockerImage(test.in) 38 | 39 | if !test.expectsErr && err != nil { 40 | t.Fatalf("should not be error for %v but: %v", test.in, err) 41 | } 42 | if test.expectsErr && err == nil { 43 | t.Fatalf("should be error for %v but not:", test.in) 44 | } 45 | if test.want.tag != tag || test.want.url != url { 46 | t.Fatalf("want tag: %q, want url: %q, but tag: %q, url: %q", test.want.tag, test.want.url, tag, url) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /subcmd/deploy/parseEnv_test.go: -------------------------------------------------------------------------------- 1 | package deploy 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func eraseEnv() { 10 | if err := os.Unsetenv("KMS_KEY_ID"); err != nil { 11 | panic(err) 12 | } 13 | if err := os.Unsetenv("AWS_PROFILE_NAME"); err != nil { 14 | panic(err) 15 | } 16 | if err := os.Unsetenv("DOCKER_DEFAULT_DEPLOY_TAG"); err != nil { 17 | panic(err) 18 | } 19 | if err := os.Unsetenv("AWS_REGION"); err != nil { 20 | panic(err) 21 | } 22 | if err := os.Unsetenv("AWS_ACCESS_KEY_ID"); err != nil { 23 | panic(err) 24 | } 25 | if err := os.Unsetenv("AWS_SECRET_ACCESS_KEY"); err != nil { 26 | panic(err) 27 | } 28 | } 29 | 30 | func TestDeployCommand_ParseEnv(t *testing.T) { 31 | eraseEnv() 32 | tests := []struct { 33 | envs map[string]string 34 | want *params 35 | }{ 36 | { 37 | map[string]string{}, 38 | ¶ms{ 39 | kmsKeyID: aws.String(""), 40 | profile: aws.String("default"), 41 | overrideTag: aws.String("latest"), 42 | region: aws.String(""), 43 | awsAccessKey: aws.String(""), 44 | awsSecretKey: aws.String(""), 45 | }, 46 | }, 47 | { 48 | map[string]string{ 49 | "KMS_KEY_ID": "hoge", 50 | }, 51 | ¶ms{ 52 | kmsKeyID: aws.String("hoge"), 53 | profile: aws.String("default"), 54 | overrideTag: aws.String("latest"), 55 | region: aws.String(""), 56 | awsAccessKey: aws.String(""), 57 | awsSecretKey: aws.String(""), 58 | }, 59 | }, 60 | { 61 | map[string]string{ 62 | "AWS_PROFILE_NAME": "pnzr", 63 | }, 64 | ¶ms{ 65 | kmsKeyID: aws.String(""), 66 | profile: aws.String("pnzr"), 67 | overrideTag: aws.String("latest"), 68 | region: aws.String(""), 69 | awsAccessKey: aws.String(""), 70 | awsSecretKey: aws.String(""), 71 | }, 72 | }, 73 | { 74 | map[string]string{ 75 | "DOCKER_DEFAULT_DEPLOY_TAG": "pnzr", 76 | }, 77 | ¶ms{ 78 | kmsKeyID: aws.String(""), 79 | profile: aws.String("default"), 80 | overrideTag: aws.String("pnzr"), 81 | region: aws.String(""), 82 | awsAccessKey: aws.String(""), 83 | awsSecretKey: aws.String(""), 84 | }, 85 | }, 86 | { 87 | map[string]string{ 88 | "AWS_REGION": "ap-northeast-1", 89 | }, 90 | ¶ms{ 91 | kmsKeyID: aws.String(""), 92 | profile: aws.String("default"), 93 | overrideTag: aws.String("latest"), 94 | region: aws.String("ap-northeast-1"), 95 | awsAccessKey: aws.String(""), 96 | awsSecretKey: aws.String(""), 97 | }, 98 | }, 99 | { 100 | map[string]string{ 101 | "AWS_ACCESS_KEY_ID": "foo", 102 | }, 103 | ¶ms{ 104 | kmsKeyID: aws.String(""), 105 | profile: aws.String("default"), 106 | overrideTag: aws.String("latest"), 107 | region: aws.String(""), 108 | awsAccessKey: aws.String("foo"), 109 | awsSecretKey: aws.String(""), 110 | }, 111 | }, 112 | { 113 | map[string]string{ 114 | "AWS_SECRET_ACCESS_KEY": "bar", 115 | }, 116 | ¶ms{ 117 | kmsKeyID: aws.String(""), 118 | profile: aws.String("default"), 119 | overrideTag: aws.String("latest"), 120 | region: aws.String(""), 121 | awsAccessKey: aws.String(""), 122 | awsSecretKey: aws.String("bar"), 123 | }, 124 | }, 125 | { 126 | map[string]string{ 127 | "HOGE": "huga", 128 | }, 129 | ¶ms{ 130 | kmsKeyID: aws.String(""), 131 | profile: aws.String("default"), 132 | overrideTag: aws.String("latest"), 133 | region: aws.String(""), 134 | awsAccessKey: aws.String(""), 135 | awsSecretKey: aws.String(""), 136 | }, 137 | }, 138 | { 139 | map[string]string{ 140 | "KMS_KEY_ID": "kms", 141 | "AWS_PROFILE_NAME": "profile", 142 | "DOCKER_DEFAULT_DEPLOY_TAG": "tag", 143 | "AWS_REGION": "region", 144 | "AWS_ACCESS_KEY_ID": "access_key", 145 | "AWS_SECRET_ACCESS_KEY": "secret_key", 146 | }, 147 | ¶ms{ 148 | kmsKeyID: aws.String("kms"), 149 | profile: aws.String("profile"), 150 | overrideTag: aws.String("tag"), 151 | region: aws.String("region"), 152 | awsAccessKey: aws.String("access_key"), 153 | awsSecretKey: aws.String("secret_key"), 154 | }, 155 | }, 156 | } 157 | 158 | for _, test := range tests { 159 | func(test struct { 160 | envs map[string]string 161 | want *params 162 | }) { 163 | defer func(m map[string]string) { 164 | for key := range m { 165 | err := os.Unsetenv(key) 166 | if err != nil { 167 | panic(err) 168 | } 169 | } 170 | }(test.envs) 171 | for key, val := range test.envs { 172 | if err := os.Setenv(key, val); err != nil { 173 | panic(err) 174 | } 175 | } 176 | 177 | deployCmd := &DeployCommand{} 178 | deployCmd.parseEnv() 179 | got := deployCmd.paramsFromEnvs 180 | 181 | if !compareParam(got, test.want) { 182 | t.Fatalf("error prams is not match") 183 | } 184 | }(test) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /subcmd/deploy/parseargs_test.go: -------------------------------------------------------------------------------- 1 | package deploy 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | ) 8 | 9 | func TestDeployCommand_parseArgs(t *testing.T) { 10 | tests := []struct { 11 | input []string 12 | want *params 13 | help bool 14 | expectsErr bool 15 | }{ 16 | { 17 | input: []string{}, 18 | want: ¶ms{}, 19 | help: false, 20 | expectsErr: false, 21 | }, 22 | { 23 | input: []string{"-h"}, 24 | want: ¶ms{}, 25 | help: true, 26 | expectsErr: false, 27 | }, 28 | { 29 | input: []string{ 30 | "-key_id", 31 | "hoge", 32 | }, 33 | want: ¶ms{ 34 | kmsKeyID: aws.String("hoge"), 35 | }, 36 | help: false, 37 | expectsErr: false, 38 | }, 39 | { 40 | input: []string{ 41 | "-key_id", 42 | }, 43 | help: false, 44 | expectsErr: true, 45 | }, 46 | { 47 | input: []string{ 48 | "-f", 49 | "hoge", 50 | }, 51 | want: ¶ms{ 52 | file: aws.String("hoge"), 53 | }, 54 | help: false, 55 | expectsErr: false, 56 | }, 57 | { 58 | input: []string{ 59 | "-f", 60 | }, 61 | help: false, 62 | expectsErr: true, 63 | }, 64 | { 65 | input: []string{ 66 | "-file", 67 | "huga", 68 | }, 69 | want: ¶ms{ 70 | file: aws.String("huga"), 71 | }, 72 | help: false, 73 | expectsErr: false, 74 | }, 75 | { 76 | input: []string{ 77 | "-file", 78 | }, 79 | help: false, 80 | expectsErr: true, 81 | }, 82 | { 83 | input: []string{ 84 | "foo", 85 | }, 86 | want: ¶ms{ 87 | file: aws.String("foo"), 88 | }, 89 | help: false, 90 | expectsErr: false, 91 | }, 92 | { 93 | input: []string{ 94 | "-f", "hoge", 95 | "-file", "huga", 96 | }, 97 | want: ¶ms{ 98 | file: aws.String("huga"), 99 | }, 100 | help: false, 101 | expectsErr: false, 102 | }, 103 | { 104 | input: []string{ 105 | "-f", "hoge", 106 | "-file", 107 | }, 108 | help: false, 109 | expectsErr: true, 110 | }, 111 | { 112 | input: []string{ 113 | "-f", "hoge", 114 | "-file", "huga", 115 | "foo", 116 | }, 117 | want: ¶ms{ 118 | file: aws.String("huga"), 119 | }, 120 | help: false, 121 | expectsErr: false, 122 | }, 123 | { 124 | input: []string{ 125 | "-profile", "ap-northeast-1", 126 | }, 127 | want: ¶ms{ 128 | profile: aws.String("ap-northeast-1"), 129 | }, 130 | help: false, 131 | expectsErr: false, 132 | }, 133 | { 134 | input: []string{ 135 | "-vars_path", "hoge", 136 | }, 137 | want: ¶ms{ 138 | varsPath: aws.String("hoge"), 139 | }, 140 | help: false, 141 | expectsErr: false, 142 | }, 143 | { 144 | input: []string{ 145 | "-t", "hoge", 146 | }, 147 | want: ¶ms{ 148 | overrideTag: aws.String("hoge"), 149 | }, 150 | help: false, 151 | expectsErr: false, 152 | }, 153 | { 154 | input: []string{ 155 | "-aws-access-key-id", "hoge", 156 | }, 157 | want: ¶ms{ 158 | awsAccessKey: aws.String("hoge"), 159 | }, 160 | help: false, 161 | expectsErr: false, 162 | }, 163 | { 164 | input: []string{ 165 | "-aws-secret-key-id", "hoge", 166 | }, 167 | want: ¶ms{ 168 | awsSecretKey: aws.String("hoge"), 169 | }, 170 | help: false, 171 | expectsErr: false, 172 | }, 173 | { 174 | input: []string{ 175 | "-aaaaaaaa", 176 | }, 177 | help: false, 178 | expectsErr: true, 179 | }, 180 | { 181 | input: []string{ 182 | "-key_id", "some_key", 183 | "-f", "some_file", 184 | "-profile", "some_profile", 185 | "-region", "some_region", 186 | "-vars_path", "some_vars_path", 187 | "-t", "some_tag", 188 | "-aws-access-key-id", "access_key", 189 | "-aws-secret-key-id", "secret_key", 190 | }, 191 | want: ¶ms{ 192 | kmsKeyID: aws.String("some_key"), 193 | file: aws.String("some_file"), 194 | profile: aws.String("some_profile"), 195 | region: aws.String("some_region"), 196 | varsPath: aws.String("some_vars_path"), 197 | overrideTag: aws.String("some_tag"), 198 | awsAccessKey: aws.String("access_key"), 199 | awsSecretKey: aws.String("secret_key"), 200 | }, 201 | help: false, 202 | expectsErr: false, 203 | }, 204 | } 205 | 206 | for _, test := range tests { 207 | func(test struct { 208 | input []string 209 | want *params 210 | help bool 211 | expectsErr bool 212 | }) { 213 | defer func() { 214 | err := recover() 215 | if !test.expectsErr && err != nil { 216 | t.Fatalf("should not be error for %v but: %v", test.input, err) 217 | } 218 | if test.expectsErr && err == nil { 219 | t.Fatalf("should be error for %v but not:", test.input) 220 | } 221 | }() 222 | deployCmd := &DeployCommand{} 223 | help := deployCmd.parseArgs(test.input) 224 | if !test.help && help != "" { 225 | t.Fatalf("should not be help for %v but: %v", test.input, help) 226 | } 227 | if test.help && help == "" { 228 | t.Fatalf("should be help for %v but not:", test.input) 229 | } 230 | if help != "" { 231 | return 232 | } 233 | if !compareParam(deployCmd.paramsFromArgs, test.want) { 234 | t.Fatalf("error prams is not match") 235 | } 236 | 237 | }(test) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /subcmd/update/update.go: -------------------------------------------------------------------------------- 1 | package update 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | "runtime" 11 | 12 | "github.com/jobtalk/pnzr/vars" 13 | ) 14 | 15 | const ( 16 | GITHUB_API = "https://api.github.com/repos/jobtalk/pnzr/tags" 17 | ) 18 | 19 | var client = &http.Client{} 20 | 21 | type commit struct { 22 | Sha string `json:"sha"` 23 | URL string `json:"url"` 24 | } 25 | 26 | type tag struct { 27 | Name string `json:"name"` 28 | ZipballURL string `json:"zipball_url"` 29 | TarballURL string `json:"tarball_url"` 30 | Commit commit `json:"commit"` 31 | } 32 | 33 | func (t tag) String() string { 34 | bin, err := json.MarshalIndent(t, "", " ") 35 | if err != nil { 36 | return "" 37 | } 38 | return string(bin) 39 | } 40 | 41 | func checkENV() bool { 42 | if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { 43 | return false 44 | } 45 | if runtime.GOARCH != "amd64" { 46 | return false 47 | } 48 | return true 49 | } 50 | 51 | type UpdateCommand struct{} 52 | 53 | func (c *UpdateCommand) Run(args []string) int { 54 | var platform string 55 | tags := []tag{} 56 | resp, err := client.Get(GITHUB_API) 57 | if err != nil { 58 | log.Println(err) 59 | return 255 60 | } 61 | bin, err := ioutil.ReadAll(resp.Body) 62 | if err != nil { 63 | log.Println(err) 64 | return 255 65 | } 66 | defer resp.Body.Close() 67 | if err := json.Unmarshal(bin, &tags); err != nil { 68 | log.Println(err) 69 | return 255 70 | } 71 | 72 | if !checkENV() { 73 | fmt.Printf("Sorry, this architecture not supported (%s %s).\n", runtime.GOOS, runtime.GOARCH) 74 | fmt.Println("Please try manual update.") 75 | return 255 76 | } 77 | 78 | if runtime.GOOS == "darwin" { 79 | platform = "darwin-amd64" 80 | } else if runtime.GOOS == "linux" { 81 | platform = "linux-amd64" 82 | } 83 | latest := tags[0].Name 84 | if vars.VERSION == latest { 85 | fmt.Println("this version is latest") 86 | return 0 87 | } 88 | if latest == "" { 89 | fmt.Println("can not get latest version") 90 | return 255 91 | } 92 | binaryURL := fmt.Sprintf("https://github.com/jobtalk/pnzr/releases/download/%s/pnzr-%s", latest, platform) 93 | 94 | dir, err := os.Executable() 95 | if err != nil { 96 | log.Println(err) 97 | return 255 98 | } 99 | 100 | latestResp, err := client.Get(binaryURL) 101 | if err != nil { 102 | log.Println(err) 103 | return 255 104 | } 105 | latestBin, err := ioutil.ReadAll(latestResp.Body) 106 | if err != nil { 107 | log.Println(err) 108 | return 255 109 | } 110 | 111 | if err := ioutil.WriteFile(dir, latestBin, 0755); err != nil { 112 | fmt.Printf("Check parmission: %s\n", dir) 113 | return 255 114 | } 115 | 116 | fmt.Println("update successed") 117 | 118 | return 0 119 | } 120 | 121 | func (c *UpdateCommand) Synopsis() string { 122 | return "Update pnzr to the latest version." 123 | } 124 | 125 | func (c *UpdateCommand) Help() string { 126 | msg := "\n\n" 127 | msg += " Usage:\n" 128 | msg += " no option\n\n" 129 | msg += " Description:\n" 130 | msg += " update pnzr to the latest version.\n" 131 | msg += "\n" 132 | return msg 133 | } 134 | -------------------------------------------------------------------------------- /subcmd/vault/decrypt/decrypt.go: -------------------------------------------------------------------------------- 1 | package decrypt 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/credentials" 9 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/ieee0824/getenv" 12 | "github.com/jobtalk/pnzr/subcmd/vault/decrypt/prototype" 13 | "github.com/jobtalk/pnzr/subcmd/vault/decrypt/v1" 14 | "github.com/jobtalk/pnzr/subcmd/vault/decrypt/v1/iface" 15 | "github.com/jobtalk/pnzr/vars" 16 | ) 17 | 18 | type DecryptCommand struct { 19 | sess *session.Session 20 | file *string 21 | profile *string 22 | region *string 23 | awsAccessKeyID *string 24 | awsSecretKeyID *string 25 | configVersion *string 26 | v1Decrypter v1_api.API 27 | } 28 | 29 | func (d *DecryptCommand) parseArgs(args []string) (helpString string) { 30 | var ( 31 | flagSet = new(flag.FlagSet) 32 | f *string 33 | ) 34 | 35 | buffer := new(bytes.Buffer) 36 | flagSet.SetOutput(buffer) 37 | d.profile = flagSet.String("profile", getenv.String("AWS_PROFILE_NAME", "default"), "aws credentials profile name") 38 | d.region = flagSet.String("region", getenv.String("AWS_REGION", "ap-northeast-1"), "aws region") 39 | d.awsAccessKeyID = flagSet.String("aws-access-key-id", getenv.String("AWS_ACCESS_KEY_ID"), "aws access key id") 40 | d.awsSecretKeyID = flagSet.String("aws-secret-key-id", getenv.String("AWS_SECRET_KEY_ID"), "aws secret key id") 41 | d.file = flagSet.String("file", "", "target file") 42 | d.configVersion = flagSet.String("v", vars.CONFIG_VERSION, "config version") 43 | f = flagSet.String("f", "", "target file") 44 | 45 | if err := flagSet.Parse(args); err != nil { 46 | if err == flag.ErrHelp { 47 | return buffer.String() 48 | } 49 | panic(err) 50 | } 51 | 52 | if *f == "" && *d.file == "" && len(flagSet.Args()) != 0 { 53 | targetName := flagSet.Args()[0] 54 | d.file = &targetName 55 | } 56 | 57 | if *d.file == "" { 58 | d.file = f 59 | } 60 | 61 | var awsConfig = aws.Config{} 62 | 63 | if *d.awsAccessKeyID != "" && *d.awsSecretKeyID != "" && *d.profile == "" { 64 | awsConfig.Credentials = credentials.NewStaticCredentials(*d.awsAccessKeyID, *d.awsSecretKeyID, "") 65 | awsConfig.Region = d.region 66 | } 67 | 68 | d.sess = session.Must(session.NewSessionWithOptions(session.Options{ 69 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 70 | SharedConfigState: session.SharedConfigEnable, 71 | Profile: *d.profile, 72 | Config: awsConfig, 73 | })) 74 | 75 | return 76 | } 77 | 78 | func (d *DecryptCommand) decrypt(fileName string) error { 79 | switch *d.configVersion { 80 | case "1.0": 81 | return d.v1Decrypter.Decrypt(fileName) 82 | case "prototype": 83 | return prototype.Decrypt(d.sess, fileName) 84 | default: 85 | return fmt.Errorf("unsupport configure version: %v", *d.configVersion) 86 | } 87 | } 88 | 89 | func (d *DecryptCommand) Help() string { 90 | return d.parseArgs([]string{"-h"}) 91 | } 92 | 93 | func (d *DecryptCommand) Synopsis() string { 94 | return "Decryption mode of encrypted file." 95 | } 96 | 97 | func (d *DecryptCommand) Run(args []string) int { 98 | if len(args) == 0 { 99 | fmt.Println(d.Synopsis()) 100 | return 0 101 | } 102 | d.parseArgs(args) 103 | d.v1Decrypter = v1.New(d.sess) 104 | 105 | if err := d.decrypt(*d.file); err != nil { 106 | panic(err) 107 | } 108 | 109 | return 0 110 | } 111 | -------------------------------------------------------------------------------- /subcmd/vault/decrypt/prototype/prototype.go: -------------------------------------------------------------------------------- 1 | package prototype 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | "github.com/jobtalk/pnzr/lib" 8 | "io/ioutil" 9 | ) 10 | 11 | func Decrypt(sess *session.Session, fileName string) error { 12 | bin, err := ioutil.ReadFile(fileName) 13 | if err != nil { 14 | return err 15 | } 16 | kms := lib.NewKMSFromBinary(bin, sess) 17 | if kms == nil { 18 | return errors.New(fmt.Sprintf("%v form is illegal", fileName)) 19 | } 20 | plainText, err := kms.Decrypt() 21 | if err != nil { 22 | return err 23 | } 24 | return ioutil.WriteFile(fileName, plainText, 0644) 25 | } 26 | -------------------------------------------------------------------------------- /subcmd/vault/decrypt/v1/iface/iface.go: -------------------------------------------------------------------------------- 1 | package v1_api 2 | 3 | type API interface { 4 | Decrypt(string) error 5 | } 6 | -------------------------------------------------------------------------------- /subcmd/vault/decrypt/v1/v1.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/aws/aws-sdk-go/aws/session" 6 | "github.com/ieee0824/cryptex" 7 | "github.com/ieee0824/cryptex/kms" 8 | "io/ioutil" 9 | ) 10 | 11 | type Decrypter struct { 12 | c *cryptex.Cryptex 13 | } 14 | 15 | func New(s *session.Session) *Decrypter { 16 | return &Decrypter{ 17 | cryptex.New(kms.New(s)), 18 | } 19 | } 20 | 21 | func (d *Decrypter) Decrypt(fileName string) error { 22 | var chipher = &cryptex.Container{} 23 | chipherBin, err := ioutil.ReadFile(fileName) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | if err := json.Unmarshal(chipherBin, &chipher); err != nil { 29 | return err 30 | } 31 | 32 | plain, err := d.c.Decrypt(chipher) 33 | if err != nil { 34 | return err 35 | } 36 | plainBin, err := json.MarshalIndent(plain, "", " ") 37 | if err != nil { 38 | return err 39 | } 40 | return ioutil.WriteFile(fileName, plainBin, 0644) 41 | } 42 | -------------------------------------------------------------------------------- /subcmd/vault/decrypt/v1/v1_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "reflect" 11 | "testing" 12 | 13 | "github.com/ieee0824/cryptex" 14 | "github.com/ieee0824/cryptex/rsa" 15 | "github.com/jobtalk/pnzr/vars" 16 | ) 17 | 18 | func compareJSON(a, b []byte) bool { 19 | var ai, bi interface{} 20 | if err := json.Unmarshal(a, &ai); err != nil { 21 | panic(err) 22 | } 23 | 24 | if err := json.Unmarshal(b, &bi); err != nil { 25 | panic(err) 26 | } 27 | 28 | return reflect.DeepEqual(ai, bi) 29 | } 30 | 31 | type encrypter struct { 32 | crypter *cryptex.Cryptex 33 | } 34 | 35 | func (e *encrypter) encrypt(fileName string) error { 36 | var i interface{} 37 | bin, err := ioutil.ReadFile(fileName) 38 | if err != nil { 39 | return err 40 | } 41 | if err := json.Unmarshal(bin, &i); err != nil { 42 | return err 43 | } 44 | 45 | c, err := e.crypter.Encrypt(i) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | chipher, err := json.MarshalIndent(c, "", " ") 51 | if err != nil { 52 | return err 53 | } 54 | return ioutil.WriteFile(fileName, chipher, 0644) 55 | } 56 | 57 | type decrypterTester struct { 58 | ORIGIN_TEST_DIR string 59 | COPY_TEST_DIR string 60 | KEY_DIR string 61 | privateKey []byte 62 | publicKey []byte 63 | cryptex *cryptex.Cryptex 64 | encrypter *encrypter 65 | decrypter *Decrypter 66 | testFileInfos []os.FileInfo 67 | } 68 | 69 | func newDecrypterTester() (*decrypterTester, error) { 70 | ret := &decrypterTester{} 71 | ret.ORIGIN_TEST_DIR = vars.TEST_DATA_DIR_ROOT + "/subcmd/vault/decrypt/v1" 72 | ret.COPY_TEST_DIR = ret.ORIGIN_TEST_DIR + "/copy" 73 | ret.KEY_DIR = vars.TEST_DATA_DIR_ROOT + "/key" 74 | 75 | if err := ret.generateRsaKey(); err != nil { 76 | return nil, err 77 | } 78 | ret.generateCryptex() 79 | ret.generateEncrypter() 80 | ret.generateDecrypter() 81 | 82 | if err := ret.generateTestFiles(); err != nil { 83 | return nil, err 84 | } 85 | 86 | return ret, nil 87 | } 88 | 89 | func (t *decrypterTester) generateRsaKey() error { 90 | var err error 91 | t.privateKey, err = ioutil.ReadFile(t.KEY_DIR + "/private-key.pem") 92 | if err != nil { 93 | return err 94 | } 95 | t.publicKey, err = ioutil.ReadFile(t.KEY_DIR + "/public-key.pem") 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | } 101 | 102 | func (t *decrypterTester) generateCryptex() { 103 | t.cryptex = cryptex.New(rsa.New(t.privateKey, t.publicKey)) 104 | } 105 | 106 | func (t *decrypterTester) generateEncrypter() { 107 | t.encrypter = &encrypter{t.cryptex} 108 | } 109 | 110 | func (t *decrypterTester) generateDecrypter() { 111 | t.decrypter = &Decrypter{t.cryptex} 112 | } 113 | 114 | func (t *decrypterTester) generateTestFiles() error { 115 | if err := exec.Command("mkdir", "-p", t.COPY_TEST_DIR).Run(); err != nil { 116 | return err 117 | } 118 | infos, err := ioutil.ReadDir(t.ORIGIN_TEST_DIR) 119 | if err != nil { 120 | return err 121 | } 122 | t.testFileInfos = infos 123 | 124 | return nil 125 | } 126 | 127 | func (t *decrypterTester) generateEncryptedFiles(tests map[string]testInput) error { 128 | for _, info := range t.testFileInfos { 129 | if info.IsDir() { 130 | continue 131 | } 132 | src, err := os.Open(t.ORIGIN_TEST_DIR + "/" + info.Name()) 133 | if err != nil { 134 | return err 135 | } 136 | dst, err := os.Create(t.COPY_TEST_DIR + "/" + info.Name()) 137 | if err != nil { 138 | return err 139 | } 140 | if _, err := io.Copy(dst, src); err != nil { 141 | return err 142 | } 143 | 144 | test, ok := tests[info.Name()] 145 | if ok && test.encryptsTestFileAtFirst { 146 | err := t.encrypter.encrypt(t.COPY_TEST_DIR + "/" + info.Name()) 147 | if err != nil { 148 | return err 149 | } 150 | } 151 | } 152 | return nil 153 | } 154 | 155 | func (t *decrypterTester) fin() error { 156 | if err := os.RemoveAll(t.COPY_TEST_DIR); err != nil { 157 | return err 158 | } 159 | return nil 160 | } 161 | 162 | func (tester *decrypterTester) run(t *testing.T, tests map[string]testInput) error { 163 | for i, test := range tests { 164 | err := tester.decrypter.Decrypt(fmt.Sprintf("%s/%v", tester.COPY_TEST_DIR, i)) 165 | if !test.expectsErr && err != nil { 166 | t.Fatalf("should not be error but: %v", err) 167 | } 168 | if test.expectsErr && err == nil { 169 | t.Fatalf("should be error") 170 | } 171 | if !test.expectsErr { 172 | org, err := ioutil.ReadFile(tester.ORIGIN_TEST_DIR + "/" + i) 173 | if err != nil { 174 | return err 175 | } 176 | decrypted, err := ioutil.ReadFile(tester.COPY_TEST_DIR + "/" + i) 177 | if err != nil { 178 | return err 179 | } 180 | if !compareJSON(org, decrypted) { 181 | t.Log(string(org)) 182 | t.Log(string(decrypted)) 183 | t.Fatalf("not match") 184 | } 185 | } 186 | } 187 | return nil 188 | } 189 | 190 | // testInput struct defines test data. 191 | // 192 | // encryptsTestFileAtFirst field is a flag 193 | // that controls whether to encrypt at the start of the test. 194 | // 195 | // expectsErr field is a flag 196 | // that controls whether or not to expect errors. 197 | type testInput struct { 198 | encryptsTestFileAtFirst bool 199 | expectsErr bool 200 | } 201 | 202 | func TestDecrypter_Decrypt(t *testing.T) { 203 | tester, err := newDecrypterTester() 204 | if err != nil { 205 | panic(err) 206 | } 207 | 208 | tests := map[string]testInput{ 209 | "0.json": { 210 | false, 211 | true, 212 | }, 213 | "1.json": { 214 | true, 215 | false, 216 | }, 217 | } 218 | 219 | if err := tester.generateEncryptedFiles(tests); err != nil { 220 | panic(err) 221 | } 222 | 223 | defer func() { 224 | if err := tester.fin(); err != nil { 225 | panic(err) 226 | } 227 | }() 228 | 229 | if err := tester.run(t, tests); err != nil { 230 | panic(err) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /subcmd/vault/edit/edit.go: -------------------------------------------------------------------------------- 1 | package edit 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/credentials" 9 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/ieee0824/getenv" 12 | "github.com/jobtalk/pnzr/subcmd/vault/edit/prototype" 13 | "github.com/jobtalk/pnzr/subcmd/vault/edit/v1" 14 | "github.com/jobtalk/pnzr/subcmd/vault/edit/v1/iface" 15 | "github.com/jobtalk/pnzr/vars" 16 | ) 17 | 18 | type EditCommand struct { 19 | sess *session.Session 20 | kmsKeyID *string 21 | file *string 22 | profile *string 23 | region *string 24 | awsAccessKeyID *string 25 | awsSecretKeyID *string 26 | configVersion *string 27 | v1Editor v1_api.API 28 | } 29 | 30 | func (e *EditCommand) parseArgs(args []string) (helpString string) { 31 | var ( 32 | flagSet = new(flag.FlagSet) 33 | f *string 34 | ) 35 | 36 | buffer := new(bytes.Buffer) 37 | flagSet.SetOutput(buffer) 38 | 39 | e.kmsKeyID = flagSet.String("key_id", getenv.String("KMS_KEY_ID"), "Amazon KMS key ID") 40 | e.profile = flagSet.String("profile", getenv.String("AWS_PROFILE_NAME", "default"), "aws credentials profile name") 41 | e.region = flagSet.String("region", getenv.String("AWS_REGION", "ap-northeast-1"), "aws region") 42 | e.awsAccessKeyID = flagSet.String("aws-access-key-id", getenv.String("AWS_ACCESS_KEY_ID"), "aws access key id") 43 | e.awsSecretKeyID = flagSet.String("aws-secret-key-id", getenv.String("AWS_SECRET_KEY_ID"), "aws secret key id") 44 | e.file = flagSet.String("file", "", "target file") 45 | e.configVersion = flagSet.String("v", vars.CONFIG_VERSION, "config version") 46 | f = flagSet.String("f", "", "target file") 47 | 48 | if err := flagSet.Parse(args); err != nil { 49 | if err == flag.ErrHelp { 50 | return buffer.String() 51 | } 52 | panic(err) 53 | } 54 | 55 | if *f == "" && *e.file == "" && len(flagSet.Args()) != 0 { 56 | targetName := flagSet.Args()[0] 57 | e.file = &targetName 58 | } 59 | 60 | if *e.file == "" { 61 | e.file = f 62 | } 63 | 64 | var awsConfig = aws.Config{} 65 | 66 | if *e.awsAccessKeyID != "" && *e.awsSecretKeyID != "" && *e.profile == "" { 67 | awsConfig.Credentials = credentials.NewStaticCredentials(*e.awsAccessKeyID, *e.awsSecretKeyID, "") 68 | awsConfig.Region = e.region 69 | } 70 | 71 | e.sess = session.Must(session.NewSessionWithOptions(session.Options{ 72 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 73 | SharedConfigState: session.SharedConfigEnable, 74 | Profile: *e.profile, 75 | Config: awsConfig, 76 | })) 77 | 78 | return 79 | } 80 | 81 | func (e *EditCommand) Help() string { 82 | return e.parseArgs([]string{"-h"}) 83 | } 84 | 85 | func (e *EditCommand) Synopsis() string { 86 | return "Edit mode of encrypted file." 87 | } 88 | 89 | func (e *EditCommand) Run(args []string) int { 90 | if len(args) == 0 { 91 | fmt.Println(e.Synopsis()) 92 | return 0 93 | } 94 | e.parseArgs(args) 95 | e.v1Editor = v1.New(e.sess, *e.kmsKeyID) 96 | 97 | switch *e.configVersion { 98 | case "1.0": 99 | if err := e.v1Editor.Edit(*e.file); err != nil { 100 | panic(err) 101 | } 102 | case "prototype": 103 | if err := prototype.Edit(e.sess, *e.kmsKeyID, *e.file); err != nil { 104 | panic(err) 105 | } 106 | default: 107 | panic(fmt.Errorf("unsupport version: %v", *e.configVersion)) 108 | } 109 | 110 | return 0 111 | } 112 | -------------------------------------------------------------------------------- /subcmd/vault/edit/prototype/prototype.go: -------------------------------------------------------------------------------- 1 | package prototype 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | "github.com/jobtalk/pnzr/lib" 8 | "github.com/jobtalk/pnzr/subcmd/vault/edit/util" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | ) 13 | 14 | func encrypt(sess *session.Session, keyID, fileName string) error { 15 | bin, err := ioutil.ReadFile(fileName) 16 | if err != nil { 17 | return err 18 | } 19 | kms := lib.NewKMS(sess) 20 | _, err = kms.SetKeyID(keyID).Encrypt(bin) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | return ioutil.WriteFile(fileName, []byte(kms.String()), 0644) 26 | } 27 | 28 | func decrypt(sess *session.Session, fileName string) error { 29 | bin, err := ioutil.ReadFile(fileName) 30 | if err != nil { 31 | return err 32 | } 33 | kms := lib.NewKMSFromBinary(bin, sess) 34 | if kms == nil { 35 | return errors.New(fmt.Sprintf("%v form is illegal", fileName)) 36 | } 37 | plainText, err := kms.Decrypt() 38 | if err != nil { 39 | return err 40 | } 41 | return ioutil.WriteFile(fileName, plainText, 0644) 42 | } 43 | 44 | func Edit(sess *session.Session, keyID, fileName string) error { 45 | if err := decrypt(sess, fileName); err != nil { 46 | panic(err) 47 | } 48 | defer func() { 49 | if err := encrypt(sess, keyID, fileName); err != nil { 50 | panic(err) 51 | } 52 | }() 53 | cmd := exec.Command(util.GetEditor(), fileName) 54 | 55 | cmd.Stdin = os.Stdin 56 | cmd.Stdout = os.Stdout 57 | cmd.Stderr = os.Stderr 58 | 59 | if err := cmd.Run(); err != nil { 60 | panic(err) 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /subcmd/vault/edit/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "os" 4 | 5 | func GetEditor() string { 6 | if e := os.Getenv("PNZR_EDITOR"); e != "" { 7 | return e 8 | } 9 | 10 | if e := os.Getenv("EDITOR"); e != "" { 11 | return e 12 | } 13 | 14 | return "nano" 15 | } 16 | -------------------------------------------------------------------------------- /subcmd/vault/edit/util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestGetEditorEnvPnzrEditor(t *testing.T) { 9 | os.Unsetenv("PNZR_EDITOR") 10 | os.Unsetenv("EDITOR") 11 | tests := []struct { 12 | in string 13 | want string 14 | }{ 15 | {"vi", "vi"}, 16 | {"", "nano"}, 17 | } 18 | 19 | for _, test := range tests { 20 | func() { 21 | defer func() { 22 | os.Unsetenv("PNZR_EDITOR") 23 | os.Unsetenv("EDITOR") 24 | }() 25 | 26 | os.Setenv("PNZR_EDITOR", test.in) 27 | 28 | got := GetEditor() 29 | 30 | if got != test.want { 31 | t.Fatalf("want %q, but %q:", test.want, got) 32 | } 33 | }() 34 | } 35 | } 36 | 37 | func TestGetEditorEnvEditor(t *testing.T) { 38 | os.Unsetenv("PNZR_EDITOR") 39 | os.Unsetenv("EDITOR") 40 | tests := []struct { 41 | in string 42 | want string 43 | }{ 44 | {"vi", "vi"}, 45 | {"", "nano"}, 46 | } 47 | 48 | for _, test := range tests { 49 | func() { 50 | defer func() { 51 | os.Unsetenv("PNZR_EDITOR") 52 | os.Unsetenv("EDITOR") 53 | }() 54 | 55 | os.Setenv("EDITOR", test.in) 56 | 57 | got := GetEditor() 58 | 59 | if got != test.want { 60 | t.Fatalf("want %q, but %q:", test.want, got) 61 | } 62 | }() 63 | } 64 | } 65 | 66 | func TestGetEditorNoEnv(t *testing.T) { 67 | os.Unsetenv("PNZR_EDITOR") 68 | os.Unsetenv("EDITOR") 69 | 70 | got := GetEditor() 71 | 72 | if got != "nano" { 73 | t.Fatalf("want %q, but %q:", "nano", got) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /subcmd/vault/edit/v1/iface/iface.go: -------------------------------------------------------------------------------- 1 | package v1_api 2 | 3 | type API interface { 4 | Edit(string) error 5 | } 6 | -------------------------------------------------------------------------------- /subcmd/vault/edit/v1/v1.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/ieee0824/cryptex" 9 | "github.com/ieee0824/cryptex/kms" 10 | "github.com/jobtalk/pnzr/subcmd/vault/edit/util" 11 | ) 12 | 13 | type Editor struct { 14 | c *cryptex.Cryptex 15 | editorName string 16 | } 17 | 18 | func New(s *session.Session, keyID string) *Editor { 19 | return &Editor{ 20 | cryptex.New(kms.New(s).SetKey(keyID)), 21 | util.GetEditor(), 22 | } 23 | } 24 | 25 | func (e *Editor) Edit(fileName string) error { 26 | cryptex.SetEditor(e.editorName) 27 | var container = &cryptex.Container{} 28 | 29 | f, err := os.Open(fileName) 30 | if err != nil { 31 | return err 32 | } 33 | if err := json.NewDecoder(f).Decode(container); err != nil { 34 | return err 35 | } 36 | result, err := e.c.Edit(container) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | w, err := os.OpenFile(fileName, os.O_TRUNC|os.O_WRONLY, 0644) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | encoder := json.NewEncoder(w) 47 | encoder.SetIndent("", " ") 48 | 49 | if err := encoder.Encode(result); err != nil { 50 | return err 51 | } 52 | 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /subcmd/vault/edit/v1/v1_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "reflect" 12 | "strings" 13 | "testing" 14 | "time" 15 | 16 | "github.com/aws/aws-sdk-go/aws/session" 17 | "github.com/ieee0824/cryptex" 18 | "github.com/ieee0824/cryptex/rsa" 19 | "github.com/jobtalk/pnzr/vars" 20 | ) 21 | 22 | func ping() error { 23 | timeup := make(chan bool) 24 | go func() { 25 | time.Sleep(5 * time.Second) 26 | timeup <- true 27 | }() 28 | 29 | for { 30 | select { 31 | case <-timeup: 32 | return fmt.Errorf("timeout") 33 | default: 34 | _, err := (&http.Client{}).Get("http://localhost:8080") 35 | if err == nil { 36 | return nil 37 | } 38 | } 39 | } 40 | } 41 | 42 | func compareJSON(a, b []byte) bool { 43 | var ai, bi interface{} 44 | if err := json.Unmarshal(a, &ai); err != nil { 45 | panic(err) 46 | } 47 | 48 | if err := json.Unmarshal(b, &bi); err != nil { 49 | panic(err) 50 | } 51 | 52 | return reflect.DeepEqual(ai, bi) 53 | } 54 | 55 | type mocChiper struct { 56 | fault bool 57 | } 58 | 59 | func newChiper(b bool) *mocChiper { 60 | return &mocChiper{b} 61 | } 62 | 63 | func (m *mocChiper) Encrypt(d []byte) ([]byte, error) { 64 | if m.fault { 65 | return nil, fmt.Errorf("some error") 66 | } 67 | d = append(d[1:], d[0]) 68 | return d, nil 69 | } 70 | 71 | func (m *mocChiper) Decrypt(d []byte) ([]byte, error) { 72 | if m.fault { 73 | return nil, fmt.Errorf("some error") 74 | } 75 | d = append(d[len(d)-1:], d[:len(d)-1]...) 76 | return d, nil 77 | } 78 | 79 | func (m *mocChiper) EncryptionType() string { 80 | return "moc" 81 | } 82 | 83 | type encrypter struct { 84 | crypter *cryptex.Cryptex 85 | } 86 | 87 | func (d *encrypter) decrypt(fileName string) error { 88 | container := new(cryptex.Container) 89 | 90 | bin, err := ioutil.ReadFile(fileName) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | if err := json.Unmarshal(bin, container); err != nil { 96 | return err 97 | } 98 | 99 | i, err := d.crypter.Decrypt(container) 100 | if err != nil { 101 | return err 102 | } 103 | plain, err := json.MarshalIndent(i, "", " ") 104 | if err != nil { 105 | return err 106 | } 107 | return ioutil.WriteFile(fileName, plain, 0644) 108 | } 109 | 110 | func (e *encrypter) encrypt(fileName string) error { 111 | var i interface{} 112 | bin, err := ioutil.ReadFile(fileName) 113 | if err != nil { 114 | return err 115 | } 116 | if err := json.Unmarshal(bin, &i); err != nil { 117 | return err 118 | } 119 | 120 | c, err := e.crypter.Encrypt(i) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | chipher, err := json.MarshalIndent(c, "", " ") 126 | if err != nil { 127 | return err 128 | } 129 | return ioutil.WriteFile(fileName, chipher, 0644) 130 | } 131 | 132 | func TestNew(t *testing.T) { 133 | if nil == New(session.New(), "") { 134 | t.Fatalf("not allocated") 135 | } 136 | } 137 | 138 | type editTester struct { 139 | ORIGIN_TEST_DIR string 140 | COPY_TEST_DIR string 141 | KEY_DIR string 142 | privateKey []byte 143 | publicKey []byte 144 | cryptex *cryptex.Cryptex 145 | editor *Editor 146 | encrypter *encrypter 147 | } 148 | 149 | func newEditTester() (*editTester, error) { 150 | ret := &editTester{} 151 | ret.ORIGIN_TEST_DIR = vars.TEST_DATA_DIR_ROOT + "/subcmd/vault/edit/v1" 152 | ret.COPY_TEST_DIR = ret.ORIGIN_TEST_DIR + "/copy" 153 | ret.KEY_DIR = vars.TEST_DATA_DIR_ROOT + "/key" 154 | 155 | if err := ret.generateRsaKey(); err != nil { 156 | return nil, err 157 | } 158 | ret.generateCryptex() 159 | ret.encrypter = &encrypter{ret.cryptex} 160 | 161 | if err := ret.generateTestDir(); err != nil { 162 | return nil, err 163 | } 164 | 165 | if err := ret.generateEditor(); err != nil { 166 | return nil, err 167 | } 168 | 169 | return ret, nil 170 | } 171 | 172 | func (t *editTester) generateRsaKey() error { 173 | var err error 174 | t.privateKey, err = ioutil.ReadFile(t.KEY_DIR + "/private-key.pem") 175 | if err != nil { 176 | return err 177 | } 178 | t.publicKey, err = ioutil.ReadFile(t.KEY_DIR + "/public-key.pem") 179 | if err != nil { 180 | return err 181 | } 182 | return nil 183 | } 184 | 185 | func (t *editTester) generateCryptex() { 186 | t.cryptex = cryptex.New(rsa.New(t.privateKey, t.publicKey)) 187 | } 188 | 189 | func (t *editTester) generateTestDir() error { 190 | if err := exec.Command("mkdir", "-p", t.COPY_TEST_DIR).Run(); err != nil { 191 | return err 192 | } 193 | return nil 194 | } 195 | 196 | func (t *editTester) fin() error { 197 | if err := os.RemoveAll(t.COPY_TEST_DIR); err != nil { 198 | return err 199 | } 200 | return nil 201 | } 202 | 203 | func (t *editTester) generateEditor() error { 204 | if err := exec.Command("go", "build", "-o", fmt.Sprintf("%s/editor", t.COPY_TEST_DIR), "github.com/jobtalk/pnzr/test/dummy_editor").Run(); err != nil { 205 | return err 206 | } 207 | t.editor = &Editor{ 208 | t.cryptex, 209 | fmt.Sprintf("%s/editor", t.COPY_TEST_DIR), 210 | } 211 | return nil 212 | } 213 | 214 | func (t *editTester) generateEncryptedFiles(tests map[string]testInput) error { 215 | infos, err := ioutil.ReadDir(t.ORIGIN_TEST_DIR) 216 | if err != nil { 217 | return err 218 | } 219 | 220 | for _, info := range infos { 221 | if info.IsDir() { 222 | continue 223 | } 224 | src, err := os.Open(t.ORIGIN_TEST_DIR + "/" + info.Name()) 225 | if err != nil { 226 | panic(err) 227 | } 228 | dst, err := os.Create(t.COPY_TEST_DIR + "/" + info.Name()) 229 | if err != nil { 230 | panic(err) 231 | } 232 | if _, err := io.Copy(dst, src); err != nil { 233 | panic(err) 234 | } 235 | 236 | if test, ok := tests[info.Name()]; ok { 237 | if test.encryptsTestFileAtFirst { 238 | err := t.encrypter.encrypt(t.COPY_TEST_DIR + "/" + info.Name()) 239 | if err != nil { 240 | panic(err) 241 | } 242 | } 243 | } 244 | } 245 | return nil 246 | } 247 | 248 | func (tester *editTester) execEdit(t *testing.T, key string, test testInput, done chan<- bool, errCh chan<- error) { 249 | defer func() { 250 | done <- true 251 | close(done) 252 | }() 253 | err := tester.editor.Edit(tester.COPY_TEST_DIR + "/" + key) 254 | if !test.expectsErr && err != nil { 255 | t.Fatalf("should not be error but: %v", err) 256 | return 257 | } 258 | if test.expectsErr && err == nil { 259 | t.Fatalf("should be error") 260 | } 261 | 262 | if test.expectsErr { 263 | return 264 | } 265 | 266 | if test.encryptsTestFileAtFirst { 267 | if err := tester.encrypter.decrypt(tester.COPY_TEST_DIR + "/" + key); err != nil { 268 | errCh <- err 269 | return 270 | } 271 | } 272 | editedBin, err := ioutil.ReadFile(tester.COPY_TEST_DIR + "/" + key) 273 | if err != nil { 274 | errCh <- err 275 | return 276 | } 277 | if !compareJSON([]byte(test.want), editedBin) { 278 | t.Log(test.want) 279 | t.Log(string(editedBin)) 280 | t.Fatalf("not match") 281 | } 282 | return 283 | } 284 | 285 | func sendEditorCommand(errCh chan error, test testInput) { 286 | client := http.Client{} 287 | 288 | if err := ping(); err != nil { 289 | errCh <- err 290 | return 291 | } 292 | 293 | req, err := http.NewRequest("POST", "http://localhost:8080/e", strings.NewReader(test.input)) 294 | if err != nil { 295 | errCh <- err 296 | return 297 | } 298 | 299 | if _, err := client.Do(req); err != nil { 300 | errCh <- err 301 | return 302 | } 303 | if _, err := client.Get("http://localhost:8080/wq"); err != nil { 304 | errCh <- err 305 | return 306 | } 307 | } 308 | 309 | func (tester *editTester) run(t *testing.T, tests map[string]testInput) error { 310 | for key, test := range tests { 311 | done := make(chan bool) 312 | errCh := make(chan error) 313 | go tester.execEdit(t, key, test, done, errCh) 314 | go sendEditorCommand(errCh, test) 315 | 316 | select { 317 | case err := <-errCh: 318 | return err 319 | case <-done: 320 | } 321 | } 322 | 323 | return nil 324 | } 325 | 326 | // testInput struct defines test data. 327 | // 328 | // input field is the input value of the test. 329 | // This test corresponds to the contents edited by the Editor. 330 | // 331 | // want field is the expected value after the test. 332 | // 333 | // encryptsTestFileAtFirst field is a flag 334 | // that controls whether to encrypt at the start of the test. 335 | // 336 | // expectsErr field is a flag 337 | // that controls whether or not to expect errors. 338 | type testInput struct { 339 | input string 340 | want string 341 | encryptsTestFileAtFirst bool 342 | expectsErr bool 343 | } 344 | 345 | func TestEditor_Edit1(t *testing.T) { 346 | tester, err := newEditTester() 347 | if err != nil { 348 | panic(err) 349 | } 350 | 351 | defer func() { 352 | err := tester.fin() 353 | if err != nil { 354 | panic(err) 355 | } 356 | }() 357 | tests := map[string]testInput{ 358 | "0.json": { 359 | `{"foo":"bar", "array": ["foo", "bar", "baz"]}`, 360 | `{"foo":"bar", "array": ["foo", "bar", "baz"]}`, 361 | true, 362 | false, 363 | }, 364 | "1.json": { 365 | `{"foo":"bar"}`, 366 | `{"array": ["foo", "bar", "baz"]}`, 367 | false, 368 | true, 369 | }, 370 | } 371 | 372 | if err := tester.generateEncryptedFiles(tests); err != nil { 373 | panic(err) 374 | } 375 | 376 | if err := tester.run(t, tests); err != nil { 377 | panic(err) 378 | } 379 | } 380 | 381 | func TestEditor_Edit2(t *testing.T) { 382 | tester, err := newEditTester() 383 | if err != nil { 384 | panic(err) 385 | } 386 | defer func() { 387 | err := tester.fin() 388 | if err != nil { 389 | panic(err) 390 | } 391 | }() 392 | 393 | tester.editor = &Editor{ 394 | cryptex.New(newChiper(true)), 395 | fmt.Sprintf("%s/editor", tester.COPY_TEST_DIR), 396 | } 397 | 398 | tests := []struct { 399 | fileName string 400 | }{ 401 | {"hoge"}, 402 | {tester.ORIGIN_TEST_DIR + "/0.json"}, 403 | } 404 | 405 | for _, test := range tests { 406 | err := tester.editor.Edit(test.fileName) 407 | if err == nil { 408 | t.Fatalf("should be error for %v but not:", test.fileName) 409 | } 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /subcmd/vault/encrypt/encrypt.go: -------------------------------------------------------------------------------- 1 | package encrypt 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/credentials" 9 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/ieee0824/getenv" 12 | "github.com/jobtalk/pnzr/subcmd/vault/encrypt/prototype" 13 | "github.com/jobtalk/pnzr/subcmd/vault/encrypt/v1" 14 | "github.com/jobtalk/pnzr/subcmd/vault/encrypt/v1/iface" 15 | "github.com/jobtalk/pnzr/vars" 16 | ) 17 | 18 | type EncryptCommand struct { 19 | sess *session.Session 20 | kmsKeyID *string 21 | file *string 22 | profile *string 23 | region *string 24 | awsAccessKeyID *string 25 | awsSecretKeyID *string 26 | configVersion *string 27 | v1Encrypter v1_api.API 28 | } 29 | 30 | func (e *EncryptCommand) encrypt(keyID string, fileName string) error { 31 | switch *e.configVersion { 32 | case "1.0": 33 | return e.v1Encrypter.Encrypt(keyID, fileName) 34 | case "prototype": 35 | return prototype.Encrypt(e.sess, keyID, fileName) 36 | default: 37 | return fmt.Errorf("unsupport configure version: %v", *e.configVersion) 38 | } 39 | } 40 | 41 | func (e *EncryptCommand) parseArgs(args []string) (helpString string) { 42 | var ( 43 | flagSet = new(flag.FlagSet) 44 | f *string 45 | ) 46 | 47 | buffer := new(bytes.Buffer) 48 | flagSet.SetOutput(buffer) 49 | 50 | e.kmsKeyID = flagSet.String("key_id", getenv.String("KMS_KEY_ID"), "Amazon KMS key ID") 51 | e.profile = flagSet.String("profile", getenv.String("AWS_PROFILE_NAME", "default"), "aws credentials profile name") 52 | e.region = flagSet.String("region", getenv.String("AWS_REGION", "ap-northeast-1"), "aws region") 53 | e.awsAccessKeyID = flagSet.String("aws-access-key-id", getenv.String("AWS_ACCESS_KEY_ID"), "aws access key id") 54 | e.awsSecretKeyID = flagSet.String("aws-secret-key-id", getenv.String("AWS_SECRET_KEY_ID"), "aws secret key id") 55 | e.file = flagSet.String("file", "", "target file") 56 | e.configVersion = flagSet.String("v", vars.CONFIG_VERSION, "config version") 57 | f = flagSet.String("f", "", "target file") 58 | 59 | if err := flagSet.Parse(args); err != nil { 60 | if err == flag.ErrHelp { 61 | return buffer.String() 62 | } 63 | panic(err) 64 | } 65 | 66 | if *f == "" && *e.file == "" && len(flagSet.Args()) != 0 { 67 | targetName := flagSet.Args()[0] 68 | e.file = &targetName 69 | } 70 | 71 | if *e.file == "" { 72 | e.file = f 73 | } 74 | 75 | var awsConfig = aws.Config{} 76 | 77 | if *e.awsAccessKeyID != "" && *e.awsSecretKeyID != "" && *e.profile == "" { 78 | awsConfig.Credentials = credentials.NewStaticCredentials(*e.awsAccessKeyID, *e.awsSecretKeyID, "") 79 | awsConfig.Region = e.region 80 | } 81 | 82 | e.sess = session.Must(session.NewSessionWithOptions(session.Options{ 83 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 84 | SharedConfigState: session.SharedConfigEnable, 85 | Profile: *e.profile, 86 | Config: awsConfig, 87 | })) 88 | 89 | return 90 | } 91 | 92 | func (e *EncryptCommand) Help() string { 93 | return e.parseArgs([]string{"-h"}) 94 | } 95 | 96 | func (e *EncryptCommand) Synopsis() string { 97 | return "Encryption mode of plaintext file." 98 | 99 | } 100 | 101 | func (e *EncryptCommand) Run(args []string) int { 102 | if len(args) == 0 { 103 | fmt.Println(e.Synopsis()) 104 | return 0 105 | } 106 | e.parseArgs(args) 107 | e.v1Encrypter = v1.New(e.sess, *e.kmsKeyID) 108 | 109 | if err := e.encrypt(*e.kmsKeyID, *e.file); err != nil { 110 | panic(err) 111 | } 112 | 113 | return 0 114 | } 115 | -------------------------------------------------------------------------------- /subcmd/vault/encrypt/prototype/prototype.go: -------------------------------------------------------------------------------- 1 | package prototype 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | "github.com/jobtalk/pnzr/lib" 6 | "io/ioutil" 7 | ) 8 | 9 | func Encrypt(sess *session.Session, keyID, fileName string) error { 10 | bin, err := ioutil.ReadFile(fileName) 11 | if err != nil { 12 | return err 13 | } 14 | kms := lib.NewKMS(sess) 15 | _, err = kms.SetKeyID(keyID).Encrypt(bin) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | return ioutil.WriteFile(fileName, []byte(kms.String()), 0644) 21 | } 22 | -------------------------------------------------------------------------------- /subcmd/vault/encrypt/v1/iface/iface.go: -------------------------------------------------------------------------------- 1 | package v1_api 2 | 3 | type API interface { 4 | Encrypt(keyID, fileName string) error 5 | } 6 | -------------------------------------------------------------------------------- /subcmd/vault/encrypt/v1/v1.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | "github.com/ieee0824/cryptex" 8 | "github.com/ieee0824/cryptex/kms" 9 | "io/ioutil" 10 | ) 11 | 12 | type Encrypter struct { 13 | c *cryptex.Cryptex 14 | } 15 | 16 | func New(s *session.Session, keyID string) *Encrypter { 17 | return &Encrypter{ 18 | cryptex.New(kms.New(s).SetKey(keyID)), 19 | } 20 | } 21 | 22 | func (e *Encrypter) Encrypt(keyID, fileName string) error { 23 | var plain map[string]interface{} 24 | bin, err := ioutil.ReadFile(fileName) 25 | if err != nil { 26 | return err 27 | } 28 | if err := json.Unmarshal(bin, &plain); err != nil { 29 | return err 30 | } 31 | 32 | if _, ok := plain["encryption_type"]; ok { 33 | return fmt.Errorf("%s is already encrypted", fileName) 34 | } 35 | 36 | cipher, err := e.c.Encrypt(plain) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | chipherBin, err := json.MarshalIndent(cipher, "", " ") 42 | if err != nil { 43 | return err 44 | } 45 | return ioutil.WriteFile(fileName, chipherBin, 0644) 46 | } 47 | -------------------------------------------------------------------------------- /subcmd/vault/encrypt/v1/v1_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "reflect" 11 | "testing" 12 | 13 | "github.com/ieee0824/cryptex" 14 | "github.com/ieee0824/cryptex/rsa" 15 | "github.com/jobtalk/pnzr/vars" 16 | ) 17 | 18 | type encrypterTester struct { 19 | ORIGIN_TEST_DIR string 20 | COPY_TEST_DIR string 21 | KEY_DIR string 22 | privateKey []byte 23 | publicKey []byte 24 | cryptex *cryptex.Cryptex 25 | encrypter *Encrypter 26 | decrypter *decrypter 27 | } 28 | 29 | func newEncrypterTester() (*encrypterTester, error) { 30 | var ret = new(encrypterTester) 31 | 32 | ret.ORIGIN_TEST_DIR = vars.TEST_DATA_DIR_ROOT + "/subcmd/vault/encrypt/v1" 33 | ret.COPY_TEST_DIR = ret.ORIGIN_TEST_DIR + "/copy" 34 | ret.KEY_DIR = vars.TEST_DATA_DIR_ROOT + "/key" 35 | 36 | if err := ret.generateRsaKey(); err != nil { 37 | return nil, err 38 | } 39 | 40 | ret.generateCryptex() 41 | ret.generateEncrypter() 42 | ret.generateDecrypter() 43 | 44 | if err := ret.generateTestFiles(); err != nil { 45 | return nil, err 46 | } 47 | 48 | return ret, nil 49 | } 50 | 51 | func (t *encrypterTester) generateRsaKey() error { 52 | var err error 53 | t.privateKey, err = ioutil.ReadFile(t.KEY_DIR + "/private-key.pem") 54 | if err != nil { 55 | return err 56 | } 57 | t.publicKey, err = ioutil.ReadFile(t.KEY_DIR + "/public-key.pem") 58 | if err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | func (t *encrypterTester) generateCryptex() { 65 | t.cryptex = cryptex.New(rsa.New(t.privateKey, t.publicKey)) 66 | } 67 | 68 | func (t *encrypterTester) generateEncrypter() { 69 | t.encrypter = &Encrypter{t.cryptex} 70 | } 71 | 72 | func (t *encrypterTester) generateDecrypter() { 73 | t.decrypter = &decrypter{t.cryptex} 74 | } 75 | 76 | func (t *encrypterTester) generateTestFiles() error { 77 | if err := exec.Command("mkdir", "-p", t.COPY_TEST_DIR).Run(); err != nil { 78 | return err 79 | } 80 | infos, err := ioutil.ReadDir(t.ORIGIN_TEST_DIR) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | for _, info := range infos { 86 | if info.IsDir() { 87 | continue 88 | } 89 | src, err := os.Open(t.ORIGIN_TEST_DIR + "/" + info.Name()) 90 | if err != nil { 91 | panic(err) 92 | } 93 | dst, err := os.Create(t.COPY_TEST_DIR + "/" + info.Name()) 94 | if err != nil { 95 | panic(err) 96 | } 97 | if _, err := io.Copy(dst, src); err != nil { 98 | panic(err) 99 | } 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func (t *encrypterTester) fin() error { 106 | if err := os.RemoveAll(t.COPY_TEST_DIR); err != nil { 107 | return err 108 | } 109 | return nil 110 | } 111 | 112 | func (tester *encrypterTester) run(t *testing.T, tests map[string]testInput, f func(t *testing.T, tester *encrypterTester, tests map[string]testInput) error) error { 113 | return f(t, tester, tests) 114 | } 115 | 116 | func encryptTestRun(t *testing.T, tester *encrypterTester, tests map[string]testInput) error { 117 | for key, test := range tests { 118 | err := tester.encrypter.Encrypt("", fmt.Sprintf("%s/%v", tester.COPY_TEST_DIR, key)) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | if !test.expectsErr { 124 | org, err := ioutil.ReadFile(tester.ORIGIN_TEST_DIR + "/" + key) 125 | if err != nil { 126 | return err 127 | } 128 | encrypted, err := ioutil.ReadFile(tester.COPY_TEST_DIR + "/" + key) 129 | if err != nil { 130 | return err 131 | } 132 | if compareJSON(org, encrypted) { 133 | t.Log(string(org)) 134 | t.Log(string(encrypted)) 135 | t.Fatalf("equal") 136 | } 137 | 138 | if err := tester.decrypter.decrypt(tester.COPY_TEST_DIR + "/" + key); err != nil { 139 | return err 140 | } 141 | 142 | decrypted, err := ioutil.ReadFile(tester.COPY_TEST_DIR + "/" + key) 143 | if !test.expectsErr && err != nil { 144 | t.Fatalf("should not be error but: %v", err) 145 | } 146 | if test.expectsErr && err == nil { 147 | t.Fatalf("should be error") 148 | } 149 | if !compareJSON(org, decrypted) { 150 | t.Log(string(org)) 151 | t.Log(string(decrypted)) 152 | t.Fatalf("not match") 153 | } 154 | } 155 | } 156 | return nil 157 | } 158 | 159 | func alreadyEncryptErrorTestRun(t *testing.T, tester *encrypterTester, tests map[string]testInput) error { 160 | for key, _ := range tests { 161 | err := tester.encrypter.Encrypt("", fmt.Sprintf("%s/%v", tester.COPY_TEST_DIR, key)) 162 | if err != nil { 163 | return err 164 | } 165 | err = tester.encrypter.Encrypt("", fmt.Sprintf("%s/%v", tester.COPY_TEST_DIR, key)) 166 | if err == nil { 167 | t.Fatalf("should be error") 168 | } 169 | } 170 | return nil 171 | } 172 | 173 | func compareJSON(a, b []byte) bool { 174 | var ai, bi interface{} 175 | if err := json.Unmarshal(a, &ai); err != nil { 176 | panic(err) 177 | } 178 | 179 | if err := json.Unmarshal(b, &bi); err != nil { 180 | panic(err) 181 | } 182 | 183 | return reflect.DeepEqual(ai, bi) 184 | } 185 | 186 | type decrypter struct { 187 | crypter *cryptex.Cryptex 188 | } 189 | 190 | func (d *decrypter) decrypt(fileName string) error { 191 | container := new(cryptex.Container) 192 | 193 | bin, err := ioutil.ReadFile(fileName) 194 | if err != nil { 195 | return err 196 | } 197 | 198 | if err := json.Unmarshal(bin, container); err != nil { 199 | return err 200 | } 201 | 202 | i, err := d.crypter.Decrypt(container) 203 | if err != nil { 204 | return err 205 | } 206 | plain, err := json.MarshalIndent(i, "", " ") 207 | if err != nil { 208 | return err 209 | } 210 | return ioutil.WriteFile(fileName, plain, 0644) 211 | } 212 | 213 | type testInput struct { 214 | expectsErr bool 215 | } 216 | 217 | func TestEncrypter_Encrypt(t *testing.T) { 218 | tests := map[string]testInput{ 219 | "0.json": { 220 | false, 221 | }, 222 | } 223 | tester, err := newEncrypterTester() 224 | if err != nil { 225 | panic(err) 226 | } 227 | defer func() { 228 | if err := tester.fin(); err != nil { 229 | panic(err) 230 | } 231 | }() 232 | 233 | if err := tester.run(t, tests, encryptTestRun); err != nil { 234 | panic(err) 235 | } 236 | } 237 | 238 | func TestEncrypter_AlreadyEncrypt(t *testing.T) { 239 | tests := map[string]testInput{ 240 | "0.json": { 241 | true, 242 | }, 243 | } 244 | tester, err := newEncrypterTester() 245 | if err != nil { 246 | panic(err) 247 | } 248 | defer func() { 249 | if err := tester.fin(); err != nil { 250 | panic(err) 251 | } 252 | }() 253 | 254 | if err := tester.run(t, tests, alreadyEncryptErrorTestRun); err != nil { 255 | panic(err) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /subcmd/vault/vault.go: -------------------------------------------------------------------------------- 1 | package vault 2 | 3 | import ( 4 | "github.com/jobtalk/pnzr/subcmd/vault/decrypt" 5 | "github.com/jobtalk/pnzr/subcmd/vault/edit" 6 | "github.com/jobtalk/pnzr/subcmd/vault/encrypt" 7 | "github.com/jobtalk/pnzr/subcmd/vault/view" 8 | "github.com/jobtalk/pnzr/vars" 9 | "github.com/mitchellh/cli" 10 | ) 11 | 12 | type VaultCommand struct { 13 | cli *cli.CLI 14 | args []string 15 | } 16 | 17 | func New(args []string) *VaultCommand { 18 | ret := &VaultCommand{ 19 | cli: cli.NewCLI("vault", vars.VERSION), 20 | args: args, 21 | } 22 | 23 | ret.cli.Commands = map[string]cli.CommandFactory{ 24 | "edit": func() (cli.Command, error) { 25 | return &edit.EditCommand{}, nil 26 | }, 27 | "view": func() (cli.Command, error) { 28 | return &view.ViewCommand{}, nil 29 | }, 30 | "encrypt": func() (cli.Command, error) { 31 | return &encrypt.EncryptCommand{}, nil 32 | }, 33 | "decrypt": func() (cli.Command, error) { 34 | return &decrypt.DecryptCommand{}, nil 35 | }, 36 | } 37 | 38 | return ret 39 | } 40 | 41 | func (v *VaultCommand) Run(args []string) int { 42 | v.cli.Args = args 43 | 44 | exitCode, err := v.cli.Run() 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | return exitCode 50 | } 51 | 52 | func (v *VaultCommand) Help() string { 53 | if 2 <= len(v.args) { 54 | v.Run(v.args[1:]) 55 | return "" 56 | } 57 | 58 | return v.Synopsis() 59 | } 60 | 61 | func (v *VaultCommand) Synopsis() string { 62 | return "Option for encrypting configuration file." 63 | } 64 | -------------------------------------------------------------------------------- /subcmd/vault/vault_test.go: -------------------------------------------------------------------------------- 1 | package vault 2 | -------------------------------------------------------------------------------- /subcmd/vault/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "flag" 8 | "fmt" 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/credentials" 11 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 12 | "github.com/aws/aws-sdk-go/aws/session" 13 | "github.com/ieee0824/cryptex" 14 | "github.com/ieee0824/cryptex/kms" 15 | "github.com/ieee0824/getenv" 16 | "github.com/jobtalk/pnzr/lib" 17 | "github.com/jobtalk/pnzr/vars" 18 | "io/ioutil" 19 | "os" 20 | "os/exec" 21 | ) 22 | 23 | type ViewCommand struct { 24 | sess *session.Session 25 | file *string 26 | profile *string 27 | region *string 28 | awsAccessKeyID *string 29 | awsSecretKeyID *string 30 | configVersion *string 31 | } 32 | 33 | func (v *ViewCommand) parseArgs(args []string) (helpString string) { 34 | var ( 35 | flagSet = new(flag.FlagSet) 36 | f *string 37 | ) 38 | 39 | buffer := new(bytes.Buffer) 40 | flagSet.SetOutput(buffer) 41 | 42 | v.profile = flagSet.String("profile", getenv.String("AWS_PROFILE_NAME", "default"), "aws credentials profile name") 43 | v.region = flagSet.String("region", getenv.String("AWS_REGION", "ap-northeast-1"), "aws region") 44 | v.awsAccessKeyID = flagSet.String("aws-access-key-id", getenv.String("AWS_ACCESS_KEY_ID"), "aws access key id") 45 | v.awsSecretKeyID = flagSet.String("aws-secret-key-id", getenv.String("AWS_SECRET_KEY_ID"), "aws secret key id") 46 | v.file = flagSet.String("file", "", "target file") 47 | v.configVersion = flagSet.String("v", vars.CONFIG_VERSION, "config version") 48 | f = flagSet.String("f", "", "target file") 49 | 50 | if err := flagSet.Parse(args); err != nil { 51 | if err == flag.ErrHelp { 52 | return buffer.String() 53 | } 54 | panic(err) 55 | } 56 | 57 | if *f == "" && *v.file == "" && len(flagSet.Args()) != 0 { 58 | targetName := flagSet.Args()[0] 59 | v.file = &targetName 60 | } 61 | 62 | if *v.file == "" { 63 | v.file = f 64 | } 65 | 66 | var awsConfig = aws.Config{} 67 | 68 | if *v.awsAccessKeyID != "" && *v.awsSecretKeyID != "" && *v.profile == "" { 69 | awsConfig.Credentials = credentials.NewStaticCredentials(*v.awsAccessKeyID, *v.awsSecretKeyID, "") 70 | awsConfig.Region = v.region 71 | } 72 | 73 | v.sess = session.Must(session.NewSessionWithOptions(session.Options{ 74 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, 75 | SharedConfigState: session.SharedConfigEnable, 76 | Profile: *v.profile, 77 | Config: awsConfig, 78 | })) 79 | 80 | return 81 | } 82 | 83 | func (v *ViewCommand) decryptTemporary(fileName string) ([]byte, error) { 84 | bin, err := ioutil.ReadFile(fileName) 85 | if err != nil { 86 | return nil, err 87 | } 88 | kms := lib.NewKMSFromBinary(bin, v.sess) 89 | if kms == nil { 90 | return nil, errors.New(fmt.Sprintf("%v form is illegal", fileName)) 91 | } 92 | plainText, err := kms.Decrypt() 93 | if err != nil { 94 | return nil, err 95 | } 96 | return plainText, nil 97 | } 98 | 99 | func (v *ViewCommand) Help() string { 100 | return v.parseArgs([]string{"-h"}) 101 | } 102 | 103 | func (v *ViewCommand) Synopsis() string { 104 | return "Viewer mode of encrypted file." 105 | } 106 | 107 | func (v *ViewCommand) Run(args []string) int { 108 | if len(args) == 0 { 109 | fmt.Println(v.Synopsis()) 110 | return 0 111 | } 112 | v.parseArgs(args) 113 | 114 | switch *v.configVersion { 115 | case "1.0": 116 | var container = &cryptex.Container{} 117 | bin, err := ioutil.ReadFile(*v.file) 118 | if err != nil { 119 | panic(err) 120 | } 121 | if err := json.Unmarshal(bin, container); err != nil { 122 | panic(err) 123 | } 124 | if err := cryptex.New(kms.New(v.sess)).View(container); err != nil { 125 | panic(err) 126 | } 127 | case "prototype": 128 | plain, err := v.decryptTemporary(*v.file) 129 | if err != nil { 130 | panic(err) 131 | } 132 | 133 | cmd := exec.Command("less") 134 | cmd.Stdin = bytes.NewReader(plain) 135 | cmd.Stdout = os.Stdout 136 | cmd.Stderr = os.Stderr 137 | 138 | if err := cmd.Run(); err != nil { 139 | panic(err) 140 | } 141 | default: 142 | panic(fmt.Errorf("unsupport version: %v", *v.configVersion)) 143 | } 144 | 145 | return 0 146 | } 147 | -------------------------------------------------------------------------------- /test/dummy_editor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/ieee0824/getenv" 7 | "io/ioutil" 8 | "os" 9 | "time" 10 | ) 11 | 12 | var ( 13 | buffer []byte 14 | killer = make(chan bool) 15 | ) 16 | 17 | func open(ctx *gin.Context) { 18 | } 19 | 20 | func wq(ctx *gin.Context) { 21 | if err := ioutil.WriteFile(os.Args[1], buffer, 0644); err != nil { 22 | panic(err) 23 | } 24 | fmt.Fprint(ctx.Writer, string(buffer)) 25 | killer <- true 26 | } 27 | 28 | func root(ctx *gin.Context) { 29 | fmt.Fprint(ctx.Writer, string(buffer)) 30 | } 31 | 32 | func edit(ctx *gin.Context) { 33 | body, err := ioutil.ReadAll(ctx.Request.Body) 34 | if err != nil { 35 | panic(err) 36 | } 37 | buffer = body 38 | } 39 | 40 | func main() { 41 | if getenv.Bool("FAULT") { 42 | os.Exit(1) 43 | } 44 | go func() { 45 | <-killer 46 | time.Sleep(500 * time.Millisecond) 47 | os.Exit(0) 48 | }() 49 | var err error 50 | if len(os.Args) != 2 { 51 | panic(fmt.Errorf("file name not set")) 52 | } 53 | 54 | buffer, err = ioutil.ReadFile(os.Args[1]) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | router := gin.Default() 60 | 61 | router.GET("/", root) 62 | router.POST("/", root) 63 | router.POST("/o", open) 64 | router.POST("/wq", wq) 65 | router.GET("/wq", wq) 66 | router.POST("/e", edit) 67 | 68 | if err := router.Run(fmt.Sprintf(":%d", getenv.Int("EDITOR_PORT", 8080))); err != nil { 69 | panic(err) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/key/private-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKQIBAAKCAgEAtjhETgOmFJspjpfBIe2NJWGLWXBscEwEBSQrS1ub1yP1chRX 3 | stlY0WpQjt5bopAN56vCti4L245ne7IJ2pwoIyvCgeBKb4XjEGqDe7+wdzalTDF7 4 | HI9/mHj1Swegm1tc5sGmn7NOOzmcNRO7BJyKX34cNaCJrgzYwHRMXvImSvOH4hj6 5 | FDccdCygCU1fRAgb/tv5jRO1EDzKFxqR5nx/EB79QUBqBKd+rIxgxcq5pepRkg6x 6 | DtxpsROleM/AdOFKG+YFFRX2dpZS5gN7S3BqhPla2k+ClLINtA1Zq15kqKPCBZbD 7 | tNuzKCTfVUviPi8ag5yomiReCgG2r8AsCtkW9NW8jNEIiVyQW51TjVcZqwLErn25 8 | OgwHTeo6YhhaezMOgwhn7rrdEgIm0zepIBJ0G7fEv/HY1yLxiSIV6yH+3Szy6zsR 9 | HV4CzUccwcQCM1jGrPmw6yL5H2QTYoKYgKjs4WwiTPYcQqHDuhuOTu9TYYNBZneb 10 | cYyppVf1BQPlTj/pQk1FLpwLKbNb8T/InBu69tNaFnSksLkPigzNDCvdgtjg2ZQm 11 | NAQWYONyB784RmcAp3J5MusnfUJKFYKZ9c5znjpUKPaAggpyvlINR+rnS++oDoab 12 | ppizaLpWJJGoSgGmdAUVtQ/3/Z0muuOiJBy/wy0f2SAHll2boSvFExwaN6cCAwEA 13 | AQKCAgBn1wq9toCXb5CwDLTnw7rSmZLSpil1mSwVTfZKHGtjn79IgmUzq35c4IwF 14 | zptDuXnjEtOEBBuHw9AfAmdxJ9oLqO1QG+PvR8oM2dYR2DwUw5RRJkV1EiVxW0TV 15 | vAP/ZII9kg+FJKYpQ/xLlvHVf+uu3Wvv94teHz4J9nxrAu1lewBtjTQ9/uX9Ik0U 16 | 3/2xBDoH841zos5k8W9s2RxZz4qNQkuqOq3v9w9N/zVKoetHrI9Nf4fQ4zRrz7lV 17 | bnbLZHNaEKvGx1xLWgDlyQgjBm39Vyg6DmNon0sF1zHJYaJVI/T2QGNIloazEsge 18 | kH7eVvNyGv1N0fmxFCbqYsX2bJ4ScoigmHT/ZTaxAg9ZwfC7SKV9+6+otcR71YWV 19 | HIZhntxJyLboS+L30VrWlpvjTtvjLPx8fTFGIa5WaW6ZOH3DFal/aaloW78Zxi8h 20 | tXHItZzfj4l67CxrXZLyvM5AvaxpTH8VPLzBrQAhRqeHP9BeKxqAUWmAWhKMABQ8 21 | mSGhtyACrfXwfAXs1pStoITmUSL080mmLm5znuu6/eie9El+s/sxqRP7t5xC71lr 22 | ADJ25FCkFwqNy4QjZiu5ZUSTkaowfvfV6+bnieHwrCkUVfq7HyIiRxJj5XuG9ies 23 | Obby0otTdZZHLJF9hYavvX+MVzijiY8437tjSgwKTVG49ELT+QKCAQEA8DTso6Hx 24 | yGiCOOGHcQ5oJgmJPtCkAUGYHzNERETD/1G888QVUbGQsWDQ1Z0Z0JsgsCYaaXSZ 25 | wF5I0gwFczGp5kZ3WfGWzCstYA0mxX7YEe6+jcktJnccoGyk/iTFTBORlDpin68k 26 | antl+Y86/3voTs8f4E9Xi+zULh7vfV7qUIHYc2S9vaLq4oDchv6wBXvkI2tu5n+A 27 | 2SSPfoRN+BvvqOjVk912xBcKcr3gRA8FykqaNeU2aPrFQNC8tAQ86P7+7UtolXSi 28 | b5Te5lfyZsyERIS5QQZKHPj1tU1j8+7RgUHJnCjOqRKHXQ2GJTN1ywYi1v6Em3Nz 29 | Sy33quwTD1G5WwKCAQEAwjNTlwbOdJpqf/NZxRjmflLeVVUeoaX4E8zzWz25okMb 30 | P9qQGFaaLNEBKOkLupx19i1PEFPSGn7Z5+/w6r0Uw2BBi3LEqGzW7fwa0MFLQTY6 31 | tfSgLYzn7azIsS97wI/rZW5Q5dpGdg8AXuMv4YEh8vIeXl0qFHWDv+/561rkVLsk 32 | 6EmrkLUGa1eLw9By7lY6M9h2fT6HOkJxx+no+ciPVPGDVnhKj6C57JlLnVCJIrxz 33 | fwlDbtMTD4Luwk0xyjJC7wOFq8NZMaNPTugoYn0w9ORiWVq/XL4ShbY9lk58nGZ2 34 | KFfD8BnZ7GppOoGPSHUw+YaXNrqSHvTVRkgQXvVApQKCAQEAuSqTILC+CZdGi0KP 35 | ff91GZ1QRzPeuXkvIx4KCfZYgIEp/1+wb4FJ4PKZCZJJTB3JXQCCa7iOrVQBHqpV 36 | qS8noXPAnX02mbzPBbQdpzDYrzozemkSfQxk7OXTanY+AR80oedAD4ibsdneCjr3 37 | Z+rEcbwI/okrG5o9FjCSvTplthniGVIXUhA8sM5VMVszpYaTnxo6K0UutjUQyJ6E 38 | 3DXs0XBq88lMvGxmiX0FXHGYLRDFP7ap8tMdK5G9Zae+rH2lVpdCz7NRZGKGUVqK 39 | aeROMeN+Sznl99+SyWgXHPuEVPe5yr9KT9sQrhK2zgaVTyQzxzf8UTj2L4YH3aKO 40 | Lsj48QKCAQBUm9oaXavLX49w6Yw99AhQFziowuoSkWXQ8qVAJldfW49etZG8GNcZ 41 | GYJA2a/zgMFgHQNqYKqUQpoblA1JqFGIgcJsx/boTmshyonAU8a+xaq0pby3u8E8 42 | auARWle3YLy1N2fljMDQJN8LeD3fKMyqtVQH1t1DhqN9F0eyg6ohRFvjHF20MTp3 43 | v++zw3ifYFABUJjnHjv6ZQJ/j+iiEwBniMWpAcWyZ9s5OtyX0IO+743+VgHcIfgW 44 | 9PujCdDcgCNrUQSG/hg5KSafgUQ/DL4so+g5d/ERrfoR6YFRoCZoWbe3Ae3rCMhK 45 | WV45UEMl1XEd9cah4I1TEWZuNW10gbPFAoIBAQCIStV1Cgzp/SrixtnIBXaG4lEA 46 | 9UQEjCq2x/4U5E9aFKVnSC6B0CTBUZhjg+kbDI0PpXKdiyVWGLYaOv/8DxwRlUWo 47 | E89+XChFwj7iHlRvREZG4fCZXmsNquRxr4fyzNBIEuabwtJgrlNx57V8Ckf9z24J 48 | wl6E1FLDz7xw1255vgzuEuV1CXnX6td0XmDj4OvLNTfdsZB5hI5cQoGSLHqaAOHh 49 | m35Td6z/AQgCbHmtdmTYlpCSCGaFIjxllyreeHUfwx+KzLku6uY9xwuWf23tHg9j 50 | piVvsAzjgO1dcClmYmORMNNbl8Vnx5PVVuD3k5YHUd1OSgagqJZ53axLVT/g 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /test/key/public-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtjhETgOmFJspjpfBIe2N 3 | JWGLWXBscEwEBSQrS1ub1yP1chRXstlY0WpQjt5bopAN56vCti4L245ne7IJ2pwo 4 | IyvCgeBKb4XjEGqDe7+wdzalTDF7HI9/mHj1Swegm1tc5sGmn7NOOzmcNRO7BJyK 5 | X34cNaCJrgzYwHRMXvImSvOH4hj6FDccdCygCU1fRAgb/tv5jRO1EDzKFxqR5nx/ 6 | EB79QUBqBKd+rIxgxcq5pepRkg6xDtxpsROleM/AdOFKG+YFFRX2dpZS5gN7S3Bq 7 | hPla2k+ClLINtA1Zq15kqKPCBZbDtNuzKCTfVUviPi8ag5yomiReCgG2r8AsCtkW 8 | 9NW8jNEIiVyQW51TjVcZqwLErn25OgwHTeo6YhhaezMOgwhn7rrdEgIm0zepIBJ0 9 | G7fEv/HY1yLxiSIV6yH+3Szy6zsRHV4CzUccwcQCM1jGrPmw6yL5H2QTYoKYgKjs 10 | 4WwiTPYcQqHDuhuOTu9TYYNBZnebcYyppVf1BQPlTj/pQk1FLpwLKbNb8T/InBu6 11 | 9tNaFnSksLkPigzNDCvdgtjg2ZQmNAQWYONyB784RmcAp3J5MusnfUJKFYKZ9c5z 12 | njpUKPaAggpyvlINR+rnS++oDoabppizaLpWJJGoSgGmdAUVtQ/3/Z0muuOiJBy/ 13 | wy0f2SAHll2boSvFExwaN6cCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /test/lib/setting/0.json: -------------------------------------------------------------------------------- 1 | hoge 2 | -------------------------------------------------------------------------------- /test/lib/setting/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "hoge": "huga" 3 | } 4 | -------------------------------------------------------------------------------- /test/lib/setting/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 1 3 | } 4 | -------------------------------------------------------------------------------- /test/lib/setting/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 1.0 3 | } 4 | -------------------------------------------------------------------------------- /test/lib/setting/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": 2 3 | } 4 | -------------------------------------------------------------------------------- /test/lib/setting/5.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /test/subcmd/deploy/mergeParams/mergeUseDotFile/0.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/mergeParams/mergeUseDotFile/0.env -------------------------------------------------------------------------------- /test/subcmd/deploy/mergeParams/mergeUseDotFile/1.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/mergeParams/mergeUseDotFile/1.env -------------------------------------------------------------------------------- /test/subcmd/deploy/mergeParams/mergeUseDotFile/2.env: -------------------------------------------------------------------------------- 1 | KMS_KEY_ID=secondary-id -------------------------------------------------------------------------------- /test/subcmd/deploy/mergeParams/mergeUseDotFile/3.env: -------------------------------------------------------------------------------- 1 | KMS_KEY_ID=secondary-id -------------------------------------------------------------------------------- /test/subcmd/deploy/mergeParams/mergeUseDotFile/4.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/mergeParams/mergeUseDotFile/4.env -------------------------------------------------------------------------------- /test/subcmd/deploy/mergeParams/mergeUseDotFile/5.env: -------------------------------------------------------------------------------- 1 | AWS_PROFILE_NAME=secondary-profile -------------------------------------------------------------------------------- /test/subcmd/deploy/mergeParams/mergeUseDotFile/6.env: -------------------------------------------------------------------------------- 1 | AWS_PROFILE_NAME=secondary-profile -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/0.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/0.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/1.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/1.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/2.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/2.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/3.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/3.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/4.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/4.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/5.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/5.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/6.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/6.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/7.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/7.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/8.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/8.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/0/9.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/0/9.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/1/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/1/.keep -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/0.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/0.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/1.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/1.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/2.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/2.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/3.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/3.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/4.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/4.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/5.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/5.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/6.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/6.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/7.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/7.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/8.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/8.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/9.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/9.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/a.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/a.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/b.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/b.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/c.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/c.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/d.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/d.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/e.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/e.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/f.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/f.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/g.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/g.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/h.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/h.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/i.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/i.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/j.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/j.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/k.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/k.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/l.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/l.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/m.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/m.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/n.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/n.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/o.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/o.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/p.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/p.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/q.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/q.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/r.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/r.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/s.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/s.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/t.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/t.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/u.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/u.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/v.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/v.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/w.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/w.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/x.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/x.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/y.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/y.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/2/z.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/2/z.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/0.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/0.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/1.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/1.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/2.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/2.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/3.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/3.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/4.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/4.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/5.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/5.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/6.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/6.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/7.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/7.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/8.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/8.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/9.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/9.json -------------------------------------------------------------------------------- /test/subcmd/deploy/testFileList/3/json.hoge: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobtalk/pnzr/fe06d46a1a6568677736af652eba4f2be8dc50b2/test/subcmd/deploy/testFileList/3/json.hoge -------------------------------------------------------------------------------- /test/subcmd/vault/decrypt/v1/.gitignore: -------------------------------------------------------------------------------- 1 | copy/ 2 | -------------------------------------------------------------------------------- /test/subcmd/vault/decrypt/v1/0.json: -------------------------------------------------------------------------------- 1 | { 2 | "hoge": 1 3 | } -------------------------------------------------------------------------------- /test/subcmd/vault/decrypt/v1/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "hoge": 1 3 | } -------------------------------------------------------------------------------- /test/subcmd/vault/edit/v1/0.json: -------------------------------------------------------------------------------- 1 | { 2 | "hoge": "huga" 3 | } 4 | -------------------------------------------------------------------------------- /test/subcmd/vault/edit/v1/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "array": ["foo", "bar", "baz"] 3 | } -------------------------------------------------------------------------------- /test/subcmd/vault/encrypt/v1/0.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar", 3 | "map": [ 4 | "hoge", "huga" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /vars/vars.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | import "github.com/ieee0824/getenv" 4 | 5 | const ( 6 | CONFIG_VERSION = "1.0" 7 | ) 8 | 9 | var ( 10 | VERSION string 11 | BUILD_DATE string 12 | BUILD_OS string 13 | ) 14 | 15 | var ( 16 | PROJECT_ROOT = getenv.String("GOPATH") + "/src/github.com/jobtalk/pnzr" 17 | TEST_DATA_DIR_ROOT = PROJECT_ROOT + "/test" 18 | ) 19 | --------------------------------------------------------------------------------