├── .gitignore ├── BUILD.bazel ├── LICENSE ├── WORKSPACE ├── go_checksum ├── BUILD └── main.go ├── go_pgp ├── BUILD └── main.go ├── readme.md ├── rules_terraform ├── BUILD └── terraform.bzl └── test ├── BUILD └── terraform.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # BAZEL 2 | ####### 3 | # Ignore backup files. 4 | *~ 5 | # Ignore Vim swap files. 6 | .*.swp 7 | # Ignore files generated by IDEs. 8 | /.classpath 9 | /.factorypath 10 | /.idea/ 11 | /.ijwb/ 12 | /.project 13 | /.settings 14 | /.vscode/ 15 | /bazel.iml 16 | # Ignore all bazel-* symlinks. There is no full list since this can change 17 | # based on the name of the directory bazel is cloned into. 18 | /bazel-* 19 | # Ignore outputs generated during Bazel bootstrapping. 20 | /output/ 21 | # Ignore jekyll build output. 22 | /production 23 | /.sass-cache 24 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:def.bzl", "gazelle") 2 | 3 | # gazelle:prefix github.com/mitchelldavis 4 | gazelle(name = "gazelle") 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "io_bazel_rules_terraform") 2 | 3 | load("//rules_terraform:terraform.bzl", "terraform_register_toolchains") 4 | 5 | terraform_register_toolchains() 6 | 7 | # For the pgp tool used to verify downloaded hashicorp tools. 8 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 9 | http_archive( 10 | name = "io_bazel_rules_go", 11 | urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.16.5/rules_go-0.16.5.tar.gz"], 12 | sha256 = "7be7dc01f1e0afdba6c8eb2b43d2fa01c743be1b9273ab1eaf6c233df078d705", 13 | ) 14 | http_archive( 15 | name = "bazel_gazelle", 16 | urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.16.0/bazel-gazelle-0.16.0.tar.gz"], 17 | sha256 = "7949fc6cc17b5b191103e97481cf8889217263acf52e00b560683413af204fcb", 18 | ) 19 | load("@bazel_gazelle//:deps.bzl", "go_repository") 20 | 21 | go_repository( 22 | name = "org_golang_x_crypto", 23 | commit = "505ab145d0a99da450461ae2c1a9f6cd10d1f447", 24 | importpath = "golang.org/x/crypto", 25 | ) 26 | 27 | load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") 28 | go_rules_dependencies() 29 | go_register_toolchains() 30 | 31 | load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") 32 | gazelle_dependencies() 33 | -------------------------------------------------------------------------------- /go_checksum/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 4 | 5 | go_library( 6 | name = "go_default_library", 7 | srcs = ["main.go"], 8 | importpath = "github.com/mitchelldavis/go_checksum", 9 | #deps = [ 10 | # "@org_golang_x_crypto//openpgp:go_default_library", 11 | # "@org_golang_x_crypto//openpgp/armor:go_default_library", 12 | # "@org_golang_x_crypto//openpgp/packet:go_default_library", 13 | #], 14 | ) 15 | 16 | go_binary( 17 | name = "go_checksum", 18 | embed = [":go_default_library"], 19 | ) 20 | -------------------------------------------------------------------------------- /go_checksum/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "log" 7 | "os" 8 | "io" 9 | "strings" 10 | "path/filepath" 11 | "crypto/sha256" 12 | "encoding/hex" 13 | ) 14 | 15 | func main() { 16 | checksumPathPtr := flag.String("shasum", "", "The path to the shasum file") 17 | targetPathPtr := flag.String("target", "", "The path to the file to check") 18 | 19 | flag.Parse() 20 | 21 | filename := filepath.Base(*targetPathPtr) 22 | verifyFile(checksumPathPtr, targetPathPtr, &filename) 23 | } 24 | 25 | func verifyFile(checksumPathPtr *string, targetPathPtr *string, targetBasePtr *string) { 26 | checksums := make(map[string]string) 27 | 28 | checksumFile, err := os.Open(*checksumPathPtr) 29 | if err != nil { 30 | log.Fatalf("Error opening checksum file: %s", err) 31 | } 32 | defer checksumFile.Close() 33 | 34 | targetFile, err := os.Open(*targetPathPtr) 35 | if err != nil { 36 | log.Fatalf("Error opening target file: %s", err) 37 | } 38 | defer targetFile.Close() 39 | 40 | // parse out the checksum target 41 | scanner := bufio.NewScanner(checksumFile) 42 | for scanner.Scan() { 43 | kv := strings.Fields(scanner.Text()) 44 | checksums[kv[1]] = kv[0] 45 | } 46 | 47 | // Generate Hash 48 | targetFile_hash := sha256.New() 49 | if _, err := io.Copy(targetFile_hash, targetFile); err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | // compare 54 | var checksumFile_checksum string 55 | var targetFile_checksum = hex.EncodeToString(targetFile_hash.Sum(nil)) 56 | var key_exists bool 57 | if checksumFile_checksum, key_exists = checksums[*targetBasePtr]; !key_exists { 58 | log.Fatalf("The Checksum file does not contain a checksum for %s", *targetBasePtr) 59 | } 60 | if checksumFile_checksum != targetFile_checksum { 61 | log.Fatalf("The checksums do not match: %s != %s", checksumFile_checksum, targetFile_checksum) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /go_pgp/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 4 | 5 | go_library( 6 | name = "go_default_library", 7 | srcs = ["main.go"], 8 | importpath = "github.com/mitchelldavis/go_pgp", 9 | deps = [ 10 | "@org_golang_x_crypto//openpgp:go_default_library", 11 | "@org_golang_x_crypto//openpgp/armor:go_default_library", 12 | "@org_golang_x_crypto//openpgp/packet:go_default_library", 13 | ], 14 | ) 15 | 16 | go_binary( 17 | name = "go_pgp", 18 | embed = [":go_default_library"], 19 | ) 20 | -------------------------------------------------------------------------------- /go_pgp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "io" 6 | "log" 7 | "os" 8 | 9 | "golang.org/x/crypto/openpgp" 10 | "golang.org/x/crypto/openpgp/armor" 11 | "golang.org/x/crypto/openpgp/packet" 12 | ) 13 | 14 | func main() { 15 | keyPathPtr := flag.String("key", "", "The path to the public key") 16 | sigPathPtr := flag.String("sig", "", "The path to the signature file") 17 | targetPathPtr := flag.String("target", "", "The path to the file to check") 18 | 19 | flag.Parse() 20 | 21 | verifyFile(keyPathPtr, sigPathPtr, targetPathPtr) 22 | } 23 | 24 | func decodePublicKey(filename string) *packet.PublicKey { 25 | 26 | // open ascii armored public key 27 | in, err := os.Open(filename) 28 | if err != nil { 29 | log.Fatalf("Error opening public key file: %s", err) 30 | } 31 | defer in.Close() 32 | 33 | block, err := armor.Decode(in) 34 | if err != nil { 35 | log.Fatalf("Error decoding public key OpenPGP Armor: %s", err) 36 | } 37 | 38 | if block.Type != openpgp.PublicKeyType { 39 | log.Fatal("Error decoding private key: Invalid private key file") 40 | } 41 | 42 | reader := packet.NewReader(block.Body) 43 | pkt, err := reader.Next() 44 | if err != nil { 45 | log.Fatalf("Error reading private key: %s", err) 46 | } 47 | 48 | key, ok := pkt.(*packet.PublicKey) 49 | if !ok { 50 | log.Fatal("Error parsing public key: Invalid public key") 51 | } 52 | return key 53 | } 54 | 55 | func decodeSignature(filename string) *packet.Signature { 56 | 57 | // open ascii armored signature 58 | in, err := os.Open(filename) 59 | if err != nil { 60 | log.Fatalf("Error opening signature file: %s", err) 61 | } 62 | defer in.Close() 63 | 64 | reader := packet.NewReader(in) 65 | pkt, err := reader.Next() 66 | if err != nil { 67 | log.Fatal("Error reading signature") 68 | } 69 | 70 | sig, ok := pkt.(*packet.Signature) 71 | if !ok { 72 | log.Fatal("Error parsing signature: Invalid Signature") 73 | } 74 | return sig 75 | } 76 | 77 | func verifyFile(keyPathFile *string, sigPathFile *string, targetPathFile *string) { 78 | pubKey := decodePublicKey(*keyPathFile) 79 | sig := decodeSignature(*sigPathFile) 80 | 81 | target, err := os.Open(*targetPathFile) 82 | if err != nil { 83 | log.Fatalf("Error opening signature file: %s", err) 84 | } 85 | defer target.Close() 86 | hash := sig.Hash.New() 87 | io.Copy(hash, target) 88 | 89 | sigerr := pubKey.VerifySignature(hash, sig) 90 | if sigerr != nil { 91 | log.Fatalf("Error verifying input: %s", sigerr) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Rules_Terraform 2 | =============== 3 | 4 | [Bazel](https://bazel.build/) rules for using [Hashicorp](https://www.hashicorp.com/)'s [Terraform](https://www.terraform.io/) within your bazel build. 5 | 6 | License 7 | ======= 8 | 9 | This is free and unencumbered software released into the public domain. 10 | 11 | Anyone is free to copy, modify, publish, use, compile, sell, or 12 | distribute this software, either in source code form or as a compiled 13 | binary, for any purpose, commercial or non-commercial, and by any 14 | means. 15 | 16 | In jurisdictions that recognize copyright laws, the author or authors 17 | of this software dedicate any and all copyright interest in the 18 | software to the public domain. We make this dedication for the benefit 19 | of the public at large and to the detriment of our heirs and 20 | successors. We intend this dedication to be an overt act of 21 | relinquishment in perpetuity of all present and future rights to this 22 | software under copyright law. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 28 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 29 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | For more information, please refer to 33 | -------------------------------------------------------------------------------- /rules_terraform/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | load("//rules_terraform:terraform.bzl", "setup_terraform_toolchains") 3 | 4 | setup_terraform_toolchains() 5 | -------------------------------------------------------------------------------- /rules_terraform/terraform.bzl: -------------------------------------------------------------------------------- 1 | #toolchain_type(name = "toolchain_type") 2 | 3 | terraform_sums_url_template = "https://releases.hashicorp.com/terraform/{0}/terraform_{0}_SHA256SUMS" 4 | terraform_sums_sig_url_template = "https://releases.hashicorp.com/terraform/{0}/terraform_{0}_SHA256SUMS" 5 | terraform_url_template = "https://releases.hashicorp.com/terraform/{0}/terraform_{0}_{1}_{2}.zip" 6 | 7 | toolchains = { 8 | "terraform_linux": { 9 | "name": "terraform_linux", 10 | "exec_compatible_with": [ 11 | "@bazel_tools//platforms:linux", 12 | "@bazel_tools//platforms:x86_64", 13 | ], 14 | "target_compatible_with": [ 15 | "@bazel_tools//platforms:linux", 16 | "@bazel_tools//platforms:x86_64", 17 | ], 18 | "toolchain":":terraform_linux", 19 | "toolchain_type":"@io_bazel_rules_terraform//:toolchain_type", 20 | "host": "linux", 21 | "arch": "amd64", 22 | "url":"https://releases.hashicorp.com/terraform/0.11.11/terraform_0.11.11_linux_amd64.zip", 23 | "sha256":"t94504f4a67bad612b5c8e3a4b7ce6ca2772b3c1559630dfd71e9c519e3d6149c" 24 | }, 25 | "terraform_osx": { 26 | "name": "terraform_osx", 27 | "exec_compatible_with": [ 28 | "@bazel_tools//platforms:osx", 29 | "@bazel_tools//platforms:x86_64", 30 | ], 31 | "target_compatible_with": [ 32 | "@bazel_tools//platforms:osx", 33 | "@bazel_tools//platforms:x86_64", 34 | ], 35 | "toolchain":":terraform_osx", 36 | "toolchain_type":"@io_bazel_rules_terraform//:toolchain_type", 37 | "host": "darwin", 38 | "arch": "amd64", 39 | "url":"https://releases.hashicorp.com/terraform/0.11.11/terraform_0.11.11_darwin_amd64.zip", 40 | "sha256":"6b6e8253b678554c67d717c42209fd857bfe64a1461763c05d3d1d85c6f618d3" 41 | } 42 | } 43 | 44 | TerraformInfo = provider( 45 | doc = "Information on how to call terraform", 46 | fields = [ 47 | "executable", 48 | "url", 49 | "sha256" 50 | ], 51 | ) 52 | 53 | def _terraform_toolchain_impl(ctx): 54 | toolchain_info = platform_common.ToolchainInfo( 55 | terraforminfo = TerraformInfo( 56 | executable = ctx.attr.executable, 57 | url = ctx.attr.url, 58 | sha256 = ctx.attr.sha256 59 | ), 60 | ) 61 | return [toolchain_info] 62 | 63 | terraform_toolchain = rule( 64 | implementation = _terraform_toolchain_impl, 65 | attrs = { 66 | "executable": attr.string(), 67 | "url": attr.string(), 68 | "sha256": attr.string() 69 | } 70 | ) 71 | 72 | def setup_terraform_toolchains(): 73 | for name, toolchain in toolchains.items(): 74 | terraform_toolchain( 75 | name = toolchain["name"], 76 | executable = "", 77 | url = toolchain["url"], 78 | sha256 = toolchain["sha256"]) 79 | native.toolchain( 80 | name = "{0}_toolchain".format(toolchain["name"]), 81 | exec_compatible_with = toolchain["exec_compatible_with"], 82 | target_compatible_with = toolchain["target_compatible_with"], 83 | toolchain = toolchain["toolchain"], 84 | toolchain_type = toolchain["toolchain_type"] 85 | ) 86 | 87 | def _download_terraform_impl(ctx): 88 | if ctx.os.name == "linux": 89 | toolchain_name = "terraform_linux" 90 | elif ctx.os.name == "mac os x": 91 | toolchain_name = "terraform_osx" 92 | else: 93 | fail("Unsupported operating system: " + ctx.os.name) 94 | 95 | toolchain = toolchains[toolchain_name] 96 | 97 | # Download The SHA256SUM File 98 | # Download The SHA256SUM.sig File 99 | # Verify the SHA256SUM File Signature. 100 | # Extract the SHA256SUM for Terraform. 101 | # Download the Terraform Executable 102 | 103 | ctx.file("BUILD.bazel", 104 | """ 105 | filegroup( 106 | name = "terraform_executable", 107 | srcs = ["terraform/terraform"], 108 | visibility = ["//visibility:public"] 109 | ) 110 | """, 111 | executable=False 112 | ) 113 | ctx.download_and_extract( 114 | url = toolchain["url"], 115 | sha256 = toolchain["sha256"], 116 | output = "terraform", 117 | type = "zip", 118 | ) 119 | 120 | download_terraform = repository_rule( 121 | implementation = _download_terraform_impl, 122 | attrs = { 123 | "version": attr.string( 124 | mandatory = True 125 | ) 126 | } 127 | ) 128 | 129 | # TODO: Need to define the plugin provider system 130 | #def _terraform_provider_plugin_impl(ctx): 131 | # pass 132 | # 133 | #terraform_provider_plugin = repository_rule( 134 | # implementation = _terraform_provider_plugin_impl, 135 | # attrs = { 136 | # "name": attr.string( 137 | # mandatory = True 138 | # ), 139 | # "version": attr.string( 140 | # mandatory = True 141 | # ) 142 | # } 143 | #) 144 | 145 | def _terraform_plan(ctx): 146 | deps = depset(ctx.files.srcs) 147 | ctx.actions.run( 148 | executable = ctx.executable._exec, 149 | inputs = deps.to_list(), 150 | outputs = [ctx.outputs.out], 151 | mnemonic = "TerraformInitialize", 152 | arguments = [ 153 | "plan", 154 | "-out={0}".format(ctx.outputs.out.path), deps.to_list()[0].dirname 155 | ] 156 | ) 157 | 158 | terraform_plan = rule( 159 | implementation = _terraform_plan, 160 | attrs = { 161 | "srcs": attr.label_list( 162 | mandatory = True, 163 | allow_files = True 164 | ), 165 | "_exec": attr.label( 166 | default = Label("@terraform_exec//:terraform_executable"), 167 | allow_files = True, 168 | executable = True, 169 | cfg = "host" 170 | ) 171 | }, 172 | toolchains = ["@io_bazel_rules_terraform//:toolchain_type"], 173 | outputs = {"out": "%{name}.out"}, 174 | ) 175 | 176 | def terraform_register_toolchains(version="0.11.11"): 177 | if "download_terraform" not in native.existing_rules(): 178 | download_terraform( 179 | name = "terraform_exec", 180 | version = version 181 | ) 182 | 183 | for name, toolchain in toolchains.items(): 184 | native.register_toolchains( 185 | "//rules_terraform:{0}_toolchain".format(toolchain["name"]), 186 | ) 187 | -------------------------------------------------------------------------------- /test/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | load("//rules_terraform:terraform.bzl", "terraform_plan") 3 | 4 | terraform_plan( 5 | name = "test", 6 | srcs = glob(["*.tf"]) 7 | ) 8 | -------------------------------------------------------------------------------- /test/terraform.tf: -------------------------------------------------------------------------------- 1 | #resource "tls_private_key" "example" { 2 | # algorithm = "ECDSA" 3 | #} 4 | --------------------------------------------------------------------------------