├── scripts ├── docker-entrypoint.sh ├── build-local.sh ├── server.sh ├── server-docker.sh ├── server-memcached.sh ├── build.sh ├── test.sh └── build-docker.sh ├── cmd └── util │ ├── doc.go │ ├── main.go │ ├── memcached_test.go │ └── memcached.go ├── version.go ├── .gitignore ├── .editorconfig ├── .goreleaser.yml ├── Gopkg.lock ├── Dockerfile.in ├── LICENSE.md ├── Gopkg.toml ├── readme.md ├── .travis.yml └── Makefile /scripts/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec ${BINSRC_ENV} "$@" 4 | 5 | -------------------------------------------------------------------------------- /cmd/util/doc.go: -------------------------------------------------------------------------------- 1 | package main // import "github.com/me-io/memcached-util/cmd/util" 2 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package memcached_backup 2 | 3 | // VERSION is the app-global version string, which should be substituted with a 4 | // real value during build. 5 | var VERSION = "0.0.1" 6 | -------------------------------------------------------------------------------- /scripts/build-local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | export CGO_ENABLED=0 8 | my_dir="$(dirname "$0")" 9 | 10 | go build -o ${my_dir}/../bin/memcached-util cmd/util/*.go 11 | -------------------------------------------------------------------------------- /scripts/server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | my_dir="$(dirname "$0")" 8 | 9 | GO_FILES=`find ${my_dir}/../cmd/util/. -type f \( -iname "*.go" ! -iname "*_test.go" \)` 10 | go run ${GO_FILES} -- P="12312" 11 | -------------------------------------------------------------------------------- /scripts/server-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | my_dir="$(dirname "$0")" 8 | 9 | docker pull meio/go-swap-server:latest 10 | docker run --rm --name go-swap-server -u 0 -p 5000:5000 -it meio/go-swap-server:latest 11 | -------------------------------------------------------------------------------- /scripts/server-memcached.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | my_dir="$(dirname "$0")" 8 | 9 | GO_FILES=`find ${my_dir}/../cmd/util/. -type f \( -iname "*.go" ! -iname "*_test.go" \)` 10 | MEMCACHED_URL=localhost:11211 CACHE=memcached go run ${GO_FILES} 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /.go 3 | /.push-* 4 | /.container-* 5 | /.dockerfile-* 6 | .idea/ 7 | /vendor 8 | /_vendor* 9 | /coverage.txt 10 | # Mac OS X files 11 | .DS_Store 12 | mem_backup.json 13 | 14 | # Binaries for programs and plugins 15 | *.exe 16 | *.dll 17 | *.so 18 | *.dylib 19 | 20 | # Test binary, build with `go test -c` 21 | *.test 22 | 23 | # Output of the go coverage tool, specifically when used with LiteIDE 24 | *.out 25 | 26 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 27 | .glide/ 28 | .env 29 | .docker_build/ 30 | profile.cov 31 | dist 32 | mem_backup.json 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.json] 15 | indent_size = 2 16 | indent_style = space 17 | 18 | [Makefile] 19 | indent_style = tab 20 | 21 | [makefile] 22 | indent_style = tab 23 | 24 | [*.go] 25 | indent_style = tab 26 | 27 | [*.yml] 28 | indent_style = space 29 | indent_size = 2 30 | end_of_line = lf 31 | charset = utf-8 32 | trim_trailing_whitespace = true 33 | insert_final_newline = true 34 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: memcached-util 2 | before: 3 | hooks: 4 | - go generate ./... 5 | builds: 6 | - main: ./cmd/util/. 7 | binary: memcached-util 8 | ldflags: 9 | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} 10 | env: 11 | - CGO_ENABLED=0 12 | checksum: 13 | name_template: "{{ .ProjectName }}_checksums.txt" 14 | archive: 15 | name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" 16 | replacements: 17 | windows: Windows 18 | amd64: 64-bit 19 | 386: 32-bit 20 | darwin: macOS 21 | linux: Linux 22 | format: tar.gz 23 | format_overrides: 24 | - goos: windows 25 | format: zip 26 | files: 27 | - LICENSE.md 28 | - README.md 29 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | if [ -z "${PKG}" ]; then 8 | echo "PKG must be set" 9 | exit 1 10 | fi 11 | if [ -z "${ARCH}" ]; then 12 | echo "ARCH must be set" 13 | exit 1 14 | fi 15 | if [ -z "${VERSION}" ]; then 16 | echo "VERSION must be set" 17 | exit 1 18 | fi 19 | 20 | if [ -z "${OS}" ]; then 21 | echo "OS must be set" 22 | exit 1 23 | fi 24 | 25 | export CGO_ENABLED=0 26 | export GOARCH="${ARCH}" 27 | export GOOS="${OS}" 28 | 29 | go install \ 30 | -installsuffix "static" \ 31 | -ldflags "-X ${PKG}/pkg/version.VERSION=${VERSION}" \ 32 | ./... 33 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:f98385a9b77f6cacae716a59c04e6ac374d101466d4369c4e8cc706a39c4bb2e" 7 | name = "github.com/bradfitz/gomemcache" 8 | packages = ["memcache"] 9 | pruneopts = "UT" 10 | revision = "bc664df9673713a0ccf26e3b55a673ec7301088b" 11 | 12 | [[projects]] 13 | digest = "1:5b3b29ce0e569f62935d9541dff2e16cc09df981ebde48e82259076a73a3d0c7" 14 | name = "github.com/op/go-logging" 15 | packages = ["."] 16 | pruneopts = "UT" 17 | revision = "b2cb9fa56473e98db8caba80237377e83fe44db5" 18 | version = "v1" 19 | 20 | [solve-meta] 21 | analyzer-name = "dep" 22 | analyzer-version = 1 23 | input-imports = [ 24 | "github.com/bradfitz/gomemcache/memcache", 25 | "github.com/op/go-logging", 26 | ] 27 | solver-name = "gps-cdcl" 28 | solver-version = 1 29 | -------------------------------------------------------------------------------- /Dockerfile.in: -------------------------------------------------------------------------------- 1 | FROM ARG_FROM 2 | 3 | # Build-time metadata as defined at http://label-schema.org 4 | ARG BUILD_DATE 5 | ARG VCS_REF 6 | ARG VERSION 7 | ARG DOCKER_TAG 8 | 9 | LABEL org.label-schema.build-date=$BUILD_DATE \ 10 | org.label-schema.name="Currency Exchange Server" \ 11 | org.label-schema.description="Currency Exchange Server" \ 12 | org.label-schema.url="https://github.com/me-io/memcached-util" \ 13 | org.label-schema.vcs-ref=$VCS_REF \ 14 | org.label-schema.vcs-url="https://github.com/me-io/memcached-util" \ 15 | org.label-schema.vendor="ME.IO" \ 16 | org.label-schema.version=$VERSION \ 17 | org.label-schema.schema-version="$DOCKER_TAG" 18 | 19 | RUN apk update \ 20 | && apk upgrade \ 21 | && apk add --no-cache ca-certificates \ 22 | && update-ca-certificates 23 | 24 | ADD bin/ARG_OS-ARG_ARCH/ARG_SRC_BIN /ARG_BIN 25 | ENV BINSRC_ENV="/ARG_BIN" 26 | COPY scripts/docker-entrypoint.sh /usr/local/bin/ 27 | 28 | USER nobody:nobody 29 | ENTRYPOINT ["docker-entrypoint.sh"] 30 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | export CGO_ENABLED=0 8 | 9 | TARGETS=$(for d in "$@"; do echo ./$d/...; done) 10 | 11 | echo "Running tests: Install packages: " 12 | go test -v -i -installsuffix "static" ${TARGETS} # Install packages that are dependencies of the test. Do not run the test.. 13 | echo "Running tests: Run: " 14 | go test -covermode=count -coverprofile=profile.cov -v -installsuffix "static" ${TARGETS} 15 | echo 16 | 17 | echo -n "Checking gofmt: " 18 | ERRS=$(find "$@" -type f -name \*.go | xargs gofmt -l 2>&1 || true) 19 | if [ -n "${ERRS}" ]; then 20 | echo "FAIL - the following files need to be gofmt'ed:" 21 | for e in ${ERRS}; do 22 | echo " $e" 23 | done 24 | echo 25 | exit 1 26 | fi 27 | echo "PASS" 28 | echo 29 | 30 | echo -n "Checking go vet: " 31 | ERRS=$(go vet ${TARGETS} 2>&1 || true) 32 | if [ -n "${ERRS}" ]; then 33 | echo "FAIL" 34 | echo "${ERRS}" 35 | echo 36 | exit 1 37 | fi 38 | echo "PASS" 39 | echo 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ME.IO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | [metadata] 28 | codename = "memcached-util" 29 | 30 | required = [ 31 | "github.com/op/go-logging", 32 | "github.com/stretchr/testify" 33 | ] 34 | 35 | 36 | 37 | [[constraint]] 38 | name = "github.com/op/go-logging" 39 | version = "1.0.0" 40 | 41 | [[constraint]] 42 | name = "github.com/stretchr/testify" 43 | version = "1.2.2" 44 | 45 | [prune] 46 | go-tests = true 47 | unused-packages = true 48 | 49 | 50 | [metadata.heroku] 51 | root-package = "github.com/me-io/memcached-util" 52 | go-version = "1.11" 53 | install = ["./cmd/util/..."] 54 | ensure = "true" 55 | 56 | [[constraint]] 57 | branch = "master" 58 | name = "github.com/bradfitz/gomemcache" 59 | -------------------------------------------------------------------------------- /scripts/build-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | REPO_NAME="meio/go-swap-server" 4 | GIT_TAG=`git describe --tags --always --dirty` 5 | GO_VER=`go version` 6 | OS="linux" 7 | ARCH="amd64" 8 | DOCKER_TAG="${OS}-${ARCH}-${GIT_TAG}" 9 | 10 | # build only tag branch in this format 0.0.0 11 | if [[ ${GIT_TAG} =~ ^[[:digit:].[:digit:].[:digit:]]+$ ]]; then 12 | true 13 | echo "TAG: ${GIT_TAG} - start build" 14 | else 15 | echo "TAG: ${GIT_TAG} - skip build" 16 | exit 0 17 | fi 18 | 19 | # build only with go version 1.11 20 | if [[ ${GO_VER} =~ 1\.11 ]]; then 21 | echo "GO_VER: ${GO_VER} - start build" 22 | else 23 | echo "GO_VER: ${GO_VER} - skip build" 24 | exit 0 25 | fi 26 | 27 | if [[ ! -z "${DOCKER_PASSWORD}" && ! -z "${DOCKER_USERNAME}" ]] 28 | then 29 | echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin 30 | fi 31 | 32 | TAG_EXIST=`curl -s "https://hub.docker.com/v2/repositories/${REPO_NAME}/tags/${DOCKER_TAG}/" | grep '"id":'` 33 | 34 | if [[ ! -z ${TAG_EXIST} ]]; then 35 | echo "${REPO_NAME}:${DOCKER_TAG} already exist" 36 | exit 0 37 | fi 38 | 39 | 40 | docker build --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ 41 | --build-arg VCS_REF=`git rev-parse --short HEAD` \ 42 | --build-arg DOCKER_TAG="${DOCKER_TAG}" \ 43 | --build-arg VERSION="${GIT_TAG}" \ 44 | -t ${REPO_NAME}:${DOCKER_TAG} -f .dockerfile-${OS}-${ARCH} . 45 | 46 | if [[ $? != 0 ]]; then 47 | echo "${REPO_NAME}:${DOCKER_TAG} build failed" 48 | exit 1 49 | fi 50 | 51 | 52 | if [[ -z ${TAG_EXIST} ]]; then 53 | docker push ${REPO_NAME}:${DOCKER_TAG} 54 | echo "${REPO_NAME}:${DOCKER_TAG} pushed successfully" 55 | 56 | fi 57 | 58 | # push latest 59 | docker build --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ 60 | --build-arg VCS_REF=`git rev-parse --short HEAD` \ 61 | --build-arg DOCKER_TAG="${DOCKER_TAG}" \ 62 | --build-arg VERSION="${GIT_TAG}" \ 63 | -t ${REPO_NAME}:latest -f .dockerfile-${OS}-${ARCH} . 64 | 65 | docker push ${REPO_NAME}:latest 66 | echo "${REPO_NAME}:latest pushed successfully" 67 | 68 | # update microbadger.com 69 | curl -XPOST "https://hooks.microbadger.com/images/meio/go-swap-server/TOoBKgNqzCZH6dNBlAopouqsLF0=" 70 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Memcached Utility 2 | > Tiny memcached utility that allows you to backup and restore memcached cache 3 | 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/me-io/memcached-util)](https://goreportcard.com/report/github.com/me-io/memcached-util) 5 | [![GoDoc](https://godoc.org/github.com/me-io/memcached-util?status.svg)](https://godoc.org/github.com/me-io/memcached-util) 6 | [![Blog URL](https://img.shields.io/badge/Author-blog-green.svg?style=flat-square)](https://meabed.com) 7 | [![Build Status](https://travis-ci.org/me-io/memcached-util.svg?branch=master)](https://travis-ci.org/me-io/memcached-util) 8 | 9 | Useful for stopping/starting/restarting memcached server without sacrificing any cached data. 10 | 11 | ## Installation 12 | 13 | Download and install the relevant binary from the [releases page](https://github.com/me-io/memcached-util/releases) 14 | 15 | - [MacOS 32Bit](https://github.com/me-io/memcached-util/releases/download/0.0.1/memcached-util_macOS_32-bit.tar.gz) 16 | - [Linux 32Bit](https://github.com/me-io/memcached-util/releases/download/0.0.1/memcached-util_Linux_32-bit.tar.gz) 17 | - [Linux 64Bit](https://github.com/me-io/memcached-util/releases/download/0.0.1/memcached-util_Linux_64-bit.tar.gz) 18 | - [MacOS 64Bit](https://github.com/me-io/memcached-util/releases/download/0.0.1/memcached-util_macOS_64-bit.tar.gz) 19 | 20 | ## Usage 21 | 22 | Use the below signature to generate or restore the backup 23 | 24 | ```sh 25 | memcached-util [--op ] 26 | [--filename ] 27 | [--addr ] 28 | ``` 29 | 30 | Detail of options are listed below 31 | 32 | | **Option** | **Default** | **Description** | 33 | |--------|------|-------| 34 | | `--addr` | `0.0.0.0:11211` | Memcached address to connect to | 35 | | `--filename` | `mem_backup.json` | Path to the file to generate the backup in or to restore from | 36 | | `--op` | | `backup` or `restore | 37 | 38 | ### Examples 39 | 40 | Generate the backup and store at the given path 41 | ```sh 42 | memcached-util --addr "192.168.99.100:11211" --op "backup" --filename "mem_backup.json" 43 | memcached-util --op "backup" --filename "/some/path/mem_backup.json" 44 | ``` 45 | Restore the backup from the given path 46 | ```sh 47 | memcached-util --addr "192.168.99.100:11211" --op "restore" --filename "mem_backup.json" 48 | memcached-util --op "restore" --filename "/some/path/mem_backup.json" 49 | ``` 50 | 51 | ## Contributing 52 | 53 | Anyone is welcome to [contribute](CONTRIBUTING.md), however, if you decide to get involved, please take a moment to review the guidelines: 54 | 55 | * [Only one feature or change per pull request](CONTRIBUTING.md#only-one-feature-or-change-per-pull-request) 56 | * [Write meaningful commit messages](CONTRIBUTING.md#write-meaningful-commit-messages) 57 | * [Follow the existing coding standards](CONTRIBUTING.md#follow-the-existing-coding-standards) 58 | 59 | 60 | ## License 61 | 62 | The code is available under the [MIT license](LICENSE.md). 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.11.x 3 | services: 4 | - docker 5 | - redis-server 6 | env: 7 | global: 8 | - secure: bOJD194hcrEFYWRJP8acA+d0oyNw2me5jzbt0Rh6eBweH27rwYYrm3IyXESmfmGcdHxAL8SNRvpufsAqKMuNChP2MZBPXxlfuQhjIxZB9r1GBZH5dGOPFVCIsbMdYt8ZRsf1PwhFUvoTzJ8kYrOk/a0M5dZeGjBALGneP7Vv5MQLc7pDlA/xU0CeAJEL6w3OYsSgDMPjDa375XMXhE52Op/RSEt3w0CZKZ8/9lp4efArwt2IPq30UrmdtSbdHhaAVw37OwIy/7EwGVJcz4Hhh5sUvzHng0yxTGqn9MmY7R+Yk5A4WnCOT1Dkta8JPx3AgYd38bgAjhVpZ27zBfPdrETfN2NDNLOekUpPtQPCqoOvTUx4nLGFMdwYG6ix0NZdP52/gPnNfUFwuR08TjDEIXRiUo84ZA3s9Cfo/e7Ai83XjqvW5PuWNAXUewDPkGS0l7aGzKjMbWAe7PZ2qM8JSOJD2Jb1btqkgURKJuzlszuyo6W0vtJP6uifVe5yex/AZPbbAUh9y72wXuI4Hj3WCNDipgdRWZdiLuxAJZr13KVwDW5fjsQMbTccjvGfm4Rl0B/F6CY5Beh3Mld72un8mY7eUHdYsQQVydMNP7+PRMIFqZl0NUoH3xlZBqz8Lz2JQ5cbozLCx380/x05ZhpwMnznQLG0E2ARyQra7preCQY= 9 | - secure: ml3DTsUscWitVnvC8hHb4cyjqck/G6XIREuy7oCrt9mmUPERHgQHpUGQDaAUlO+UuumZufthWzo5280xaNSOMS/XJ78sx3r1FLl1/3d7uFAc/jAEeJzdff0Fd1Fq02QaTAPm74HkwY9uAGOiEdcmwaHofbxycIsPmv6BrxUbSap4/ndxqLbKGzUCiyZCS0ajDkXOVTnwuTO6aFahqokvR7RQfvwuOcMRuGlEys5JOVbVX3gO8pqQaSt7R2r+gTW8DQe+h3s6L3vAXBaUKOUqgBM8N/exxWE5652ggxEyVmV6ecFer+QSWUIAxceqm0LsR1sUNeAMHO98f1M5S+m8wjIDpWsvufRukGysywQ7DBVTcWs+kFs9JjTjJqkms27V9WShKhx1TjpCtss5cw9BCQblbpYrGW8O1+hgiKsBprroHdIUGVqVX3kgiGumkIfHX5SthzF/vZIwJzYANy3VftrgM3EhPJbvfoY4KFJEi0m90bEZ4mAn9S6Q8rjQom1rGRn5ltXEfcWFrA8n8ZbqPWihw7rB0JLLvN6MPB0OFlxGe7WV+5v8yG0q9guOobR+4StV5+28hnN1mf6pehB6LIjXCvkkxm57jesX06abeMh0QNA92Qo1fMPgSyk0JErrLzboPwH+mahMu7K6JXZEoYib4hSI9oQOncvz5W5fh9s= 10 | - secure: XUQyt9ix2jCpn/jrzBcfJ5Le5wDYtKQZzPgBLjcFQONz3d6sUbOHbf/MX3yxKVIk5AygkJlldVLFzLHG4QmElvU9JX2vtJxzLD3EmZIvZJ39DLcPcy3Md2UkhVU9D16em4qb3rw0RpQxOeyLQla5tI/nfoM5UACwsQF+4VicaH/J3QPvHwQK2jfaoOBSOGCCIjQ4xDO6OLcmABT9gbZl0S6XR6cAUc8vIXE1e6Aizye8lApPDgLE1DRDXnTTjJDaItIDn+i6XBDeqQS9Yk13QhaHvxBoHSEfP0qbuPbl5nH72X8PrW1QwB/Dm9CjfScyCSkvMxqfwa54sQqlBkJVzqyaHIoSLh9dMsQI0mSjvUKMM2RQRpsyRRuc86IOMN2Q+cAgHOuhxyAc7qoaujOmBItw+v4D+5dg8ug0rCXHeRMFbNR7FQE0QDYv1vl0nOj07e1kjdl7IKMtDj6nUxKpoPI3KFuYBXAHW2onL5G5l3k7wApHeM07j29yYCSNI7rRcSZa1YC7QiHVU+zuMeNB3Asszj5EBIh1NEqvS/ql0AsZ4dCusqYNPflY4sUSG8B3n4YYaHRkIWCx35+9/hXWgkrsnZx4et6C9LN0qCRO+6yDUOtzdXsNeeMmWigaZ+Y37+n04+nKBOInILMNYmGIBzjtscN3/DRwj738q4vxkTA= 11 | - secure: XmORQONfhnVXlIH24iOxpL7+V0pC/+9LpNliL8zg+XIFzVH4CHFTkqHiEu596W1zdZ9gXfcaJgrBWiJfzTFXriwHmlmWtmSMxg2rl5JzxOPnjNgepHU6rofS1NK8id4StgxvW8mwonZ12LaZTOCD+q6p30IXCMP1+nvnhUNqucdRHD3Cw1rufgb/Q9LjKwZ2lyn10IbwFmOzzKLvwWaAqpys3tOtqxFLvd9c6VTrTvZr8SUt4Z8AbhjOSFtCM7fgxFMpDliPoQ3I+SzBrotL26ibcWUR43IxaeYvVxPm1lgYl18e+/BT3rTLGJZXsWDw/nckUsyT+dpW7jzlYHH07eStH4N1o2eHupwvg5yLNxB8t2KBzxhRzcKZ6+nR9c/zMs2qfQlrA3lf+DdmArIOnmLDhkfHK054lTjhrsKF5uvY3dSn3eFrZE7DRePq5JyO9Vmk5APO02GqeZhDp6xMMimDdKulYJtWp1b/scE30gG7vsawbVJfNQuIWa2hdxUhWnmclDE2Lz7xmgrF9NIHywKwv+Ixz8Eq0+jSzskqCOJIcOQW+ZtTeq0bfb2gmcQEolDKr8T6IfnwhkN+pySKGatIAmUdvLo1PSqzyzv7VQQkKxJWdbPbms+V2OJvrSlRrLvRU59mq7j7JwerhkVita03fPLv3Ye8BNNBe8w0H7Q= 12 | before_install: 13 | - export COVERALLS_PARALLEL=true 14 | - export GOMAXPROCS=1 15 | - export GITHUB_TOKEN=$GITHUB_TOKEN_RELEASE 16 | - go get github.com/mattn/goveralls 17 | script: 18 | - make test-docker 19 | - "$GOPATH/bin/goveralls -repotoken $COVERALLS -coverprofile=profile.cov -service=travis-ci" 20 | - make build 21 | - test -f bin/linux-amd64/util 22 | - make container 23 | - docker images | grep meio/memcached-util 24 | deploy: 25 | - provider: script 26 | skip_cleanup: true 27 | script: "./scripts/build-docker.sh" 28 | on: 29 | tags: true 30 | condition: "$TRAVIS_OS_NAME = linux" 31 | - provider: script 32 | skip_cleanup: true 33 | script: curl -sL https://git.io/goreleaser | bash 34 | on: 35 | tags: true 36 | condition: "$TRAVIS_OS_NAME = linux" 37 | notifications: 38 | webhooks: 39 | urls: https://coveralls.io/webhook?repo_token=$COVERALLS 40 | -------------------------------------------------------------------------------- /cmd/util/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "github.com/op/go-logging" 7 | "io/ioutil" 8 | "os" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var ( 14 | client *memClient 15 | addr *string 16 | path *string 17 | operation *string 18 | 19 | format = logging.MustStringFormatter( 20 | `%{color}%{time:2006-01-02T15:04:05.999999} %{shortfunc} ▶ %{level:.8s} %{id:03x}%{color:reset} %{message}`, 21 | ) 22 | 23 | // Logger ... Logger Driver 24 | Logger = logging.MustGetLogger("memcached-util") 25 | ) 26 | 27 | // init ... init function of the server 28 | func init() { 29 | // Logging 30 | backendStderr := logging.NewLogBackend(os.Stderr, "", 0) 31 | backendFormatted := logging.NewBackendFormatter(backendStderr, format) 32 | // Only DEBUG and more severe messages should be sent to backend1 33 | backendLevelFormatted := logging.AddModuleLevel(backendFormatted) 34 | backendLevelFormatted.SetLevel(logging.DEBUG, "") 35 | // Set the backend to be used. 36 | logging.SetBackend(backendLevelFormatted) 37 | 38 | addr = flag.String("addr", `localhost:11211`, "Address to memcached server") 39 | path = flag.String("filename", "mem_backup.json", "Path to store the output file at") 40 | operation = flag.String("op", "", "Whether to backup the cache") 41 | 42 | // If the given filename does not have the suffix, add to it 43 | *path = strings.Trim(*path, "/") 44 | if !strings.HasSuffix(*path, ".json") { 45 | *path = *path + ".json" 46 | } 47 | 48 | flag.Parse() 49 | 50 | client = createClient(addr) 51 | } 52 | 53 | // backupCache: Exports the cache into file at the given path 54 | func backupCache() { 55 | var cachedData []KeyValue 56 | keys := client.ListKeys() 57 | foundCount := len(keys) 58 | 59 | Logger.Infof("%d values found in the storage", foundCount) 60 | if foundCount == 0 { 61 | Logger.Infof("No records to publish") 62 | os.Exit(0) 63 | } 64 | 65 | for _, key := range keys { 66 | keyValue, _ := client.Get(key.Name) 67 | keyValue.Expiry = key.Expiry 68 | cachedData = append(cachedData, *keyValue) 69 | } 70 | 71 | cachedJson, _ := json.Marshal(cachedData) 72 | ioutil.WriteFile(*path, cachedJson, 0644) 73 | Logger.Infof("Output file successfully generated at: %s", *path) 74 | } 75 | 76 | // restoreCache: Checks for the existence of the given file and 77 | // restores the data back to memcached 78 | func restoreCache() { 79 | if _, err := os.Stat(*path); os.IsNotExist(err) { 80 | Logger.Errorf("File %s does not exist or is not readable", *path) 81 | os.Exit(1) 82 | } 83 | 84 | cachedData, _ := ioutil.ReadFile(*path) 85 | var keyValues []KeyValue 86 | err := json.Unmarshal(cachedData, &keyValues) 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | for _, keyValue := range keyValues { 92 | expiryTime := time.Unix(int64(keyValue.Expiry), 0) 93 | currentTime := time.Now() 94 | 95 | duration := expiryTime.Sub(currentTime) 96 | expirySeconds := int(duration.Seconds()) 97 | if expirySeconds <= 0 { 98 | Logger.Warningf("Key %s already expired, skipping ..", keyValue.Name) 99 | } else { 100 | Logger.Infof("Restoring value for %s. Expires in %d seconds", keyValue.Name, expirySeconds) 101 | } 102 | 103 | client.Set(keyValue.Name, keyValue.Value, int(expirySeconds)) 104 | } 105 | } 106 | 107 | // main: Validates the arguments and processes backup or restore 108 | func main() { 109 | Logger.Infof("address %s", *addr) 110 | 111 | if *operation == "backup" { 112 | backupCache() 113 | } else if *operation == "restore" { 114 | restoreCache() 115 | } else { 116 | Logger.Error("--op is required with either 'backup' or 'restore'") 117 | os.Exit(1) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cmd/util/memcached_test.go: -------------------------------------------------------------------------------- 1 | // +build !integration 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | // Test Helpers 12 | type MockedCommandExecutor struct { 13 | t *testing.T 14 | executedCommands []string 15 | returnValues map[string][]string 16 | closed bool 17 | } 18 | 19 | func (executor *MockedCommandExecutor) execute(command string, responseDelimiters []string) []string { 20 | executor.executedCommands = append(executor.executedCommands, command) 21 | returnVal, ok := executor.returnValues[command] 22 | if ok { 23 | return returnVal 24 | } 25 | return []string{} 26 | } 27 | 28 | func (executor *MockedCommandExecutor) Close() { 29 | } 30 | 31 | func (executor *MockedCommandExecutor) addReturnValue(command string, returnValue []string) { 32 | executor.returnValues[command] = returnValue 33 | } 34 | 35 | /* 36 | Asserts that a given slice of commands have been called executed against the command executor. 37 | */ 38 | func (executor *MockedCommandExecutor) assertCommands(expectedCommands []string) { 39 | if !reflect.DeepEqual(executor.executedCommands, expectedCommands) { 40 | executor.t.Errorf("Executed command were '%v', expected '%v'", executor.executedCommands, expectedCommands) 41 | } 42 | } 43 | 44 | func createTestClient(t *testing.T) (*memClient, *MockedCommandExecutor) { 45 | executor := &MockedCommandExecutor{t, []string{}, map[string][]string{}, false} 46 | client := &memClient{ 47 | server: "foo", 48 | executor: executor, 49 | } 50 | return client, executor 51 | } 52 | 53 | // Actual tests 54 | 55 | func TestMemClient(t *testing.T) { 56 | _, err := MemClient("foo:1234") 57 | if err == nil { 58 | t.Errorf("Memclient should return an error for foo:1234") 59 | } 60 | } 61 | 62 | func TestGet(t *testing.T) { 63 | client, executor := createTestClient(t) 64 | client.Get("testkey") 65 | executor.assertCommands([]string{"get testkey\r\n"}) 66 | } 67 | 68 | func TestSet(t *testing.T) { 69 | client, executor := createTestClient(t) 70 | client.Set("testkey", "testval", 123) 71 | executor.assertCommands([]string{"set testkey 0 123 7\r\ntestval\r\n"}) 72 | } 73 | 74 | func TestVersion(t *testing.T) { 75 | client, executor := createTestClient(t) 76 | version := client.Version() 77 | executor.assertCommands([]string{"version \r\n"}) 78 | if version != "UNKNOWN" { 79 | t.Errorf("Received version does not match expected version (%v!=%v)", version, "VERSION myversion.1234") 80 | } 81 | 82 | executor.addReturnValue("version \r\n", []string{"VERSION myversion.1234"}) 83 | version = client.Version() 84 | if version != "VERSION myversion.1234" { 85 | t.Errorf("Received version does not match expected version (%v!=%v)", version, "VERSION myversion.1234") 86 | } 87 | } 88 | 89 | func TestStats(t *testing.T) { 90 | client, executor := createTestClient(t) 91 | // return some random stats 92 | returnStats := []string{"STAT time 1446586044", "STAT version 1.4.14 (Ubuntu)", "STAT libevent 2.0.21-stable"} 93 | executor.addReturnValue("stats\r\n", returnStats) 94 | 95 | stats := client.Stats() 96 | 97 | // validate that the result is correct and that the expected commands were executed 98 | expectedStats := []Stat{ 99 | {"time", "1446586044"}, 100 | {"version", "1.4.14 (Ubuntu)"}, 101 | {"libevent", "2.0.21-stable"}, 102 | } 103 | 104 | if !reflect.DeepEqual(stats, expectedStats) { 105 | t.Errorf("Returned cache stats incorrect (%v!=%v)", stats, expectedStats) 106 | } 107 | 108 | executor.assertCommands([]string{"stats\r\n"}) 109 | } 110 | 111 | func TestListKeys(t *testing.T) { 112 | // setup testcase 113 | client, executor := createTestClient(t) 114 | executor.addReturnValue("stats items\r\n", []string{"STAT items:1:number 4"}) 115 | 116 | executor.addReturnValue("stats cachedump 1 4\n", []string{ 117 | "ITEM location [9 b; 1539093795 s]", 118 | "ITEM profession [9 b; 1539088675 s]", 119 | "ITEM age [4 b; 1539088575 s]", 120 | "ITEM username [8 b; 1539088375 s]", 121 | }) 122 | 123 | keys := client.ListKeys() 124 | 125 | // validate that the result is correct and that the expected commands were executed 126 | expectedKeys := []Key{ 127 | {Original: "ITEM location [9 b; 1539093795 s]", Name: "location", Expiry: 1539093795}, 128 | {Original: "ITEM profession [9 b; 1539088675 s]", Name: "profession", Expiry: 1539088675}, 129 | {Original: "ITEM age [4 b; 1539088575 s]", Name: "age", Expiry: 1539088575}, 130 | {Original: "ITEM username [8 b; 1539088375 s]", Name: "username", Expiry: 1539088375}, 131 | } 132 | 133 | if (!reflect.DeepEqual(keys, expectedKeys)) { 134 | fmt.Println(keys) 135 | t.Errorf("Returned cache keys incorrect (%v!=%v)", keys, expectedKeys) 136 | } 137 | 138 | executor.assertCommands([]string{"stats items\r\n", "stats cachedump 1 4\n"}) 139 | } 140 | 141 | func TestStat(t *testing.T) { 142 | // setup testcase 143 | client, executor := createTestClient(t) 144 | returnStats := []string{"STAT time 1446586044", "STAT version 1.4.14 (Ubuntu)", "STAT libevent 2.0.21-stable"} 145 | executor.addReturnValue("stats\r\n", returnStats) 146 | 147 | time, ok := client.Stat("time") 148 | 149 | expectedTime := Stat{"time", "1446586044"} 150 | if !ok || !reflect.DeepEqual(time, expectedTime) { 151 | t.Errorf("Returned cache stat incorrect (%v!=%v)", time, expectedTime) 152 | } 153 | executor.assertCommands([]string{"stats\r\n"}) 154 | } 155 | -------------------------------------------------------------------------------- /cmd/util/memcached.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "os" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // The CommandExecutor interface defines an 14 | // entity that is able to execute memcached 15 | // commands against a memcached server. 16 | type CommandExecutor interface { 17 | execute(command string, delimiters []string) []string 18 | Close() 19 | } 20 | 21 | type KeyValue struct { 22 | Name string `json:"name,omitempty"` 23 | Value string `json:"value,omitempty"` 24 | Flag int `json:"flag,omitempty"` 25 | Expiry int `json:"expiry,omitempty"` 26 | Length int `json:"length,omitempty"` 27 | } 28 | 29 | type Key struct { 30 | Original string `json:"original,omitempty"` 31 | Name string `json:"name,omitempty"` 32 | Expiry int `json:"expiry,omitempty"` 33 | } 34 | 35 | type MemcachedCommandExecutor struct { 36 | connection net.Conn 37 | } 38 | 39 | func MemClient(server string) (*memClient, error) { 40 | conn, err := net.Dial("tcp", server) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return &memClient{ 46 | server: server, 47 | executor: &MemcachedCommandExecutor{ 48 | connection: conn, 49 | }, 50 | }, nil 51 | } 52 | 53 | type memClient struct { 54 | server string 55 | executor CommandExecutor 56 | } 57 | 58 | type Stat struct { 59 | name string 60 | value string 61 | } 62 | 63 | func (executor *MemcachedCommandExecutor) execute(command string, responseDelimiters []string) []string { 64 | fmt.Fprint(executor.connection, command) 65 | scanner := bufio.NewScanner(executor.connection) 66 | var result []string 67 | 68 | OUTER: 69 | for scanner.Scan() { 70 | line := scanner.Text() 71 | for _, delimiter := range responseDelimiters { 72 | if line == delimiter { 73 | break OUTER 74 | } 75 | } 76 | result = append(result, line) 77 | // if there is no delimiter specified, then the response is just a single line and we should return after 78 | // reading that first line (e.g. version command) 79 | if len(responseDelimiters) == 0 { 80 | break OUTER 81 | } 82 | // if there is an error storing the value, then we should break out of the loop 83 | if line == "ERROR" { 84 | break OUTER 85 | } 86 | } 87 | return result 88 | } 89 | 90 | // Closes the memcached connection 91 | func (executor *MemcachedCommandExecutor) Close() { 92 | executor.connection.Close() 93 | } 94 | 95 | // List all cache keys on the memcached server. 96 | func (client *memClient) ListKeys() []Key { 97 | var keys []Key 98 | result := client.executor.execute("stats items\r\n", []string{"END"}) 99 | 100 | // identify all slabs and their number of items by parsing the 'stats items' command 101 | r, _ := regexp.Compile("STAT items:([0-9]*):number ([0-9]*)") 102 | slabCounts := map[int]int{} 103 | for _, stat := range result { 104 | matches := r.FindStringSubmatch(stat) 105 | if len(matches) == 3 { 106 | slabId, _ := strconv.Atoi(matches[1]) 107 | slabItemCount, _ := strconv.Atoi(matches[2]) 108 | slabCounts[slabId] = slabItemCount 109 | } 110 | } 111 | 112 | // For each slab, dump all items and add each key to the `keys` slice 113 | r, _ = regexp.Compile(`ITEM\s*?(.*?)\s*?\[\d+\s*?b;\s*?(\d+)\s*?s\]`) 114 | for slabId, slabCount := range slabCounts { 115 | command := fmt.Sprintf("stats cachedump %v %v\n", slabId, slabCount) 116 | commandResult := client.executor.execute(command, []string{"END"}) 117 | for _, item := range commandResult { 118 | matches := r.FindStringSubmatch(item) 119 | expiry, _ := strconv.Atoi(matches[2]) 120 | 121 | keys = append(keys, Key{ 122 | matches[0], 123 | strings.TrimSpace(matches[1]), 124 | expiry, 125 | }) 126 | } 127 | } 128 | 129 | return keys 130 | } 131 | 132 | // Sets a given cache key on the memcached server to a given value. 133 | func (client *memClient) Set(key string, value string, expiration int) { 134 | flags := "0" 135 | command := fmt.Sprintf("set %s %s %d %d\r\n%s\r\n", key, flags, expiration, len(value), value) 136 | client.executor.execute(command, []string{"STORED"}) 137 | } 138 | 139 | // Retrieves a given cache key from the memcached server. 140 | // Returns a string array with the value and a boolean indicating 141 | // whether a value was found or not. 142 | func (client *memClient) Get(keyName string) (*KeyValue, bool) { 143 | command := fmt.Sprintf("get %s\r\n", keyName) 144 | result := client.executor.execute(command, []string{"END"}) 145 | 146 | if len(result) < 2 { 147 | return &KeyValue{}, false 148 | } 149 | 150 | keyValue := result[1] 151 | 152 | parts := strings.Split(result[0], " ") 153 | // ditch the first "VALUE " line 154 | flag, _ := strconv.Atoi(parts[2]) 155 | length, _ := strconv.Atoi(parts[3]) 156 | 157 | return &KeyValue{ 158 | keyName, 159 | keyValue, 160 | flag, 161 | 0, // Expiry is not returned in response, we will populate it in a different manner 162 | length, 163 | }, true 164 | } 165 | 166 | // Get the server version. 167 | func (client *memClient) Version() string { 168 | result := client.executor.execute("version \r\n", []string{}) 169 | if len(result) == 1 { 170 | return result[0] 171 | } 172 | 173 | return "UNKNOWN" 174 | } 175 | 176 | // Retrieves all server statistics. 177 | func (client *memClient) Stats() []Stat { 178 | result := client.executor.execute("stats\r\n", []string{"END"}) 179 | 180 | var stats []Stat 181 | for _, stat := range result { 182 | parts := strings.SplitN(stat, " ", 3) 183 | stats = append(stats, Stat{parts[1], parts[2]}) 184 | } 185 | 186 | return stats 187 | } 188 | 189 | // Retrieves a specific server statistic. 190 | func (client *memClient) Stat(statName string) (Stat, bool) { 191 | stats := client.Stats() 192 | for _, stat := range stats { 193 | if stat.name == statName { 194 | return stat, true 195 | } 196 | } 197 | 198 | return Stat{}, false 199 | } 200 | 201 | // Creates a memClient and deals with any errors 202 | // that might occur (e.g. unable to connect to server). 203 | func createClient(addr *string) (*memClient) { 204 | server := *addr 205 | client, err := MemClient(server) 206 | if err != nil { 207 | fmt.Fprintln(os.Stderr, "Unable to connect to", server) 208 | os.Exit(1) 209 | } 210 | 211 | return client 212 | } 213 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # The binary to build (just the basename). 2 | BIN := memcached-util 3 | SRC_BIN := util 4 | 5 | # This repo's root import path (under GOPATH). 6 | PKG := github.com/me-io/memcached-util 7 | 8 | # Where to push the docker image. 9 | REGISTRY ?= meio 10 | 11 | # Which architecture to build - see $(BUILD_PLATFORMS) for options. 12 | ARCH ?= amd64 13 | OS ?= linux 14 | 15 | # This version-strategy uses git tags to set the version string 16 | VERSION := $(shell git describe --tags --always --dirty) 17 | # 18 | # This version-strategy uses a manual value to set the version string 19 | #VERSION := 1.2.3 20 | 21 | ### 22 | ### These variables should not need tweaking. 23 | ### 24 | 25 | SRC_DIRS := cmd pkg # directories which hold app source (not vendored) 26 | 27 | ## 28 | MEMCACHED_URL ?= localhost:11211 29 | 30 | # $(OS)-$(ARCH) pairs to build binaries and containers for 31 | BUILD_PLATFORMS := linux-amd64 linux-arm linux-arm64 linux-ppc64le freebsd-amd64 freebsd-386 32 | CONTAINER_PLATFORMS := linux-amd64 linux-arm64 linux-ppc64le # must be a subset of BUILD_PLATFORMS 33 | 34 | # Set default base image dynamically for each arch 35 | ifeq ($(ARCH),amd64) 36 | BASEIMAGE?=alpine 37 | endif 38 | ifeq ($(ARCH),arm) 39 | BASEIMAGE?=armel/busybox 40 | endif 41 | ifeq ($(ARCH),arm64) 42 | BASEIMAGE?=aarch64/busybox 43 | endif 44 | ifeq ($(ARCH),ppc64le) 45 | BASEIMAGE?=ppc64le/busybox 46 | endif 47 | 48 | IMAGE := $(REGISTRY)/$(BIN)-$(OS)-$(ARCH) 49 | 50 | BUILD_IMAGE ?= golang:1.11-alpine 51 | 52 | # If you want to build all binaries, see the 'all-build' rule. 53 | # If you want to build all containers, see the 'all-container' rule. 54 | # If you want to build AND push all containers, see the 'all-push' rule. 55 | all: build 56 | 57 | build-%: 58 | @$(MAKE) --no-print-directory ARCH=$(word 2,$(subst -, ,$*)) OS=$(word 1,$(subst -, ,$*)) build 59 | 60 | container-%: 61 | @$(MAKE) --no-print-directory ARCH=$(word 2,$(subst -, ,$*)) OS=$(word 1,$(subst -, ,$*)) container 62 | 63 | push-%: 64 | @$(MAKE) --no-print-directory ARCH=$(word 2,$(subst -, ,$*)) OS=$(word 1,$(subst -, ,$*)) push 65 | 66 | all-build: $(addprefix build-, $(BUILD_PLATFORMS)) 67 | 68 | all-container: $(addprefix container-, $(CONTAINER_PLATFORMS)) 69 | 70 | all-push: $(addprefix push-, $(CONTAINER_PLATFORMS)) 71 | 72 | build: bin/$(OS)-$(ARCH)/$(BIN) 73 | 74 | bin/$(OS)-$(ARCH)/$(BIN): build-dirs 75 | ifeq ($(filter $(OS)-$(ARCH),$(BUILD_PLATFORMS)),) 76 | $(error unsupported build platform $(OS)-$(ARCH) not in $(BUILD_PLATFORMS)) 77 | endif 78 | @echo "building: $@" 79 | @docker run \ 80 | -ti \ 81 | --rm \ 82 | -u $$(id -u):$$(id -g) \ 83 | -v "$$(pwd)/.go:/go" \ 84 | -v "$$(pwd):/go/src/$(PKG)" \ 85 | -v "$$(pwd)/bin/$(OS)-$(ARCH):/go/bin" \ 86 | -v "$$(pwd)/bin/$(OS)-$(ARCH):/go/bin/$(OS)_$(ARCH)" \ 87 | -v "$$(pwd)/.go/std/$(OS)-$(ARCH):/usr/local/go/pkg/$(OS)_$(ARCH)_static" \ 88 | -v "$$(pwd)/.go/cache:/.cache" \ 89 | -w /go/src/$(PKG) \ 90 | $(BUILD_IMAGE) \ 91 | /bin/sh -c " \ 92 | ARCH=$(ARCH) \ 93 | OS=$(OS) \ 94 | VERSION=$(VERSION) \ 95 | PKG=$(PKG) \ 96 | ./scripts/build.sh \ 97 | " 98 | 99 | # Example: make shell CMD="-c 'date > datefile'" 100 | shell: build-dirs 101 | @echo "launching a shell in the containerized build environment" 102 | @docker run \ 103 | -ti \ 104 | --rm \ 105 | -u $$(id -u):$$(id -g) \ 106 | -v "$$(pwd)/.go:/go" \ 107 | -v "$$(pwd):/go/src/$(PKG)" \ 108 | -v "$$(pwd)/bin/$(OS)-$(ARCH):/go/bin" \ 109 | -v "$$(pwd)/bin/$(OS)-$(ARCH):/go/bin/$(OS)_$(ARCH)" \ 110 | -v "$$(pwd)/.go/std/$(OS)-$(ARCH):/usr/local/go/pkg/$(OS)_$(ARCH)_static" \ 111 | -v "$$(pwd)/.go/cache:/.cache" \ 112 | -w /go/src/$(PKG) \ 113 | $(BUILD_IMAGE) \ 114 | /bin/sh $(CMD) 115 | 116 | DOTFILE_IMAGE = $(subst :,_,$(subst /,_,$(IMAGE))-$(VERSION)) 117 | 118 | container: .container-$(DOTFILE_IMAGE) container-name 119 | .container-$(DOTFILE_IMAGE): bin/$(OS)-$(ARCH)/$(BIN) Dockerfile.in 120 | @sed \ 121 | -e 's|ARG_BIN|$(BIN)|g' \ 122 | -e 's|ARG_SRC_BIN|$(SRC_BIN)|g' \ 123 | -e 's|ARG_OS|$(OS)|g' \ 124 | -e 's|ARG_ARCH|$(ARCH)|g' \ 125 | -e 's|ARG_FROM|$(BASEIMAGE)|g' \ 126 | Dockerfile.in > .dockerfile-$(OS)-$(ARCH) 127 | @docker build -t $(IMAGE):$(VERSION) -f .dockerfile-$(OS)-$(ARCH) . 128 | @docker images -q $(IMAGE):$(VERSION) > $@ 129 | 130 | container-name: 131 | @echo "container: $(IMAGE):$(VERSION)" 132 | 133 | push: .push-$(DOTFILE_IMAGE) push-name 134 | .push-$(DOTFILE_IMAGE): .container-$(DOTFILE_IMAGE) 135 | ifeq ($(findstring gcr.io,$(REGISTRY)),gcr.io) 136 | @gcloud docker -- push $(IMAGE):$(VERSION) 137 | else 138 | @docker push $(IMAGE):$(VERSION) 139 | endif 140 | @docker images -q $(IMAGE):$(VERSION) > $@ 141 | 142 | push-name: 143 | @echo "pushed: $(IMAGE):$(VERSION)" 144 | 145 | version: 146 | @echo $(VERSION) 147 | 148 | test-local: 149 | MEMCACHED_URL=${MEMCACHED_URL} go test -v ./... 150 | 151 | test-docker: build-dirs 152 | @dep version >/dev/null 2>&1 || ( wget -O - https://raw.githubusercontent.com/golang/dep/master/install.sh | sh ) 153 | @dep ensure -vendor-only 154 | @docker container rm memcached-util-memcached -f > /dev/null 2>&1 || true 155 | @docker run \ 156 | -ti \ 157 | --rm \ 158 | --name memcached-util-memcached \ 159 | -d memcached:1.5.10-alpine -m 64 160 | @docker run \ 161 | -e "MEMCACHED_URL=memcached:11211" \ 162 | --link memcached-util-memcached:memcached \ 163 | -ti \ 164 | --rm \ 165 | -u $$(id -u):$$(id -g) \ 166 | -v "$$(pwd)/.go:/go" \ 167 | -v "$$(pwd)/bin/$(OS)-$(ARCH):/go/bin" \ 168 | -v "$$(pwd)/.go/std/$(OS)-$(ARCH):/usr/local/go/pkg/$(OS)_$(ARCH)_static" \ 169 | -v "$$(pwd)/.go/cache:/.cache" \ 170 | -w /go/src/$(PKG) \ 171 | $(BUILD_IMAGE) \ 172 | /bin/sh -c " \ 173 | ./scripts/test.sh $(SRC_DIRS) \ 174 | " ; \ 175 | docker container rm memcached-util-memcached -f > /dev/null 2>&1 176 | 177 | build-dirs: 178 | @mkdir -p bin/$(OS)-$(ARCH) 179 | @mkdir -p .go/cache .go/src/$(PKG) .go/pkg .go/bin .go/std/$(OS)-$(ARCH) 180 | 181 | clean: container-clean bin-clean 182 | 183 | container-clean: 184 | rm -rf .container-* .dockerfile-* .push-* dist 185 | 186 | bin-clean: 187 | rm -rf .go bin 188 | --------------------------------------------------------------------------------