├── .travis.yml ├── deploy.sh ├── terraform └── cloudflare │ ├── record.tf │ └── vars.tf ├── Dockerfile ├── test.sh ├── .gitignore ├── README.md └── Makefile /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: required 3 | notifications: 4 | email: true 5 | services: 6 | - docker 7 | script: 8 | - make test 9 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -o pipefail 4 | 5 | if [[ "$GITHUB_REF" != "refs/heads/master" ]]; then 6 | echo "$GITHUB_REF was not master, exiting..." 7 | exit 0 8 | fi 9 | 10 | echo "On branch ${GITHUB_REF}, deploying..." 11 | 12 | ( 13 | cd /usr/src 14 | make cf-import cf-plan cf-apply TERRAFORM_FLAGS=-auto-approve 15 | ) 16 | -------------------------------------------------------------------------------- /terraform/cloudflare/record.tf: -------------------------------------------------------------------------------- 1 | provider "cloudflare" { 2 | version = "~> 1.9" 3 | email = "${var.cloudflare_email}" 4 | token = "${var.cloudflare_token}" 5 | } 6 | 7 | resource "cloudflare_record" "record" { 8 | domain = "${var.record_domain}" 9 | name = "${var.record_name}" 10 | value = "${var.record_value}" 11 | type = "${var.record_type}" 12 | ttl = "${var.record_ttl}" 13 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM r.j3ss.co/terraform:latest 2 | 3 | LABEL "com.github.actions.name"="Cloudflare DNS" 4 | LABEL "com.github.actions.description"="Update or Create DNS record on cloudlfare" 5 | LABEL "com.github.actions.icon"="cloud" 6 | LABEL "com.github.actions.color"="orange" 7 | 8 | RUN apk add --no-cache \ 9 | git \ 10 | jq \ 11 | curl \ 12 | make 13 | 14 | COPY terraform /usr/src/terraform 15 | COPY Makefile /usr/src 16 | COPY deploy.sh /usr/local/bin/deploy 17 | 18 | WORKDIR /usr/src 19 | 20 | ENTRYPOINT ["deploy"] 21 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -o pipefail 4 | 5 | ERRORS=() 6 | 7 | # find all executables and run `shellcheck` 8 | for f in $(find . -type f -not -iwholename '*.git*' | sort -u); do 9 | if file "$f" | grep --quiet shell; then 10 | { 11 | shellcheck "$f" && echo "[OK]: sucessfully linted $f" 12 | } || { 13 | # add to errors 14 | ERRORS+=("$f") 15 | } 16 | fi 17 | done 18 | 19 | if [ ${#ERRORS[@]} -eq 0 ]; then 20 | echo "No errors, hooray" 21 | else 22 | echo "These files failed shellcheck: ${ERRORS[*]}" 23 | exit 1 24 | fi 25 | -------------------------------------------------------------------------------- /terraform/cloudflare/vars.tf: -------------------------------------------------------------------------------- 1 | # Cloudflare variables 2 | variable "cloudflare_email" { 3 | default = "CLOUDFLARE_EMAIL" 4 | } 5 | 6 | variable "cloudflare_token" { 7 | default = "CLOUDFLARE_TOKEN" 8 | } 9 | 10 | variable "record_domain" { 11 | default = "RECORD_DOMAIN" 12 | } 13 | 14 | variable "record_name" { 15 | default = "RECORD_NAME" 16 | } 17 | 18 | variable "record_value" { 19 | default = "RECORD_VALUE" 20 | type = "string" 21 | } 22 | 23 | variable "record_type" { 24 | default = "RECORD_TYPE" 25 | } 26 | 27 | variable "record_ttl" { 28 | default = 1 29 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###Go### 2 | 3 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 4 | *.o 5 | *.a 6 | *.so 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | 27 | 28 | ###OSX### 29 | 30 | .DS_Store 31 | .AppleDouble 32 | .LSOverride 33 | 34 | # Icon must ends with two \r. 35 | Icon 36 | 37 | 38 | # Thumbnails 39 | ._* 40 | 41 | # Files that might appear on external disk 42 | .Spotlight-V100 43 | .Trashes 44 | 45 | # Terraform 46 | .terraform/ 47 | terraform.tfstate* 48 | .terraform.tfstate* 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare DNS GitHub Action 2 | 3 | [![Build Status](https://travis-ci.org/xorilog/cloudflare-dns-action.svg?branch=master)](https://travis-ci.org/xorilog/cloudflare-dns-action) 4 | 5 | A GitHub action to set a Cloudflare DNS record on push to the master branch. 6 | 7 | ```hcl 8 | workflow "on push to master, adjust domain on Cloudflare" { 9 | on = "push" 10 | resolves = ["set cloudflare dns record"] 11 | } 12 | 13 | action "set cloudflare dns record" { 14 | uses = "xorilog/cloudflare-dns-action@master" 15 | env = { 16 | RECORD_DOMAIN = "site.example.com", 17 | RECORD_TYPE = "A", 18 | RECORD_VALUE = "192.168.0.11", 19 | RECORD_NAME = "terraform", 20 | RECORD_TTL = "1", 21 | } 22 | secrets = [ "CLOUDFLARE_EMAIL", "CLOUDFLARE_TOKEN" ] 23 | } 24 | ``` 25 | 26 | _Heavily_ inspired by [Jessie Frazelle's](https://twitter.com/jessfraz) [aws-fargate-action](https://github.com/jessfraz/aws-fargate-action) and [Chris Pilsworth](https://twitter.com/cpilsworth) [cloudflare-worker-action](https://github.com/cpilsworth/cloudflare-worker-action) GitHub action project. :trophy: 27 | 28 | ### Tests 29 | 30 | The tests use [shellcheck](https://github.com/koalaman/shellcheck). You don't 31 | need to install anything. They run in a container. 32 | 33 | ```console 34 | $ make test 35 | ``` 36 | 37 | ### Using the `Makefile` 38 | 39 | ```console 40 | $ make help 41 | cf-apply Run terraform apply for Amazon. 42 | cf-destroy Run terraform destroy for Amazon. 43 | cf-plan Run terraform plan for Amazon. 44 | shellcheck Runs the shellcheck tests on the scripts. 45 | test Runs the tests on the repository. 46 | update-terraform Update terraform binary locally from the docker container. 47 | update Update terraform binary locally. 48 | ``` 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | CLOUDFLARE_EMAIL := ${CLOUDFLARE_EMAIL} 4 | CLOUDFLARE_TOKEN := ${CLOUDFLARE_TOKEN} 5 | 6 | RECORD_DOMAIN := ${RECORD_DOMAIN} 7 | RECORD_NAME := ${RECORD_NAME} 8 | RECORD_VALUE := ${RECORD_VALUE} 9 | RECORD_TYPE := ${RECORD_TYPE} 10 | RECORD_TTL ?= 1 11 | 12 | ZONEID := $(shell curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$(RECORD_DOMAIN)&status=active&page=1&per_page=20&order=status&direction=desc&match=all" -H "X-Auth-Email: $(CLOUDFLARE_EMAIL)" -H "X-Auth-Key: $(CLOUDFLARE_TOKEN)" -H "Content-Type: application/json" | jq -r '.result[].id') 13 | RECORD_ID := $(shell curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$(ZONEID)/dns_records?name=$(RECORD_NAME).$(RECORD_DOMAIN)&page=1&per_page=20&order=type&direction=desc&match=all" -H "X-Auth-Email: $(CLOUDFLARE_EMAIL)" -H "X-Auth-Key: $(CLOUDFLARE_TOKEN)" -H "Content-Type: application/json"| jq -r '.result[].id') 14 | 15 | CF_DIR=$(CURDIR)/terraform/cloudflare 16 | TERRAFORM_FLAGS := 17 | CF_TERRAFORM_FLAGS = -var "cloudflare_email=$(CLOUDFLARE_EMAIL)" \ 18 | -var "cloudflare_email=$(CLOUDFLARE_EMAIL)" \ 19 | -var "cloudflare_token=$(CLOUDFLARE_TOKEN)" \ 20 | -var "record_domain=$(RECORD_DOMAIN)" \ 21 | -var "record_name=$(RECORD_NAME)" \ 22 | -var "record_value=$(RECORD_VALUE)" \ 23 | -var "record_type=$(RECORD_TYPE)" \ 24 | -var "record_ttl=$(RECORD_TTL)" 25 | 26 | .PHONY: help 27 | help: 28 | @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)" 29 | 30 | .PHONY: cf-init 31 | cf-init: 32 | @:$(call check_defined, CLOUDFLARE_EMAIL, Cloudflare email address) 33 | @:$(call check_defined, CLOUDFLARE_TOKEN, Cloudflare api key) 34 | @:$(call check_defined, RECORD_DOMAIN, Record domain) 35 | @:$(call check_defined, RECORD_NAME, Record name) 36 | @:$(call check_defined, RECORD_VALUE, Record value) 37 | @:$(call check_defined, RECORD_TYPE, Record type) 38 | @:$(call check_defined, RECORD_TTL, Record TTL) 39 | @cd $(CF_DIR) && terraform init $(CF_TERRAFORM_FLAGS) 40 | 41 | .PHONY: cf-import 42 | cf-import: cf-init ## Run terraform plan for Cloudflare worker. 43 | ifdef RECORD_ID 44 | @cd $(CF_DIR) && terraform import $(CF_TERRAFORM_FLAGS) cloudflare_record.record $(RECORD_DOMAIN)/$(RECORD_ID) 45 | endif 46 | 47 | .PHONY: cf-plan 48 | cf-plan: cf-init ## Run terraform plan for Cloudflare worker. 49 | @cd $(CF_DIR) && terraform plan $(CF_TERRAFORM_FLAGS) 50 | 51 | .PHONY: cf-apply 52 | cf-apply: cf-init ## Run terraform apply for Cloudflare worker. 53 | @cd $(CF_DIR) && terraform apply $(CF_TERRAFORM_FLAGS) \ 54 | $(TERRAFORM_FLAGS) 55 | 56 | .PHONY: cf-destroy 57 | cf-destroy: cf-init ## Run terraform destroy for Cloudflare worker. 58 | @cd $(CF_DIR) && terraform destroy \ 59 | $(CF_TERRAFORM_FLAGS) 60 | 61 | check_defined = \ 62 | $(strip $(foreach 1,$1, \ 63 | $(call __check_defined,$1,$(strip $(value 2))))) 64 | __check_defined = \ 65 | $(if $(value $1),, \ 66 | $(error Undefined $1$(if $2, ($2))$(if $(value @), \ 67 | required by target `$@'))) 68 | 69 | .PHONY: update 70 | update: update-terraform ## Update terraform binary locally. 71 | 72 | TERRAFORM_BINARY:=$(shell which terraform || echo "/usr/local/bin/terraform") 73 | TMP_TERRAFORM_BINARY:=/tmp/terraform 74 | .PHONY: update-terraform 75 | update-terraform: ## Update terraform binary locally from the docker container. 76 | @echo "Updating terraform binary..." 77 | $(shell docker run --rm --entrypoint bash r.j3ss.co/terraform -c "cd \$\$$(dirname \$\$$(which terraform)) && tar -Pc terraform" | tar -xvC $(dir $(TMP_TERRAFORM_BINARY)) > /dev/null) 78 | sudo mv $(TMP_TERRAFORM_BINARY) $(TERRAFORM_BINARY) 79 | sudo chmod +x $(TERRAFORM_BINARY) 80 | @echo "Update terraform binary: $(TERRAFORM_BINARY)" 81 | @terraform version 82 | 83 | .PHONY: test 84 | test: shellcheck ## Runs the tests on the repository. 85 | 86 | # if this session isn't interactive, then we don't want to allocate a 87 | # TTY, which would fail, but if it is interactive, we do want to attach 88 | # so that the user can send e.g. ^C through. 89 | INTERACTIVE := $(shell [ -t 0 ] && echo 1 || echo 0) 90 | ifeq ($(INTERACTIVE), 1) 91 | DOCKER_FLAGS += -t 92 | endif 93 | 94 | .PHONY: shellcheck 95 | shellcheck: ## Runs the shellcheck tests on the scripts. 96 | docker run --rm -i $(DOCKER_FLAGS) \ 97 | --name shellcheck \ 98 | -v $(CURDIR):/usr/src:ro \ 99 | --workdir /usr/src \ 100 | r.j3ss.co/shellcheck ./test.sh 101 | 102 | --------------------------------------------------------------------------------