From 5f4837491ff52842c8cbf3c80d3a580e1916c923 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 20:36:17 -0700 Subject: [PATCH 01/14] Overhaul Makefile and Dockerfile Trying to standardize builds across architectures. We wrote this basic Makefile/Dockerfile template for another container, but it works well, so reuse it here. --- .gitignore | 1 + Dockerfile | 6 ++-- Makefile | 94 +++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e56e04 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/Dockerfile b/Dockerfile index 618d93c..aa090dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,9 @@ FROM gcr.io/google_containers/ubuntu-slim:0.1 +ARG ARCH +ADD bin/git-sync-${ARCH} /git-sync + ENV GIT_SYNC_DEST /git -VOLUME ["/git"] RUN apt-get update && \ apt-get install -y git ca-certificates --no-install-recommends && \ @@ -9,8 +11,6 @@ RUN apt-get update && \ apt-get clean -y && \ rm -rf /var/lib/apt/lists/* -COPY git-sync /git-sync - # Move the existing SSH binary, then replace it with the wrapper script RUN mv /usr/bin/ssh /usr/bin/ssh-binary COPY ssh-wrapper.sh /usr/bin/ssh diff --git a/Makefile b/Makefile index 73bfcc7..6797a55 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,89 @@ -all: push +# Copyright 2016 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -# 0.0 shouldn't clobber any released builds -TAG = 0.0 -PREFIX = gcr.io/google_containers/git-sync +.PHONY: all push push-legacy container clean -binary: main.go - CGO_ENABLED=0 GOOS=linux godep go build -a -installsuffix cgo -ldflags '-w' -o git-sync +REGISTRY ?= gcr.io/google_containers +IMAGE = $(REGISTRY)/git-sync-$(ARCH) +LEGACY_AMD64_IMAGE = $(REGISTRY)/git-sync -container: binary - docker build -t $(PREFIX):$(TAG) . +TAG = 1.0 -push: container - gcloud docker push $(PREFIX):$(TAG) +# Architectures supported: amd64, arm, arm64 and ppc64le +ARCH ?= amd64 + +# TODO: get a base image for non-x86 archs +# arm arm64 ppc64le +ALL_ARCH = amd64 + +KUBE_CROSS_IMAGE ?= gcr.io/google_containers/kube-cross +KUBE_CROSS_VERSION ?= v1.6.3-2 + +GO_PKG = k8s.io/git-sync +BIN = git-sync +SRCS = main.go + +# If you want to build all containers, see the 'all-container' rule. +# If you want to build AND push all containers, see the 'all-push' rule. +all: all-build + +sub-container-%: + $(MAKE) ARCH=$* container + +sub-push-%: + $(MAKE) ARCH=$* push + +all-build: $(addprefix bin/$(BIN)-,$(ALL_ARCH)) + +all-container: $(addprefix sub-container-,$(ALL_ARCH)) + +all-push: $(addprefix sub-push-,$(ALL_ARCH)) + +build: bin/$(BIN)-$(ARCH) + +bin/$(BIN)-$(ARCH): $(SRCS) + mkdir -p bin + docker run \ + -u $$(id -u):$$(id -g) \ + -v $$(pwd):/go/src/$(GO_PKG) \ + $(KUBE_CROSS_IMAGE):$(KUBE_CROSS_VERSION) \ + /bin/bash -c " \ + cd /go/src/$(GO_PKG) && \ + CGO_ENABLED=0 godep go build \ + -installsuffix cgo \ + -ldflags '-w' \ + -o $@" + +container: .container-$(ARCH) +.container-$(ARCH): bin/$(BIN)-$(ARCH) + docker build -t $(IMAGE):$(TAG) --build-arg ARCH=$(ARCH) . +ifeq ($(ARCH),amd64) + docker tag $(IMAGE):$(TAG) $(LEGACY_AMD64_IMAGE):$(TAG) +endif + touch $@ + +push: .push-$(ARCH) +.push-$(ARCH): .container-$(ARCH) + gcloud docker push $(IMAGE):$(TAG) + touch $@ + +push-legacy: .push-legacy-$(ARCH) +.push-legacy-$(ARCH): .container-$(ARCH) +ifeq ($(ARCH),amd64) + gcloud docker push $(LEGACY_AMD64_IMAGE):$(TAG) +endif + touch $@ clean: - docker rmi -f $(PREFIX):$(TAG) || true + rm -rf .container-* .push-* bin/ From 4c112490afe6b6efda4a2c1e5e4b00eba4a1baec Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 20:37:57 -0700 Subject: [PATCH 02/14] Get rid of godep - no deps yet --- Godeps/Godeps.json | 8 -------- Godeps/Readme | 5 ----- Godeps/_workspace/.gitignore | 2 -- Makefile | 2 +- 4 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 Godeps/Godeps.json delete mode 100644 Godeps/Readme delete mode 100644 Godeps/_workspace/.gitignore diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json deleted file mode 100644 index be1078e..0000000 --- a/Godeps/Godeps.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ImportPath": "k8s.io/git-sync", - "GoVersion": "go1.5", - "Packages": [ - "./..." - ], - "Deps": [] -} diff --git a/Godeps/Readme b/Godeps/Readme deleted file mode 100644 index 4cdaa53..0000000 --- a/Godeps/Readme +++ /dev/null @@ -1,5 +0,0 @@ -This directory tree is generated automatically by godep. - -Please do not edit. - -See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore deleted file mode 100644 index f037d68..0000000 --- a/Godeps/_workspace/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/pkg -/bin diff --git a/Makefile b/Makefile index 6797a55..6f14d8f 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ bin/$(BIN)-$(ARCH): $(SRCS) $(KUBE_CROSS_IMAGE):$(KUBE_CROSS_VERSION) \ /bin/bash -c " \ cd /go/src/$(GO_PKG) && \ - CGO_ENABLED=0 godep go build \ + CGO_ENABLED=0 go build \ -installsuffix cgo \ -ldflags '-w' \ -o $@" From 0a403496bf2308be5ec05b53d4554cca0733e760 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 20:41:05 -0700 Subject: [PATCH 03/14] Fix README to actually run --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c6b9f75..722c1f4 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,27 @@ git-sync is a command that pull a git repository to a local directory. It can be used to source a container volume with the content of a git repo. -In order to ensure that the git repository content is cloned and updated atomically, you cannot use a volume root directory as the directory for your local repository. +In order to ensure that the git repository content is cloned and updated atomically, you cannot use a volume root directory as the directory for your local repository. -The local repository is created in a subdirectory of /git, with the subdirectory name specified by GIT_SYNC_DEST. +The local repository is created in a subdirectory of /git, with the subdirectory name specified by GIT_SYNC_DEST. ## Usage ``` # build the container -docker build -t git-sync . +docker build -t yourname/git-sync . +# or +make container + # run the git-sync container -docker run -d GIT_SYNC_REPO=https://github.com/GoogleCloudPlatform/kubernetes GIT_SYNC_DEST=/git -e GIT_SYNC_BRANCH=gh-pages -r HEAD -v /git-data:/git git-sync +docker run -d \ + -e GIT_SYNC_REPO=https://github.com/kubernetes/kubernetes \ + -e GIT_SYNC_DEST=/git \ + -e GIT_SYNC_BRANCH=gh-pages \ + -v /git-data:/git \ + git-sync # run a nginx container to serve sync'ed content -docker run -d -p 8080:80 -v /git-data:/usr/share/nginx/html nginx +docker run -d -p 8080:80 -v /git-data:/usr/share/nginx/html nginx ``` [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/git-sync/README.md?pixel)]() From c39a9369b976e1bf872b6c924ff9811bfffc69ab Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 20:52:06 -0700 Subject: [PATCH 04/14] Make /git a parameter rather than const This makes easier testing. --- main.go | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/main.go b/main.go index 2fd8671..60a8b2b 100644 --- a/main.go +++ b/main.go @@ -35,11 +35,10 @@ import ( "time" ) -const volMount = "/git" - var flRepo = flag.String("repo", envString("GIT_SYNC_REPO", ""), "git repo url") var flBranch = flag.String("branch", envString("GIT_SYNC_BRANCH", "master"), "git branch") var flRev = flag.String("rev", envString("GIT_SYNC_REV", "HEAD"), "git rev") +var flRoot = flag.String("root", envString("GIT_SYNC_ROOT", "/git"), "root directory for git operations") var flDest = flag.String("dest", envString("GIT_SYNC_DEST", ""), "destination subdirectory path within volume") var flWait = flag.Int("wait", envInt("GIT_SYNC_WAIT", 0), "number of seconds to wait before next sync") var flOneTime = flag.Bool("one-time", envBool("GIT_SYNC_ONE_TIME", false), "exit after the initial checkout") @@ -114,7 +113,7 @@ func main() { initialSync := true failCount := 0 for { - if err := syncRepo(*flRepo, *flDest, *flBranch, *flRev, *flDepth); err != nil { + if err := syncRepo(*flRepo, *flRoot, *flDest, *flBranch, *flRev, *flDepth); err != nil { if initialSync || failCount >= *flMaxSyncFailures { log.Fatalf("error syncing repo: %v", err) } @@ -140,27 +139,27 @@ func main() { } // updateSymlink atomically swaps the symlink to point at the specified directory and cleans up the previous worktree. -func updateSymlink(link, newDir string) error { +func updateSymlink(gitRoot, link, newDir string) error { // Get currently-linked repo directory (to be removed), unless it doesn't exist - currentDir, err := filepath.EvalSymlinks(path.Join(volMount, link)) + currentDir, err := filepath.EvalSymlinks(path.Join(gitRoot, link)) if err != nil && !os.IsNotExist(err) { return fmt.Errorf("error accessing symlink: %v", err) } // newDir is /git/rev-..., we need to change it to relative path. // Volume in other container may not be mounted at /git, so the symlink can't point to /git. - newDirRelative, err := filepath.Rel(volMount, newDir) + newDirRelative, err := filepath.Rel(gitRoot, newDir) if err != nil { return fmt.Errorf("error converting to relative path: %v", err) } - if _, err := runCommand("ln", volMount, []string{"-snf", newDirRelative, "tmp-link"}); err != nil { + if _, err := runCommand("ln", gitRoot, []string{"-snf", newDirRelative, "tmp-link"}); err != nil { return fmt.Errorf("error creating symlink: %v", err) } log.Printf("create symlink %v->%v", "tmp-link", newDirRelative) - if _, err := runCommand("mv", volMount, []string{"-T", "tmp-link", link}); err != nil { + if _, err := runCommand("mv", gitRoot, []string{"-T", "tmp-link", link}); err != nil { return fmt.Errorf("error replacing symlink: %v", err) } @@ -174,7 +173,7 @@ func updateSymlink(link, newDir string) error { log.Printf("remove %v", currentDir) - output, err := runCommand("git", volMount, []string{"worktree", "prune"}) + output, err := runCommand("git", gitRoot, []string{"worktree", "prune"}) if err != nil { return err } @@ -186,9 +185,9 @@ func updateSymlink(link, newDir string) error { } // addWorktreeAndSwap creates a new worktree and calls updateSymlink to swap the symlink to point to the new worktree -func addWorktreeAndSwap(dest, branch, rev string) error { +func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { // fetch branch - output, err := runCommand("git", volMount, []string{"fetch", "origin", branch}) + output, err := runCommand("git", gitRoot, []string{"fetch", "origin", branch}) if err != nil { return err } @@ -197,8 +196,8 @@ func addWorktreeAndSwap(dest, branch, rev string) error { // add worktree in subdir rand.Seed(time.Now().UnixNano()) - worktreePath := path.Join(volMount, "rev-"+strconv.Itoa(rand.Int())) - output, err = runCommand("git", volMount, []string{"worktree", "add", worktreePath, "origin/" + branch}) + worktreePath := path.Join(gitRoot, "rev-"+strconv.Itoa(rand.Int())) + output, err = runCommand("git", gitRoot, []string{"worktree", "add", worktreePath, "origin/" + branch}) if err != nil { return err } @@ -207,7 +206,7 @@ func addWorktreeAndSwap(dest, branch, rev string) error { // .git file in worktree directory holds a reference to /git/.git/worktrees/ // Replace it with a reference using relative paths, so that other containers can use a different volume mount name - worktreePathRelative, err := filepath.Rel(volMount, worktreePath) + worktreePathRelative, err := filepath.Rel(gitRoot, worktreePath) if err != nil { return err } @@ -232,10 +231,10 @@ func addWorktreeAndSwap(dest, branch, rev string) error { } } - return updateSymlink(dest, worktreePath) + return updateSymlink(gitRoot, dest, worktreePath) } -func initRepo(repo, dest, branch, rev string, depth int) error { +func initRepo(repo, dest, branch, rev string, depth int, gitRoot string) error { // clone repo args := []string{"clone", "--no-checkout", "-b", branch} if depth != 0 { @@ -243,7 +242,7 @@ func initRepo(repo, dest, branch, rev string, depth int) error { args = append(args, string(depth)) } args = append(args, repo) - args = append(args, volMount) + args = append(args, gitRoot) output, err := runCommand("git", "", args) if err != nil { return err @@ -255,13 +254,13 @@ func initRepo(repo, dest, branch, rev string, depth int) error { } // syncRepo syncs the branch of a given repository to the destination at the given rev. -func syncRepo(repo, dest, branch, rev string, depth int) error { - target := path.Join(volMount, dest) +func syncRepo(repo, gitRoot, dest, branch, rev string, depth int) error { + target := path.Join(gitRoot, dest) gitRepoPath := path.Join(target, ".git") _, err := os.Stat(gitRepoPath) switch { case os.IsNotExist(err): - err = initRepo(repo, target, branch, rev, depth) + err = initRepo(repo, target, branch, rev, depth, gitRoot) if err != nil { return err } @@ -278,7 +277,7 @@ func syncRepo(repo, dest, branch, rev string, depth int) error { } } - return addWorktreeAndSwap(dest, branch, rev) + return addWorktreeAndSwap(gitRoot, dest, branch, rev) } // gitRemoteChanged returns true if the remote HEAD is different from the local HEAD, false otherwise From 2d9dbf9fa679ea54f45004392af2236f5a78dfa7 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 20:57:59 -0700 Subject: [PATCH 05/14] clean up flag definitions --- main.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index 60a8b2b..8c265f7 100644 --- a/main.go +++ b/main.go @@ -38,23 +38,27 @@ import ( var flRepo = flag.String("repo", envString("GIT_SYNC_REPO", ""), "git repo url") var flBranch = flag.String("branch", envString("GIT_SYNC_BRANCH", "master"), "git branch") var flRev = flag.String("rev", envString("GIT_SYNC_REV", "HEAD"), "git rev") -var flRoot = flag.String("root", envString("GIT_SYNC_ROOT", "/git"), "root directory for git operations") -var flDest = flag.String("dest", envString("GIT_SYNC_DEST", ""), "destination subdirectory path within volume") -var flWait = flag.Int("wait", envInt("GIT_SYNC_WAIT", 0), "number of seconds to wait before next sync") -var flOneTime = flag.Bool("one-time", envBool("GIT_SYNC_ONE_TIME", false), "exit after the initial checkout") -var flDepth = flag.Int("depth", envInt("GIT_SYNC_DEPTH", 0), "shallow clone with a history truncated to the specified number of commits") +var flDepth = flag.Int("depth", envInt("GIT_SYNC_DEPTH", 0), + "shallow clone with a history truncated to the specified number of commits") +var flRoot = flag.String("root", envString("GIT_SYNC_ROOT", "/git"), + "root directory for git operations") +var flDest = flag.String("dest", envString("GIT_SYNC_DEST", ""), + "path at which to publish the checked-out files (a subdirectory under --root)") +var flWait = flag.Int("wait", envInt("GIT_SYNC_WAIT", 0), + "number of seconds between syncs") +var flOneTime = flag.Bool("one-time", envBool("GIT_SYNC_ONE_TIME", false), + "exit after the initial checkout") var flMaxSyncFailures = flag.Int("max-sync-failures", envInt("GIT_SYNC_MAX_SYNC_FAILURES", 0), - `number of consecutive failures allowed before aborting (the first pull must succeed)`) + "number of consecutive failures allowed before aborting (the first pull must succeed)") +var flChmod = flag.Int("change-permissions", envInt("GIT_SYNC_PERMISSIONS", 0), + "change the permissions of the checked-out files to this") var flUsername = flag.String("username", envString("GIT_SYNC_USERNAME", ""), "username") var flPassword = flag.String("password", envString("GIT_SYNC_PASSWORD", ""), "password") var flSSH = flag.Bool("ssh", envBool("GIT_SYNC_SSH", false), "use SSH protocol") -var flChmod = flag.Int("change-permissions", envInt("GIT_SYNC_PERMISSIONS", 0), `If set it will change the permissions of the directory - that contains the git repository. Example: 744`) - func envString(key, def string) string { if env := os.Getenv(key); env != "" { return env From 119465829f53b1fbb4d4fdaa6714992d89f3a24d Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 20:59:51 -0700 Subject: [PATCH 06/14] Minor log cleanup in prep for later changes --- main.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index 8c265f7..3026b94 100644 --- a/main.go +++ b/main.go @@ -96,21 +96,24 @@ func main() { flag.Parse() if *flRepo == "" || *flDest == "" { flag.Usage() - log.Fatal(usage) + os.Exit(1) } if _, err := exec.LookPath("git"); err != nil { - log.Fatalf("required git executable not found: %v", err) + log.Printf("required git executable not found: %v", err) + os.Exit(1) } if *flUsername != "" && *flPassword != "" { if err := setupGitAuth(*flUsername, *flPassword, *flRepo); err != nil { - log.Fatalf("error creating .netrc file: %v", err) + log.Printf("error creating .netrc file: %v", err) + os.Exit(1) } } if *flSSH { if err := setupGitSSH(); err != nil { - log.Fatalf("error configuring SSH: %v", err) + log.Printf("error configuring SSH: %v", err) + os.Exit(1) } } @@ -119,7 +122,8 @@ func main() { for { if err := syncRepo(*flRepo, *flRoot, *flDest, *flBranch, *flRev, *flDepth); err != nil { if initialSync || failCount >= *flMaxSyncFailures { - log.Fatalf("error syncing repo: %v", err) + log.Printf("error syncing repo: %v", err) + os.Exit(1) } failCount++ @@ -138,7 +142,6 @@ func main() { log.Printf("waiting %d seconds", *flWait) time.Sleep(time.Duration(*flWait) * time.Second) - log.Println("done") } } @@ -276,7 +279,7 @@ func syncRepo(repo, gitRoot, dest, branch, rev string, depth int) error { return err } if !needUpdate { - log.Printf("No change") + log.Printf("no change") return nil } } From 344eca6507d2c5a31b5b4b58a846498ac055335e Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 21:03:02 -0700 Subject: [PATCH 07/14] Reorder args to make more sense --- main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 3026b94..b4a7c2d 100644 --- a/main.go +++ b/main.go @@ -120,7 +120,7 @@ func main() { initialSync := true failCount := 0 for { - if err := syncRepo(*flRepo, *flRoot, *flDest, *flBranch, *flRev, *flDepth); err != nil { + if err := syncRepo(*flRepo, *flBranch, *flRev, *flDepth, *flRoot, *flDest); err != nil { if initialSync || failCount >= *flMaxSyncFailures { log.Printf("error syncing repo: %v", err) os.Exit(1) @@ -241,7 +241,7 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { return updateSymlink(gitRoot, dest, worktreePath) } -func initRepo(repo, dest, branch, rev string, depth int, gitRoot string) error { +func initRepo(repo, branch, rev string, depth int, gitRoot string) error { // clone repo args := []string{"clone", "--no-checkout", "-b", branch} if depth != 0 { @@ -261,13 +261,13 @@ func initRepo(repo, dest, branch, rev string, depth int, gitRoot string) error { } // syncRepo syncs the branch of a given repository to the destination at the given rev. -func syncRepo(repo, gitRoot, dest, branch, rev string, depth int) error { +func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error { target := path.Join(gitRoot, dest) gitRepoPath := path.Join(target, ".git") _, err := os.Stat(gitRepoPath) switch { case os.IsNotExist(err): - err = initRepo(repo, target, branch, rev, depth, gitRoot) + err = initRepo(repo, branch, rev, depth, gitRoot) if err != nil { return err } From cd1f719e8b55fc90df13540f1f85deca2349ccb2 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 21:30:23 -0700 Subject: [PATCH 08/14] Overhaul runCommand() Better args, logging. --- main.go | 54 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/main.go b/main.go index b4a7c2d..86ea473 100644 --- a/main.go +++ b/main.go @@ -160,13 +160,13 @@ func updateSymlink(gitRoot, link, newDir string) error { return fmt.Errorf("error converting to relative path: %v", err) } - if _, err := runCommand("ln", gitRoot, []string{"-snf", newDirRelative, "tmp-link"}); err != nil { + if _, err := runCommand(gitRoot, "ln", "-snf", newDirRelative, "tmp-link"); err != nil { return fmt.Errorf("error creating symlink: %v", err) } log.Printf("create symlink %v->%v", "tmp-link", newDirRelative) - if _, err := runCommand("mv", gitRoot, []string{"-T", "tmp-link", link}); err != nil { + if _, err := runCommand(gitRoot, "mv", "-T", "tmp-link", link); err != nil { return fmt.Errorf("error replacing symlink: %v", err) } @@ -180,12 +180,12 @@ func updateSymlink(gitRoot, link, newDir string) error { log.Printf("remove %v", currentDir) - output, err := runCommand("git", gitRoot, []string{"worktree", "prune"}) + output, err := runCommand(gitRoot, "git", "worktree", "prune") if err != nil { return err } - log.Printf("worktree prune %v", string(output)) + log.Printf("worktree prune %v", output) } return nil @@ -194,22 +194,22 @@ func updateSymlink(gitRoot, link, newDir string) error { // addWorktreeAndSwap creates a new worktree and calls updateSymlink to swap the symlink to point to the new worktree func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { // fetch branch - output, err := runCommand("git", gitRoot, []string{"fetch", "origin", branch}) + output, err := runCommand(gitRoot, "git", "fetch", "origin", branch) if err != nil { return err } - log.Printf("fetch %q: %s", branch, string(output)) + log.Printf("fetch %q: %s", branch, output) // add worktree in subdir rand.Seed(time.Now().UnixNano()) worktreePath := path.Join(gitRoot, "rev-"+strconv.Itoa(rand.Int())) - output, err = runCommand("git", gitRoot, []string{"worktree", "add", worktreePath, "origin/" + branch}) + output, err = runCommand(gitRoot, "git", "worktree", "add", worktreePath, "origin/"+branch) if err != nil { return err } - log.Printf("add worktree origin/%q: %v", branch, string(output)) + log.Printf("add worktree origin/%q: %v", branch, output) // .git file in worktree directory holds a reference to /git/.git/worktrees/ // Replace it with a reference using relative paths, so that other containers can use a different volume mount name @@ -223,16 +223,16 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { } // reset working copy - output, err = runCommand("git", worktreePath, []string{"reset", "--hard", rev}) + output, err = runCommand(worktreePath, "git", "reset", "--hard", rev) if err != nil { return err } - log.Printf("reset %q: %v", rev, string(output)) + log.Printf("reset %q: %v", rev, output) if *flChmod != 0 { // set file permissions - _, err = runCommand("chmod", "", []string{"-R", string(*flChmod), worktreePath}) + _, err = runCommand("", "chmod", "-R", strconv.Itoa(*flChmod), worktreePath) if err != nil { return err } @@ -250,12 +250,12 @@ func initRepo(repo, branch, rev string, depth int, gitRoot string) error { } args = append(args, repo) args = append(args, gitRoot) - output, err := runCommand("git", "", args) + output, err := runCommand("", "git", args...) if err != nil { return err } - log.Printf("clone %q: %s", repo, string(output)) + log.Printf("clone %q: %s", repo, output) return nil } @@ -289,32 +289,46 @@ func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error { // gitRemoteChanged returns true if the remote HEAD is different from the local HEAD, false otherwise func gitRemoteChanged(localDir, branch string) (bool, error) { - _, err := runCommand("git", localDir, []string{"remote", "update"}) + _, err := runCommand(localDir, "git", "remote", "update") if err != nil { return false, err } - localHead, err := runCommand("git", localDir, []string{"rev-parse", "HEAD"}) + localHead, err := runCommand(localDir, "git", "rev-parse", "HEAD") if err != nil { return false, err } - remoteHead, err := runCommand("git", localDir, []string{"rev-parse", fmt.Sprintf("origin/%v", branch)}) + remoteHead, err := runCommand(localDir, "git", "rev-parse", fmt.Sprintf("origin/%v", branch)) if err != nil { return false, err } - return (strings.Compare(string(localHead), string(remoteHead)) != 0), nil + return (localHead != remoteHead), nil } -func runCommand(command, cwd string, args []string) ([]byte, error) { +func cmdForLog(command string, args ...string) string { + if strings.ContainsAny(command, " \t\n") { + command = fmt.Sprintf("%q", command) + } + for i := range args { + if strings.ContainsAny(args[i], " \t\n") { + args[i] = fmt.Sprintf("%q", args[i]) + } + } + return command + " " + strings.Join(args, " ") +} + +func runCommand(cwd, command string, args ...string) (string, error) { + log.Printf("run(%q): %s", cwd, cmdForLog(command, args...)) + cmd := exec.Command(command, args...) if cwd != "" { cmd.Dir = cwd } output, err := cmd.CombinedOutput() if err != nil { - return []byte{}, fmt.Errorf("error running command %q : %v: %s", strings.Join(cmd.Args, " "), err, string(output)) + return "", fmt.Errorf("error running command: %v: %q", err, string(output)) } - return output, nil + return string(output), nil } func setupGitAuth(username, password, gitURL string) error { From 75998b92375ad3e473f3adad503ab89a90354851 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 21:40:48 -0700 Subject: [PATCH 09/14] clean up logging of git runs --- main.go | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index 86ea473..e038901 100644 --- a/main.go +++ b/main.go @@ -194,22 +194,20 @@ func updateSymlink(gitRoot, link, newDir string) error { // addWorktreeAndSwap creates a new worktree and calls updateSymlink to swap the symlink to point to the new worktree func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { // fetch branch - output, err := runCommand(gitRoot, "git", "fetch", "origin", branch) + _, err := runCommand(gitRoot, "git", "fetch", "origin", branch) if err != nil { return err } - - log.Printf("fetch %q: %s", branch, output) + log.Printf("fetched origin/%s", branch) // add worktree in subdir rand.Seed(time.Now().UnixNano()) worktreePath := path.Join(gitRoot, "rev-"+strconv.Itoa(rand.Int())) - output, err = runCommand(gitRoot, "git", "worktree", "add", worktreePath, "origin/"+branch) + _, err = runCommand(gitRoot, "git", "worktree", "add", worktreePath, "origin/"+branch) if err != nil { return err } - - log.Printf("add worktree origin/%q: %v", branch, output) + log.Printf("added worktree %s for origin/%s", worktreePath, branch) // .git file in worktree directory holds a reference to /git/.git/worktrees/ // Replace it with a reference using relative paths, so that other containers can use a different volume mount name @@ -223,12 +221,11 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { } // reset working copy - output, err = runCommand(worktreePath, "git", "reset", "--hard", rev) + _, err = runCommand(worktreePath, "git", "reset", "--hard", rev) if err != nil { return err } - - log.Printf("reset %q: %v", rev, output) + log.Printf("reset worktree %s to %s", worktreePath, rev) if *flChmod != 0 { // set file permissions @@ -241,21 +238,17 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { return updateSymlink(gitRoot, dest, worktreePath) } -func initRepo(repo, branch, rev string, depth int, gitRoot string) error { - // clone repo +func cloneRepo(repo, branch, rev string, depth int, gitRoot string) error { args := []string{"clone", "--no-checkout", "-b", branch} if depth != 0 { - args = append(args, "-depth") - args = append(args, string(depth)) + args = append(args, "-depth", strconv.Itoa(depth)) } - args = append(args, repo) - args = append(args, gitRoot) - output, err := runCommand("", "git", args...) + args = append(args, repo, gitRoot) + _, err := runCommand("", "git", args...) if err != nil { return err } - - log.Printf("clone %q: %s", repo, output) + log.Printf("cloned %s", repo) return nil } @@ -267,7 +260,7 @@ func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error { _, err := os.Stat(gitRepoPath) switch { case os.IsNotExist(err): - err = initRepo(repo, branch, rev, depth, gitRoot) + err = cloneRepo(repo, branch, rev, depth, gitRoot) if err != nil { return err } From 7fcc902aa7c9f2934a63720942669d55a0a306d3 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 20 Aug 2016 21:47:20 -0700 Subject: [PATCH 10/14] Print the git hash for any rev --- main.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main.go b/main.go index e038901..6964a5c 100644 --- a/main.go +++ b/main.go @@ -200,6 +200,16 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { } log.Printf("fetched origin/%s", branch) + // find the real commit for the rev + output, err := runCommand(gitRoot, "git", "rev-list", "-n1", rev) + if err != nil { + return err + } + revhash := strings.Trim(string(output), "\n") + if revhash != rev { + log.Printf("rev %s resolves to %s", rev, revhash) + } + // add worktree in subdir rand.Seed(time.Now().UnixNano()) worktreePath := path.Join(gitRoot, "rev-"+strconv.Itoa(rand.Int())) From ff73d8e4f0c4dcd9b9695b8f3d3495715dd5b3a4 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sun, 21 Aug 2016 20:48:40 -0700 Subject: [PATCH 11/14] Overhaul git logic to sync only when needed I wanted to understand it all so I ran a bunch of tests and found that it was resyncing when not required any time the rev was not == HEAD. Some stackoverflow and git manpage spelunking produced this. Nice side-effect is tha the workdir is named after the githash instead of random. --- main.go | 106 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/main.go b/main.go index 6964a5c..8ca9859 100644 --- a/main.go +++ b/main.go @@ -25,9 +25,9 @@ import ( "io" "io/ioutil" "log" - "math/rand" "os" "os/exec" + "os/signal" "path" "path/filepath" "strconv" @@ -132,19 +132,35 @@ func main() { time.Sleep(time.Duration(*flWait) * time.Second) continue } - - initialSync = false - failCount = 0 - - if *flOneTime { - os.Exit(0) + if initialSync { + if isHash, err := revIsHash(*flRev, *flRoot); err != nil { + log.Printf("can't tell if rev %s is a git hash, exiting", *flRev) + os.Exit(1) + } else if isHash { + log.Printf("rev %s appears to be a git hash, no further sync needed", *flRev) + sleepForever() + } + if *flOneTime { + os.Exit(0) + } + initialSync = false } - log.Printf("waiting %d seconds", *flWait) + failCount = 0 + log.Printf("next sync in %d seconds", *flWait) time.Sleep(time.Duration(*flWait) * time.Second) } } +// Do no work, but don't do something that triggers go's runtime into thinking +// it is deadlocked. +func sleepForever() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, os.Kill) + <-c + os.Exit(0) +} + // updateSymlink atomically swaps the symlink to point at the specified directory and cleans up the previous worktree. func updateSymlink(gitRoot, link, newDir string) error { // Get currently-linked repo directory (to be removed), unless it doesn't exist @@ -163,14 +179,12 @@ func updateSymlink(gitRoot, link, newDir string) error { if _, err := runCommand(gitRoot, "ln", "-snf", newDirRelative, "tmp-link"); err != nil { return fmt.Errorf("error creating symlink: %v", err) } - - log.Printf("create symlink %v->%v", "tmp-link", newDirRelative) + log.Printf("created symlink %s -> %s", "tmp-link", newDirRelative) if _, err := runCommand(gitRoot, "mv", "-T", "tmp-link", link); err != nil { return fmt.Errorf("error replacing symlink: %v", err) } - - log.Printf("rename symlink %v to %v", "tmp-link", link) + log.Printf("renamed symlink %s to %s", "tmp-link", link) // Clean up previous worktree if len(currentDir) > 0 { @@ -178,14 +192,14 @@ func updateSymlink(gitRoot, link, newDir string) error { return fmt.Errorf("error removing directory: %v", err) } - log.Printf("remove %v", currentDir) + log.Printf("removed %s", currentDir) - output, err := runCommand(gitRoot, "git", "worktree", "prune") + _, err := runCommand(gitRoot, "git", "worktree", "prune") if err != nil { return err } - log.Printf("worktree prune %v", output) + log.Printf("pruned old worktrees") } return nil @@ -193,14 +207,15 @@ func updateSymlink(gitRoot, link, newDir string) error { // addWorktreeAndSwap creates a new worktree and calls updateSymlink to swap the symlink to point to the new worktree func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { - // fetch branch - _, err := runCommand(gitRoot, "git", "fetch", "origin", branch) + // Fetch the branch. This is required for the worktree to be based on the + // current rev. + _, err := runCommand(gitRoot, "git", "fetch", "--tags", "origin", branch) if err != nil { return err } log.Printf("fetched origin/%s", branch) - // find the real commit for the rev + // Find the real commit for the rev. output, err := runCommand(gitRoot, "git", "rev-list", "-n1", rev) if err != nil { return err @@ -210,17 +225,18 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { log.Printf("rev %s resolves to %s", rev, revhash) } - // add worktree in subdir - rand.Seed(time.Now().UnixNano()) - worktreePath := path.Join(gitRoot, "rev-"+strconv.Itoa(rand.Int())) + // Make a worktree for this exact git hash. + worktreePath := path.Join(gitRoot, "rev-"+revhash) _, err = runCommand(gitRoot, "git", "worktree", "add", worktreePath, "origin/"+branch) if err != nil { return err } log.Printf("added worktree %s for origin/%s", worktreePath, branch) - // .git file in worktree directory holds a reference to /git/.git/worktrees/ - // Replace it with a reference using relative paths, so that other containers can use a different volume mount name + // The .git file in the worktree directory holds a reference to + // /git/.git/worktrees/. Replace it with a reference + // using relative paths, so that other containers can use a different volume + // mount name. worktreePathRelative, err := filepath.Rel(gitRoot, worktreePath) if err != nil { return err @@ -230,7 +246,7 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { return err } - // reset working copy + // Reset the worktree's working copy to the specific rev. _, err = runCommand(worktreePath, "git", "reset", "--hard", rev) if err != nil { return err @@ -263,6 +279,18 @@ func cloneRepo(repo, branch, rev string, depth int, gitRoot string) error { return nil } +func revIsHash(rev, gitRoot string) (bool, error) { + // If a rev is a tag name or HEAD, rev-list will produce the git hash. If + // it is already a git hash, the output will be the same hash. Of course, a + // user could specify "abc" and match "abcdef12345678", so we just do a + // prefix match. + output, err := runCommand(gitRoot, "git", "rev-list", "-n1", rev) + if err != nil { + return false, err + } + return strings.HasPrefix(output, rev), nil +} + // syncRepo syncs the branch of a given repository to the destination at the given rev. func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error { target := path.Join(gitRoot, dest) @@ -277,12 +305,14 @@ func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error { case err != nil: return fmt.Errorf("error checking if repo exist %q: %v", gitRepoPath, err) default: - needUpdate, err := gitRemoteChanged(target, branch) + needed, err := needResync(target, rev) if err != nil { return err } - if !needUpdate { - log.Printf("no change") + if needed { + log.Printf("update required") + } else { + log.Printf("no update required") return nil } } @@ -290,21 +320,31 @@ func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error { return addWorktreeAndSwap(gitRoot, dest, branch, rev) } -// gitRemoteChanged returns true if the remote HEAD is different from the local HEAD, false otherwise -func gitRemoteChanged(localDir, branch string) (bool, error) { - _, err := runCommand(localDir, "git", "remote", "update") +// needResync returns true if the upstream hash for rev is different from the local one. +func needResync(localDir, rev string) (bool, error) { + // Ask git what the exact hash is for rev. + local, err := runCommand(localDir, "git", "rev-list", "-n1", rev) if err != nil { return false, err } - localHead, err := runCommand(localDir, "git", "rev-parse", "HEAD") + local = strings.Trim(local, "\n") + + // Fetch rev from upstream. + _, err := runCommand(localDir, "git", "fetch", "origin", rev) if err != nil { return false, err } - remoteHead, err := runCommand(localDir, "git", "rev-parse", fmt.Sprintf("origin/%v", branch)) + + // Ask git what the exact hash is for upstream rev. + remote, err := runCommand(localDir, "git", "rev-list", "-n1", "FETCH_HEAD") if err != nil { return false, err } - return (localHead != remoteHead), nil + remote = strings.Trim(remote, "\n") + + log.Printf("local hash: %s", local) + log.Printf("remote hash: %s", remote) + return (local != remote), nil } func cmdForLog(command string, args ...string) string { From dd60fb0312df723515cba726708d8b81e3e043d2 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Wed, 24 Aug 2016 21:27:15 -0700 Subject: [PATCH 12/14] Clean up logging using glogr --- Godeps/Godeps.json | 22 + Godeps/Readme | 5 + Makefile | 5 +- main.go | 82 +- vendor/github.com/golang/glog/LICENSE | 191 ++++ vendor/github.com/golang/glog/README | 44 + vendor/github.com/golang/glog/glog.go | 1180 ++++++++++++++++++++ vendor/github.com/golang/glog/glog_file.go | 124 ++ vendor/github.com/thockin/glogr/LICENSE | 201 ++++ vendor/github.com/thockin/glogr/README.md | 8 + vendor/github.com/thockin/glogr/glogr.go | 86 ++ vendor/github.com/thockin/logr/LICENSE | 201 ++++ vendor/github.com/thockin/logr/README.md | 36 + vendor/github.com/thockin/logr/logr.go | 48 + 14 files changed, 2201 insertions(+), 32 deletions(-) create mode 100644 Godeps/Godeps.json create mode 100644 Godeps/Readme create mode 100644 vendor/github.com/golang/glog/LICENSE create mode 100644 vendor/github.com/golang/glog/README create mode 100644 vendor/github.com/golang/glog/glog.go create mode 100644 vendor/github.com/golang/glog/glog_file.go create mode 100644 vendor/github.com/thockin/glogr/LICENSE create mode 100644 vendor/github.com/thockin/glogr/README.md create mode 100644 vendor/github.com/thockin/glogr/glogr.go create mode 100644 vendor/github.com/thockin/logr/LICENSE create mode 100644 vendor/github.com/thockin/logr/README.md create mode 100644 vendor/github.com/thockin/logr/logr.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..de1ca71 --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,22 @@ +{ + "ImportPath": "k8s.io/git-sync", + "GoVersion": "go1.6", + "GodepVersion": "v66", + "Packages": [ + "./..." + ], + "Deps": [ + { + "ImportPath": "github.com/golang/glog", + "Rev": "23def4e6c14b4da8ac2ed8007337bc5eb5007998" + }, + { + "ImportPath": "github.com/thockin/glogr", + "Rev": "7a7f3ced4f9f52e96710761820bd85ed6c400aa5" + }, + { + "ImportPath": "github.com/thockin/logr", + "Rev": "103d90809f342ce8b186c0bc903a34bc1c8007be" + } + ] +} diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Makefile b/Makefile index 6f14d8f..5fab771 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,6 @@ KUBE_CROSS_VERSION ?= v1.6.3-2 GO_PKG = k8s.io/git-sync BIN = git-sync -SRCS = main.go # If you want to build all containers, see the 'all-container' rule. # If you want to build AND push all containers, see the 'all-push' rule. @@ -52,7 +51,7 @@ all-push: $(addprefix sub-push-,$(ALL_ARCH)) build: bin/$(BIN)-$(ARCH) -bin/$(BIN)-$(ARCH): $(SRCS) +bin/$(BIN)-$(ARCH): FORCE mkdir -p bin docker run \ -u $$(id -u):$$(id -g) \ @@ -87,3 +86,5 @@ endif clean: rm -rf .container-* .push-* bin/ + +FORCE: diff --git a/main.go b/main.go index 8ca9859..c88bc93 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "os" "os/exec" "os/signal" @@ -33,6 +32,9 @@ import ( "strconv" "strings" "time" + + "github.com/thockin/glogr" + "github.com/thockin/logr" ) var flRepo = flag.String("repo", envString("GIT_SYNC_REPO", ""), "git repo url") @@ -59,6 +61,17 @@ var flPassword = flag.String("password", envString("GIT_SYNC_PASSWORD", ""), "pa var flSSH = flag.Bool("ssh", envBool("GIT_SYNC_SSH", false), "use SSH protocol") +var log = newLoggerOrDie() + +func newLoggerOrDie() logr.Logger { + g, err := glogr.New() + if err != nil { + fmt.Fprintf(os.Stderr, "failind to initialize logging: %v", err) + os.Exit(1) + } + return g +} + func envString(key, def string) string { if env := os.Getenv(key); env != "" { return env @@ -82,7 +95,7 @@ func envInt(key string, def int) int { if env := os.Getenv(key); env != "" { val, err := strconv.Atoi(env) if err != nil { - log.Printf("invalid value for %q: using default: %q", key, def) + log.Errorf("invalid value for %q: using default: %q", key, def) return def } return val @@ -90,29 +103,29 @@ func envInt(key string, def int) int { return def } -const usage = "usage: GIT_SYNC_REPO= GIT_SYNC_DEST= [GIT_SYNC_BRANCH= GIT_SYNC_WAIT= GIT_SYNC_DEPTH= GIT_SYNC_USERNAME= GIT_SYNC_PASSWORD= GIT_SYNC_SSH= GIT_SYNC_ONE_TIME= GIT_SYNC_MAX_SYNC_FAILURES=] git-sync -repo GIT_REPO_URL -dest PATH [-branch -wait -username -password -ssh -depth -one-time -max-sync-failures]" - func main() { + setFlagDefaults() + flag.Parse() if *flRepo == "" || *flDest == "" { flag.Usage() os.Exit(1) } if _, err := exec.LookPath("git"); err != nil { - log.Printf("required git executable not found: %v", err) + log.Errorf("required git executable not found: %v", err) os.Exit(1) } if *flUsername != "" && *flPassword != "" { if err := setupGitAuth(*flUsername, *flPassword, *flRepo); err != nil { - log.Printf("error creating .netrc file: %v", err) + log.Errorf("error creating .netrc file: %v", err) os.Exit(1) } } if *flSSH { if err := setupGitSSH(); err != nil { - log.Printf("error configuring SSH: %v", err) + log.Errorf("error configuring SSH: %v", err) os.Exit(1) } } @@ -122,22 +135,22 @@ func main() { for { if err := syncRepo(*flRepo, *flBranch, *flRev, *flDepth, *flRoot, *flDest); err != nil { if initialSync || failCount >= *flMaxSyncFailures { - log.Printf("error syncing repo: %v", err) + log.Errorf("error syncing repo: %v", err) os.Exit(1) } failCount++ - log.Printf("unexpected error syncing repo: %v", err) - log.Printf("waiting %d seconds before retryng", *flWait) + log.Errorf("unexpected error syncing repo: %v", err) + log.V(0).Infof("waiting %d seconds before retryng", *flWait) time.Sleep(time.Duration(*flWait) * time.Second) continue } if initialSync { if isHash, err := revIsHash(*flRev, *flRoot); err != nil { - log.Printf("can't tell if rev %s is a git hash, exiting", *flRev) + log.Errorf("can't tell if rev %s is a git hash, exiting", *flRev) os.Exit(1) } else if isHash { - log.Printf("rev %s appears to be a git hash, no further sync needed", *flRev) + log.V(0).Infof("rev %s appears to be a git hash, no further sync needed", *flRev) sleepForever() } if *flOneTime { @@ -147,11 +160,21 @@ func main() { } failCount = 0 - log.Printf("next sync in %d seconds", *flWait) + log.V(0).Infof("next sync in %d seconds", *flWait) time.Sleep(time.Duration(*flWait) * time.Second) } } +func setFlagDefaults() { + // Force logging to stderr. + stderrFlag := flag.Lookup("logtostderr") + if stderrFlag == nil { + fmt.Fprintf(os.Stderr, "can't find flag 'logtostderr'") + os.Exit(1) + } + stderrFlag.Value.Set("true") +} + // Do no work, but don't do something that triggers go's runtime into thinking // it is deadlocked. func sleepForever() { @@ -179,12 +202,12 @@ func updateSymlink(gitRoot, link, newDir string) error { if _, err := runCommand(gitRoot, "ln", "-snf", newDirRelative, "tmp-link"); err != nil { return fmt.Errorf("error creating symlink: %v", err) } - log.Printf("created symlink %s -> %s", "tmp-link", newDirRelative) + log.V(1).Infof("created symlink %s -> %s", "tmp-link", newDirRelative) if _, err := runCommand(gitRoot, "mv", "-T", "tmp-link", link); err != nil { return fmt.Errorf("error replacing symlink: %v", err) } - log.Printf("renamed symlink %s to %s", "tmp-link", link) + log.V(1).Infof("renamed symlink %s to %s", "tmp-link", link) // Clean up previous worktree if len(currentDir) > 0 { @@ -192,14 +215,14 @@ func updateSymlink(gitRoot, link, newDir string) error { return fmt.Errorf("error removing directory: %v", err) } - log.Printf("removed %s", currentDir) + log.V(1).Infof("removed %s", currentDir) _, err := runCommand(gitRoot, "git", "worktree", "prune") if err != nil { return err } - log.Printf("pruned old worktrees") + log.V(1).Infof("pruned old worktrees") } return nil @@ -213,7 +236,6 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { if err != nil { return err } - log.Printf("fetched origin/%s", branch) // Find the real commit for the rev. output, err := runCommand(gitRoot, "git", "rev-list", "-n1", rev) @@ -222,7 +244,7 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { } revhash := strings.Trim(string(output), "\n") if revhash != rev { - log.Printf("rev %s resolves to %s", rev, revhash) + log.V(0).Infof("rev %s resolves to %s", rev, revhash) } // Make a worktree for this exact git hash. @@ -231,7 +253,7 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { if err != nil { return err } - log.Printf("added worktree %s for origin/%s", worktreePath, branch) + log.V(0).Infof("added worktree %s for origin/%s", worktreePath, branch) // The .git file in the worktree directory holds a reference to // /git/.git/worktrees/. Replace it with a reference @@ -251,7 +273,7 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev string) error { if err != nil { return err } - log.Printf("reset worktree %s to %s", worktreePath, rev) + log.V(0).Infof("reset worktree %s to %s", worktreePath, rev) if *flChmod != 0 { // set file permissions @@ -274,7 +296,7 @@ func cloneRepo(repo, branch, rev string, depth int, gitRoot string) error { if err != nil { return err } - log.Printf("cloned %s", repo) + log.V(0).Infof("cloned %s", repo) return nil } @@ -310,9 +332,9 @@ func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error { return err } if needed { - log.Printf("update required") + log.V(0).Infof("update required") } else { - log.Printf("no update required") + log.V(0).Infof("no update required") return nil } } @@ -330,7 +352,7 @@ func needResync(localDir, rev string) (bool, error) { local = strings.Trim(local, "\n") // Fetch rev from upstream. - _, err := runCommand(localDir, "git", "fetch", "origin", rev) + _, err = runCommand(localDir, "git", "fetch", "origin", rev) if err != nil { return false, err } @@ -342,8 +364,8 @@ func needResync(localDir, rev string) (bool, error) { } remote = strings.Trim(remote, "\n") - log.Printf("local hash: %s", local) - log.Printf("remote hash: %s", remote) + log.V(2).Infof("local hash: %s", local) + log.V(2).Infof("remote hash: %s", remote) return (local != remote), nil } @@ -360,7 +382,7 @@ func cmdForLog(command string, args ...string) string { } func runCommand(cwd, command string, args ...string) (string, error) { - log.Printf("run(%q): %s", cwd, cmdForLog(command, args...)) + log.V(5).Infof("run(%q): %s", cwd, cmdForLog(command, args...)) cmd := exec.Command(command, args...) if cwd != "" { @@ -375,7 +397,7 @@ func runCommand(cwd, command string, args ...string) (string, error) { } func setupGitAuth(username, password, gitURL string) error { - log.Println("setting up the git credential cache") + log.V(1).Infof("setting up the git credential cache") cmd := exec.Command("git", "config", "--global", "credential.helper", "cache") output, err := cmd.CombinedOutput() if err != nil { @@ -399,7 +421,7 @@ func setupGitAuth(username, password, gitURL string) error { } func setupGitSSH() error { - log.Println("setting up git SSH credentials") + log.V(1).Infof("setting up git SSH credentials") if _, err := os.Stat("/etc/git-secret/ssh"); err != nil { return fmt.Errorf("error: could not find SSH key Secret: %v", err) diff --git a/vendor/github.com/golang/glog/LICENSE b/vendor/github.com/golang/glog/LICENSE new file mode 100644 index 0000000..37ec93a --- /dev/null +++ b/vendor/github.com/golang/glog/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/golang/glog/README b/vendor/github.com/golang/glog/README new file mode 100644 index 0000000..387b4eb --- /dev/null +++ b/vendor/github.com/golang/glog/README @@ -0,0 +1,44 @@ +glog +==== + +Leveled execution logs for Go. + +This is an efficient pure Go implementation of leveled logs in the +manner of the open source C++ package + https://github.com/google/glog + +By binding methods to booleans it is possible to use the log package +without paying the expense of evaluating the arguments to the log. +Through the -vmodule flag, the package also provides fine-grained +control over logging at the file level. + +The comment from glog.go introduces the ideas: + + Package glog implements logging analogous to the Google-internal + C++ INFO/ERROR/V setup. It provides functions Info, Warning, + Error, Fatal, plus formatting variants such as Infof. It + also provides V-style logging controlled by the -v and + -vmodule=file=2 flags. + + Basic examples: + + glog.Info("Prepare to repel boarders") + + glog.Fatalf("Initialization failed: %s", err) + + See the documentation for the V function for an explanation + of these examples: + + if glog.V(2) { + glog.Info("Starting transaction...") + } + + glog.V(2).Infoln("Processed", nItems, "elements") + + +The repository contains an open source version of the log package +used inside Google. The master copy of the source lives inside +Google, not here. The code in this repo is for export only and is not itself +under development. Feature requests will be ignored. + +Send bug reports to golang-nuts@googlegroups.com. diff --git a/vendor/github.com/golang/glog/glog.go b/vendor/github.com/golang/glog/glog.go new file mode 100644 index 0000000..54bd7af --- /dev/null +++ b/vendor/github.com/golang/glog/glog.go @@ -0,0 +1,1180 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup. +// It provides functions Info, Warning, Error, Fatal, plus formatting variants such as +// Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. +// +// Basic examples: +// +// glog.Info("Prepare to repel boarders") +// +// glog.Fatalf("Initialization failed: %s", err) +// +// See the documentation for the V function for an explanation of these examples: +// +// if glog.V(2) { +// glog.Info("Starting transaction...") +// } +// +// glog.V(2).Infoln("Processed", nItems, "elements") +// +// Log output is buffered and written periodically using Flush. Programs +// should call Flush before exiting to guarantee all log output is written. +// +// By default, all log statements write to files in a temporary directory. +// This package provides several flags that modify this behavior. +// As a result, flag.Parse must be called before any logging is done. +// +// -logtostderr=false +// Logs are written to standard error instead of to files. +// -alsologtostderr=false +// Logs are written to standard error as well as to files. +// -stderrthreshold=ERROR +// Log events at or above this severity are logged to standard +// error as well as to files. +// -log_dir="" +// Log files will be written to this directory instead of the +// default temporary directory. +// +// Other flags provide aids to debugging. +// +// -log_backtrace_at="" +// When set to a file and line number holding a logging statement, +// such as +// -log_backtrace_at=gopherflakes.go:234 +// a stack trace will be written to the Info log whenever execution +// hits that statement. (Unlike with -vmodule, the ".go" must be +// present.) +// -v=0 +// Enable V-leveled logging at the specified level. +// -vmodule="" +// The syntax of the argument is a comma-separated list of pattern=N, +// where pattern is a literal file name (minus the ".go" suffix) or +// "glob" pattern and N is a V level. For instance, +// -vmodule=gopher*=3 +// sets the V level to 3 in all Go files whose names begin "gopher". +// +package glog + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "io" + stdLog "log" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// severity identifies the sort of log: info, warning etc. It also implements +// the flag.Value interface. The -stderrthreshold flag is of type severity and +// should be modified only through the flag.Value interface. The values match +// the corresponding constants in C++. +type severity int32 // sync/atomic int32 + +// These constants identify the log levels in order of increasing severity. +// A message written to a high-severity log file is also written to each +// lower-severity log file. +const ( + infoLog severity = iota + warningLog + errorLog + fatalLog + numSeverity = 4 +) + +const severityChar = "IWEF" + +var severityName = []string{ + infoLog: "INFO", + warningLog: "WARNING", + errorLog: "ERROR", + fatalLog: "FATAL", +} + +// get returns the value of the severity. +func (s *severity) get() severity { + return severity(atomic.LoadInt32((*int32)(s))) +} + +// set sets the value of the severity. +func (s *severity) set(val severity) { + atomic.StoreInt32((*int32)(s), int32(val)) +} + +// String is part of the flag.Value interface. +func (s *severity) String() string { + return strconv.FormatInt(int64(*s), 10) +} + +// Get is part of the flag.Value interface. +func (s *severity) Get() interface{} { + return *s +} + +// Set is part of the flag.Value interface. +func (s *severity) Set(value string) error { + var threshold severity + // Is it a known name? + if v, ok := severityByName(value); ok { + threshold = v + } else { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + threshold = severity(v) + } + logging.stderrThreshold.set(threshold) + return nil +} + +func severityByName(s string) (severity, bool) { + s = strings.ToUpper(s) + for i, name := range severityName { + if name == s { + return severity(i), true + } + } + return 0, false +} + +// OutputStats tracks the number of output lines and bytes written. +type OutputStats struct { + lines int64 + bytes int64 +} + +// Lines returns the number of lines written. +func (s *OutputStats) Lines() int64 { + return atomic.LoadInt64(&s.lines) +} + +// Bytes returns the number of bytes written. +func (s *OutputStats) Bytes() int64 { + return atomic.LoadInt64(&s.bytes) +} + +// Stats tracks the number of lines of output and number of bytes +// per severity level. Values must be read with atomic.LoadInt64. +var Stats struct { + Info, Warning, Error OutputStats +} + +var severityStats = [numSeverity]*OutputStats{ + infoLog: &Stats.Info, + warningLog: &Stats.Warning, + errorLog: &Stats.Error, +} + +// Level is exported because it appears in the arguments to V and is +// the type of the v flag, which can be set programmatically. +// It's a distinct type because we want to discriminate it from logType. +// Variables of type level are only changed under logging.mu. +// The -v flag is read only with atomic ops, so the state of the logging +// module is consistent. + +// Level is treated as a sync/atomic int32. + +// Level specifies a level of verbosity for V logs. *Level implements +// flag.Value; the -v flag is of type Level and should be modified +// only through the flag.Value interface. +type Level int32 + +// get returns the value of the Level. +func (l *Level) get() Level { + return Level(atomic.LoadInt32((*int32)(l))) +} + +// set sets the value of the Level. +func (l *Level) set(val Level) { + atomic.StoreInt32((*int32)(l), int32(val)) +} + +// String is part of the flag.Value interface. +func (l *Level) String() string { + return strconv.FormatInt(int64(*l), 10) +} + +// Get is part of the flag.Value interface. +func (l *Level) Get() interface{} { + return *l +} + +// Set is part of the flag.Value interface. +func (l *Level) Set(value string) error { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(Level(v), logging.vmodule.filter, false) + return nil +} + +// moduleSpec represents the setting of the -vmodule flag. +type moduleSpec struct { + filter []modulePat +} + +// modulePat contains a filter for the -vmodule flag. +// It holds a verbosity level and a file pattern to match. +type modulePat struct { + pattern string + literal bool // The pattern is a literal string + level Level +} + +// match reports whether the file matches the pattern. It uses a string +// comparison if the pattern contains no metacharacters. +func (m *modulePat) match(file string) bool { + if m.literal { + return file == m.pattern + } + match, _ := filepath.Match(m.pattern, file) + return match +} + +func (m *moduleSpec) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + var b bytes.Buffer + for i, f := range m.filter { + if i > 0 { + b.WriteRune(',') + } + fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) + } + return b.String() +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported. +func (m *moduleSpec) Get() interface{} { + return nil +} + +var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") + +// Syntax: -vmodule=recordio=2,file=1,gfs*=3 +func (m *moduleSpec) Set(value string) error { + var filter []modulePat + for _, pat := range strings.Split(value, ",") { + if len(pat) == 0 { + // Empty strings such as from a trailing comma can be ignored. + continue + } + patLev := strings.Split(pat, "=") + if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { + return errVmoduleSyntax + } + pattern := patLev[0] + v, err := strconv.Atoi(patLev[1]) + if err != nil { + return errors.New("syntax error: expect comma-separated list of filename=N") + } + if v < 0 { + return errors.New("negative value for vmodule level") + } + if v == 0 { + continue // Ignore. It's harmless but no point in paying the overhead. + } + // TODO: check syntax of filter? + filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(logging.verbosity, filter, true) + return nil +} + +// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters +// that require filepath.Match to be called to match the pattern. +func isLiteral(pattern string) bool { + return !strings.ContainsAny(pattern, `\*?[]`) +} + +// traceLocation represents the setting of the -log_backtrace_at flag. +type traceLocation struct { + file string + line int +} + +// isSet reports whether the trace location has been specified. +// logging.mu is held. +func (t *traceLocation) isSet() bool { + return t.line > 0 +} + +// match reports whether the specified file and line matches the trace location. +// The argument file name is the full path, not the basename specified in the flag. +// logging.mu is held. +func (t *traceLocation) match(file string, line int) bool { + if t.line != line { + return false + } + if i := strings.LastIndex(file, "/"); i >= 0 { + file = file[i+1:] + } + return t.file == file +} + +func (t *traceLocation) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + return fmt.Sprintf("%s:%d", t.file, t.line) +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported +func (t *traceLocation) Get() interface{} { + return nil +} + +var errTraceSyntax = errors.New("syntax error: expect file.go:234") + +// Syntax: -log_backtrace_at=gopherflakes.go:234 +// Note that unlike vmodule the file extension is included here. +func (t *traceLocation) Set(value string) error { + if value == "" { + // Unset. + t.line = 0 + t.file = "" + } + fields := strings.Split(value, ":") + if len(fields) != 2 { + return errTraceSyntax + } + file, line := fields[0], fields[1] + if !strings.Contains(file, ".") { + return errTraceSyntax + } + v, err := strconv.Atoi(line) + if err != nil { + return errTraceSyntax + } + if v <= 0 { + return errors.New("negative or zero value for level") + } + logging.mu.Lock() + defer logging.mu.Unlock() + t.line = v + t.file = file + return nil +} + +// flushSyncWriter is the interface satisfied by logging destinations. +type flushSyncWriter interface { + Flush() error + Sync() error + io.Writer +} + +func init() { + flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") + flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") + flag.Var(&logging.verbosity, "v", "log level for V logs") + flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") + flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") + flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") + + // Default stderrThreshold is ERROR. + logging.stderrThreshold = errorLog + + logging.setVState(0, nil, false) + go logging.flushDaemon() +} + +// Flush flushes all pending log I/O. +func Flush() { + logging.lockAndFlushAll() +} + +// loggingT collects all the global state of the logging setup. +type loggingT struct { + // Boolean flags. Not handled atomically because the flag.Value interface + // does not let us avoid the =true, and that shorthand is necessary for + // compatibility. TODO: does this matter enough to fix? Seems unlikely. + toStderr bool // The -logtostderr flag. + alsoToStderr bool // The -alsologtostderr flag. + + // Level flag. Handled atomically. + stderrThreshold severity // The -stderrthreshold flag. + + // freeList is a list of byte buffers, maintained under freeListMu. + freeList *buffer + // freeListMu maintains the free list. It is separate from the main mutex + // so buffers can be grabbed and printed to without holding the main lock, + // for better parallelization. + freeListMu sync.Mutex + + // mu protects the remaining elements of this structure and is + // used to synchronize logging. + mu sync.Mutex + // file holds writer for each of the log types. + file [numSeverity]flushSyncWriter + // pcs is used in V to avoid an allocation when computing the caller's PC. + pcs [1]uintptr + // vmap is a cache of the V Level for each V() call site, identified by PC. + // It is wiped whenever the vmodule flag changes state. + vmap map[uintptr]Level + // filterLength stores the length of the vmodule filter chain. If greater + // than zero, it means vmodule is enabled. It may be read safely + // using sync.LoadInt32, but is only modified under mu. + filterLength int32 + // traceLocation is the state of the -log_backtrace_at flag. + traceLocation traceLocation + // These flags are modified only under lock, although verbosity may be fetched + // safely using atomic.LoadInt32. + vmodule moduleSpec // The state of the -vmodule flag. + verbosity Level // V logging level, the value of the -v flag/ +} + +// buffer holds a byte Buffer for reuse. The zero value is ready for use. +type buffer struct { + bytes.Buffer + tmp [64]byte // temporary byte array for creating headers. + next *buffer +} + +var logging loggingT + +// setVState sets a consistent state for V logging. +// l.mu is held. +func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) { + // Turn verbosity off so V will not fire while we are in transition. + logging.verbosity.set(0) + // Ditto for filter length. + atomic.StoreInt32(&logging.filterLength, 0) + + // Set the new filters and wipe the pc->Level map if the filter has changed. + if setFilter { + logging.vmodule.filter = filter + logging.vmap = make(map[uintptr]Level) + } + + // Things are consistent now, so enable filtering and verbosity. + // They are enabled in order opposite to that in V. + atomic.StoreInt32(&logging.filterLength, int32(len(filter))) + logging.verbosity.set(verbosity) +} + +// getBuffer returns a new, ready-to-use buffer. +func (l *loggingT) getBuffer() *buffer { + l.freeListMu.Lock() + b := l.freeList + if b != nil { + l.freeList = b.next + } + l.freeListMu.Unlock() + if b == nil { + b = new(buffer) + } else { + b.next = nil + b.Reset() + } + return b +} + +// putBuffer returns a buffer to the free list. +func (l *loggingT) putBuffer(b *buffer) { + if b.Len() >= 256 { + // Let big buffers die a natural death. + return + } + l.freeListMu.Lock() + b.next = l.freeList + l.freeList = b + l.freeListMu.Unlock() +} + +var timeNow = time.Now // Stubbed out for testing. + +/* +header formats a log header as defined by the C++ implementation. +It returns a buffer containing the formatted header and the user's file and line number. +The depth specifies how many stack frames above lives the source line to be identified in the log message. + +Log lines have this form: + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... +where the fields are defined as follows: + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() + file The file name + line The line number + msg The user-supplied message +*/ +func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { + _, file, line, ok := runtime.Caller(3 + depth) + if !ok { + file = "???" + line = 1 + } else { + slash := strings.LastIndex(file, "/") + if slash >= 0 { + file = file[slash+1:] + } + } + return l.formatHeader(s, file, line), file, line +} + +// formatHeader formats a log header using the provided file name and line number. +func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { + now := timeNow() + if line < 0 { + line = 0 // not a real line number, but acceptable to someDigits + } + if s > fatalLog { + s = infoLog // for safety. + } + buf := l.getBuffer() + + // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. + // It's worth about 3X. Fprintf is hard. + _, month, day := now.Date() + hour, minute, second := now.Clock() + // Lmmdd hh:mm:ss.uuuuuu threadid file:line] + buf.tmp[0] = severityChar[s] + buf.twoDigits(1, int(month)) + buf.twoDigits(3, day) + buf.tmp[5] = ' ' + buf.twoDigits(6, hour) + buf.tmp[8] = ':' + buf.twoDigits(9, minute) + buf.tmp[11] = ':' + buf.twoDigits(12, second) + buf.tmp[14] = '.' + buf.nDigits(6, 15, now.Nanosecond()/1000, '0') + buf.tmp[21] = ' ' + buf.nDigits(7, 22, pid, ' ') // TODO: should be TID + buf.tmp[29] = ' ' + buf.Write(buf.tmp[:30]) + buf.WriteString(file) + buf.tmp[0] = ':' + n := buf.someDigits(1, line) + buf.tmp[n+1] = ']' + buf.tmp[n+2] = ' ' + buf.Write(buf.tmp[:n+3]) + return buf +} + +// Some custom tiny helper functions to print the log header efficiently. + +const digits = "0123456789" + +// twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i]. +func (buf *buffer) twoDigits(i, d int) { + buf.tmp[i+1] = digits[d%10] + d /= 10 + buf.tmp[i] = digits[d%10] +} + +// nDigits formats an n-digit integer at buf.tmp[i], +// padding with pad on the left. +// It assumes d >= 0. +func (buf *buffer) nDigits(n, i, d int, pad byte) { + j := n - 1 + for ; j >= 0 && d > 0; j-- { + buf.tmp[i+j] = digits[d%10] + d /= 10 + } + for ; j >= 0; j-- { + buf.tmp[i+j] = pad + } +} + +// someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. +func (buf *buffer) someDigits(i, d int) int { + // Print into the top, then copy down. We know there's space for at least + // a 10-digit number. + j := len(buf.tmp) + for { + j-- + buf.tmp[j] = digits[d%10] + d /= 10 + if d == 0 { + break + } + } + return copy(buf.tmp[i:], buf.tmp[j:]) +} + +func (l *loggingT) println(s severity, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintln(buf, args...) + l.output(s, buf, file, line, false) +} + +func (l *loggingT) print(s severity, args ...interface{}) { + l.printDepth(s, 1, args...) +} + +func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) { + buf, file, line := l.header(s, depth) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +func (l *loggingT) printf(s severity, format string, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintf(buf, format, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +// printWithFileLine behaves like print but uses the provided file and line number. If +// alsoLogToStderr is true, the log message always appears on standard error; it +// will also appear in the log file unless --logtostderr is set. +func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) { + buf := l.formatHeader(s, file, line) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, alsoToStderr) +} + +// output writes the data to the log files and releases the buffer. +func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) { + l.mu.Lock() + if l.traceLocation.isSet() { + if l.traceLocation.match(file, line) { + buf.Write(stacks(false)) + } + } + data := buf.Bytes() + if !flag.Parsed() { + os.Stderr.Write([]byte("ERROR: logging before flag.Parse: ")) + os.Stderr.Write(data) + } else if l.toStderr { + os.Stderr.Write(data) + } else { + if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { + os.Stderr.Write(data) + } + if l.file[s] == nil { + if err := l.createFiles(s); err != nil { + os.Stderr.Write(data) // Make sure the message appears somewhere. + l.exit(err) + } + } + switch s { + case fatalLog: + l.file[fatalLog].Write(data) + fallthrough + case errorLog: + l.file[errorLog].Write(data) + fallthrough + case warningLog: + l.file[warningLog].Write(data) + fallthrough + case infoLog: + l.file[infoLog].Write(data) + } + } + if s == fatalLog { + // If we got here via Exit rather than Fatal, print no stacks. + if atomic.LoadUint32(&fatalNoStacks) > 0 { + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(1) + } + // Dump all goroutine stacks before exiting. + // First, make sure we see the trace for the current goroutine on standard error. + // If -logtostderr has been specified, the loop below will do that anyway + // as the first stack in the full dump. + if !l.toStderr { + os.Stderr.Write(stacks(false)) + } + // Write the stack trace for all goroutines to the files. + trace := stacks(true) + logExitFunc = func(error) {} // If we get a write error, we'll still exit below. + for log := fatalLog; log >= infoLog; log-- { + if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set. + f.Write(trace) + } + } + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway. + } + l.putBuffer(buf) + l.mu.Unlock() + if stats := severityStats[s]; stats != nil { + atomic.AddInt64(&stats.lines, 1) + atomic.AddInt64(&stats.bytes, int64(len(data))) + } +} + +// timeoutFlush calls Flush and returns when it completes or after timeout +// elapses, whichever happens first. This is needed because the hooks invoked +// by Flush may deadlock when glog.Fatal is called from a hook that holds +// a lock. +func timeoutFlush(timeout time.Duration) { + done := make(chan bool, 1) + go func() { + Flush() // calls logging.lockAndFlushAll() + done <- true + }() + select { + case <-done: + case <-time.After(timeout): + fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout) + } +} + +// stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines. +func stacks(all bool) []byte { + // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. + n := 10000 + if all { + n = 100000 + } + var trace []byte + for i := 0; i < 5; i++ { + trace = make([]byte, n) + nbytes := runtime.Stack(trace, all) + if nbytes < len(trace) { + return trace[:nbytes] + } + n *= 2 + } + return trace +} + +// logExitFunc provides a simple mechanism to override the default behavior +// of exiting on error. Used in testing and to guarantee we reach a required exit +// for fatal logs. Instead, exit could be a function rather than a method but that +// would make its use clumsier. +var logExitFunc func(error) + +// exit is called if there is trouble creating or writing log files. +// It flushes the logs and exits the program; there's no point in hanging around. +// l.mu is held. +func (l *loggingT) exit(err error) { + fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err) + // If logExitFunc is set, we do that instead of exiting. + if logExitFunc != nil { + logExitFunc(err) + return + } + l.flushAll() + os.Exit(2) +} + +// syncBuffer joins a bufio.Writer to its underlying file, providing access to the +// file's Sync method and providing a wrapper for the Write method that provides log +// file rotation. There are conflicting methods, so the file cannot be embedded. +// l.mu is held for all its methods. +type syncBuffer struct { + logger *loggingT + *bufio.Writer + file *os.File + sev severity + nbytes uint64 // The number of bytes written to this file +} + +func (sb *syncBuffer) Sync() error { + return sb.file.Sync() +} + +func (sb *syncBuffer) Write(p []byte) (n int, err error) { + if sb.nbytes+uint64(len(p)) >= MaxSize { + if err := sb.rotateFile(time.Now()); err != nil { + sb.logger.exit(err) + } + } + n, err = sb.Writer.Write(p) + sb.nbytes += uint64(n) + if err != nil { + sb.logger.exit(err) + } + return +} + +// rotateFile closes the syncBuffer's file and starts a new one. +func (sb *syncBuffer) rotateFile(now time.Time) error { + if sb.file != nil { + sb.Flush() + sb.file.Close() + } + var err error + sb.file, _, err = create(severityName[sb.sev], now) + sb.nbytes = 0 + if err != nil { + return err + } + + sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) + + // Write header. + var buf bytes.Buffer + fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) + fmt.Fprintf(&buf, "Running on machine: %s\n", host) + fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) + fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") + n, err := sb.file.Write(buf.Bytes()) + sb.nbytes += uint64(n) + return err +} + +// bufferSize sizes the buffer associated with each log file. It's large +// so that log records can accumulate without the logging thread blocking +// on disk I/O. The flushDaemon will block instead. +const bufferSize = 256 * 1024 + +// createFiles creates all the log files for severity from sev down to infoLog. +// l.mu is held. +func (l *loggingT) createFiles(sev severity) error { + now := time.Now() + // Files are created in decreasing severity order, so as soon as we find one + // has already been created, we can stop. + for s := sev; s >= infoLog && l.file[s] == nil; s-- { + sb := &syncBuffer{ + logger: l, + sev: s, + } + if err := sb.rotateFile(now); err != nil { + return err + } + l.file[s] = sb + } + return nil +} + +const flushInterval = 30 * time.Second + +// flushDaemon periodically flushes the log file buffers. +func (l *loggingT) flushDaemon() { + for _ = range time.NewTicker(flushInterval).C { + l.lockAndFlushAll() + } +} + +// lockAndFlushAll is like flushAll but locks l.mu first. +func (l *loggingT) lockAndFlushAll() { + l.mu.Lock() + l.flushAll() + l.mu.Unlock() +} + +// flushAll flushes all the logs and attempts to "sync" their data to disk. +// l.mu is held. +func (l *loggingT) flushAll() { + // Flush from fatal down, in case there's trouble flushing. + for s := fatalLog; s >= infoLog; s-- { + file := l.file[s] + if file != nil { + file.Flush() // ignore error + file.Sync() // ignore error + } + } +} + +// CopyStandardLogTo arranges for messages written to the Go "log" package's +// default logs to also appear in the Google logs for the named and lower +// severities. Subsequent changes to the standard log's default output location +// or format may break this behavior. +// +// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not +// recognized, CopyStandardLogTo panics. +func CopyStandardLogTo(name string) { + sev, ok := severityByName(name) + if !ok { + panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) + } + // Set a log format that captures the user's file and line: + // d.go:23: message + stdLog.SetFlags(stdLog.Lshortfile) + stdLog.SetOutput(logBridge(sev)) +} + +// logBridge provides the Write method that enables CopyStandardLogTo to connect +// Go's standard logs to the logs provided by this package. +type logBridge severity + +// Write parses the standard logging line and passes its components to the +// logger for severity(lb). +func (lb logBridge) Write(b []byte) (n int, err error) { + var ( + file = "???" + line = 1 + text string + ) + // Split "d.go:23: message" into "d.go", "23", and "message". + if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 { + text = fmt.Sprintf("bad log format: %s", b) + } else { + file = string(parts[0]) + text = string(parts[2][1:]) // skip leading space + line, err = strconv.Atoi(string(parts[1])) + if err != nil { + text = fmt.Sprintf("bad line number: %s", b) + line = 1 + } + } + // printWithFileLine with alsoToStderr=true, so standard log messages + // always appear on standard error. + logging.printWithFileLine(severity(lb), file, line, true, text) + return len(b), nil +} + +// setV computes and remembers the V level for a given PC +// when vmodule is enabled. +// File pattern matching takes the basename of the file, stripped +// of its .go suffix, and uses filepath.Match, which is a little more +// general than the *? matching used in C++. +// l.mu is held. +func (l *loggingT) setV(pc uintptr) Level { + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + // The file is something like /a/b/c/d.go. We want just the d. + if strings.HasSuffix(file, ".go") { + file = file[:len(file)-3] + } + if slash := strings.LastIndex(file, "/"); slash >= 0 { + file = file[slash+1:] + } + for _, filter := range l.vmodule.filter { + if filter.match(file) { + l.vmap[pc] = filter.level + return filter.level + } + } + l.vmap[pc] = 0 + return 0 +} + +// Verbose is a boolean type that implements Infof (like Printf) etc. +// See the documentation of V for more information. +type Verbose bool + +// V reports whether verbosity at the call site is at least the requested level. +// The returned value is a boolean of type Verbose, which implements Info, Infoln +// and Infof. These methods will write to the Info log if called. +// Thus, one may write either +// if glog.V(2) { glog.Info("log this") } +// or +// glog.V(2).Info("log this") +// The second form is shorter but the first is cheaper if logging is off because it does +// not evaluate its arguments. +// +// Whether an individual call to V generates a log record depends on the setting of +// the -v and --vmodule flags; both are off by default. If the level in the call to +// V is at least the value of -v, or of -vmodule for the source file containing the +// call, the V call will log. +func V(level Level) Verbose { + // This function tries hard to be cheap unless there's work to do. + // The fast path is two atomic loads and compares. + + // Here is a cheap but safe test to see if V logging is enabled globally. + if logging.verbosity.get() >= level { + return Verbose(true) + } + + // It's off globally but it vmodule may still be set. + // Here is another cheap but safe test to see if vmodule is enabled. + if atomic.LoadInt32(&logging.filterLength) > 0 { + // Now we need a proper lock to use the logging structure. The pcs field + // is shared so we must lock before accessing it. This is fairly expensive, + // but if V logging is enabled we're slow anyway. + logging.mu.Lock() + defer logging.mu.Unlock() + if runtime.Callers(2, logging.pcs[:]) == 0 { + return Verbose(false) + } + v, ok := logging.vmap[logging.pcs[0]] + if !ok { + v = logging.setV(logging.pcs[0]) + } + return Verbose(v >= level) + } + return Verbose(false) +} + +// Info is equivalent to the global Info function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Info(args ...interface{}) { + if v { + logging.print(infoLog, args...) + } +} + +// Infoln is equivalent to the global Infoln function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infoln(args ...interface{}) { + if v { + logging.println(infoLog, args...) + } +} + +// Infof is equivalent to the global Infof function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infof(format string, args ...interface{}) { + if v { + logging.printf(infoLog, format, args...) + } +} + +// Info logs to the INFO log. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Info(args ...interface{}) { + logging.print(infoLog, args...) +} + +// InfoDepth acts as Info but uses depth to determine which call frame to log. +// InfoDepth(0, "msg") is the same as Info("msg"). +func InfoDepth(depth int, args ...interface{}) { + logging.printDepth(infoLog, depth, args...) +} + +// Infoln logs to the INFO log. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Infoln(args ...interface{}) { + logging.println(infoLog, args...) +} + +// Infof logs to the INFO log. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Infof(format string, args ...interface{}) { + logging.printf(infoLog, format, args...) +} + +// Warning logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Warning(args ...interface{}) { + logging.print(warningLog, args...) +} + +// WarningDepth acts as Warning but uses depth to determine which call frame to log. +// WarningDepth(0, "msg") is the same as Warning("msg"). +func WarningDepth(depth int, args ...interface{}) { + logging.printDepth(warningLog, depth, args...) +} + +// Warningln logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Warningln(args ...interface{}) { + logging.println(warningLog, args...) +} + +// Warningf logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Warningf(format string, args ...interface{}) { + logging.printf(warningLog, format, args...) +} + +// Error logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Error(args ...interface{}) { + logging.print(errorLog, args...) +} + +// ErrorDepth acts as Error but uses depth to determine which call frame to log. +// ErrorDepth(0, "msg") is the same as Error("msg"). +func ErrorDepth(depth int, args ...interface{}) { + logging.printDepth(errorLog, depth, args...) +} + +// Errorln logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Errorln(args ...interface{}) { + logging.println(errorLog, args...) +} + +// Errorf logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Errorf(format string, args ...interface{}) { + logging.printf(errorLog, format, args...) +} + +// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Fatal(args ...interface{}) { + logging.print(fatalLog, args...) +} + +// FatalDepth acts as Fatal but uses depth to determine which call frame to log. +// FatalDepth(0, "msg") is the same as Fatal("msg"). +func FatalDepth(depth int, args ...interface{}) { + logging.printDepth(fatalLog, depth, args...) +} + +// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Fatalln(args ...interface{}) { + logging.println(fatalLog, args...) +} + +// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Fatalf(format string, args ...interface{}) { + logging.printf(fatalLog, format, args...) +} + +// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. +// It allows Exit and relatives to use the Fatal logs. +var fatalNoStacks uint32 + +// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Exit(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.print(fatalLog, args...) +} + +// ExitDepth acts as Exit but uses depth to determine which call frame to log. +// ExitDepth(0, "msg") is the same as Exit("msg"). +func ExitDepth(depth int, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printDepth(fatalLog, depth, args...) +} + +// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +func Exitln(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.println(fatalLog, args...) +} + +// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Exitf(format string, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printf(fatalLog, format, args...) +} diff --git a/vendor/github.com/golang/glog/glog_file.go b/vendor/github.com/golang/glog/glog_file.go new file mode 100644 index 0000000..65075d2 --- /dev/null +++ b/vendor/github.com/golang/glog/glog_file.go @@ -0,0 +1,124 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// File I/O for logs. + +package glog + +import ( + "errors" + "flag" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" + "sync" + "time" +) + +// MaxSize is the maximum size of a log file in bytes. +var MaxSize uint64 = 1024 * 1024 * 1800 + +// logDirs lists the candidate directories for new log files. +var logDirs []string + +// If non-empty, overrides the choice of directory in which to write logs. +// See createLogDirs for the full list of possible destinations. +var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") + +func createLogDirs() { + if *logDir != "" { + logDirs = append(logDirs, *logDir) + } + logDirs = append(logDirs, os.TempDir()) +} + +var ( + pid = os.Getpid() + program = filepath.Base(os.Args[0]) + host = "unknownhost" + userName = "unknownuser" +) + +func init() { + h, err := os.Hostname() + if err == nil { + host = shortHostname(h) + } + + current, err := user.Current() + if err == nil { + userName = current.Username + } + + // Sanitize userName since it may contain filepath separators on Windows. + userName = strings.Replace(userName, `\`, "_", -1) +} + +// shortHostname returns its argument, truncating at the first period. +// For instance, given "www.google.com" it returns "www". +func shortHostname(hostname string) string { + if i := strings.Index(hostname, "."); i >= 0 { + return hostname[:i] + } + return hostname +} + +// logName returns a new log file name containing tag, with start time t, and +// the name for the symlink for tag. +func logName(tag string, t time.Time) (name, link string) { + name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d", + program, + host, + userName, + tag, + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + pid) + return name, program + "." + tag +} + +var onceLogDirs sync.Once + +// create creates a new log file and returns the file and its filename, which +// contains tag ("INFO", "FATAL", etc.) and t. If the file is created +// successfully, create also attempts to update the symlink for that tag, ignoring +// errors. +func create(tag string, t time.Time) (f *os.File, filename string, err error) { + onceLogDirs.Do(createLogDirs) + if len(logDirs) == 0 { + return nil, "", errors.New("log: no log dirs") + } + name, link := logName(tag, t) + var lastErr error + for _, dir := range logDirs { + fname := filepath.Join(dir, name) + f, err := os.Create(fname) + if err == nil { + symlink := filepath.Join(dir, link) + os.Remove(symlink) // ignore err + os.Symlink(name, symlink) // ignore err + return f, fname, nil + } + lastErr = err + } + return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) +} diff --git a/vendor/github.com/thockin/glogr/LICENSE b/vendor/github.com/thockin/glogr/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/github.com/thockin/glogr/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/thockin/glogr/README.md b/vendor/github.com/thockin/glogr/README.md new file mode 100644 index 0000000..3829b03 --- /dev/null +++ b/vendor/github.com/thockin/glogr/README.md @@ -0,0 +1,8 @@ +# Minimal Go logging using glog + +This package implements the [logr interface](https://github.com/thockin/logr) +in terms of Google's [glog](https://godoc.org/github.com/golang/glog). This +provides a relatively minimalist API to logging in Go, backed by a well-proven +implementation. + +This is a BETA grade implementation. diff --git a/vendor/github.com/thockin/glogr/glogr.go b/vendor/github.com/thockin/glogr/glogr.go new file mode 100644 index 0000000..d2c8534 --- /dev/null +++ b/vendor/github.com/thockin/glogr/glogr.go @@ -0,0 +1,86 @@ +// Package glogr implements github.com/thockin/logr.Logger in terms of +// github.com/golang/glog. +package glogr + +import ( + "fmt" + "runtime" + + "github.com/golang/glog" + "github.com/thockin/logr" +) + +// New returns a logr.Logger which is implemented by glog. +func New() (logr.Logger, error) { + return glogger{ + level: 0, + prefix: "", + }, nil +} + +type glogger struct { + level int + prefix string +} + +func prepend(prefix interface{}, args []interface{}) []interface{} { + return append([]interface{}{prefix}, args...) +} + +// Magic string for intermediate frames that we should ignore. +const autogeneratedFrameName = "" + +// Discover how many frames we need to climb to find the caller. This approach +// was suggested by Ian Lance Taylor of the Go team, so it *should* be safe +// enough (famous last words). +func framesToCaller() int { + // 1 is the immediate caller. 3 should be too many. + for i := 1; i < 3; i++ { + _, file, _, _ := runtime.Caller(i + 1) // +1 for this function's frame + if file != autogeneratedFrameName { + return i + } + } + return 1 // something went wrong, this is safe +} + +func (l glogger) Info(args ...interface{}) { + if l.Enabled() { + glog.InfoDepth(framesToCaller(), prepend(l.prefix, args)...) + } +} + +func (l glogger) Infof(format string, args ...interface{}) { + if l.Enabled() { + glog.InfoDepth(framesToCaller(), fmt.Sprintf("%s"+format, prepend(l.prefix, args)...)) + } +} + +func (l glogger) Enabled() bool { + return bool(glog.V(glog.Level(l.level))) +} + +func (l glogger) Error(args ...interface{}) { + glog.ErrorDepth(framesToCaller(), prepend(l.prefix, args)...) +} + +func (l glogger) Errorf(format string, args ...interface{}) { + glog.ErrorDepth(framesToCaller(), fmt.Sprintf("%s"+format, prepend(l.prefix, args)...)) +} + +func (l glogger) V(level int) logr.InfoLogger { + return glogger{ + level: level, + prefix: l.prefix, + } +} + +func (l glogger) NewWithPrefix(prefix string) logr.Logger { + return glogger{ + level: l.level, + prefix: prefix, + } +} + +var _ logr.Logger = glogger{} +var _ logr.InfoLogger = glogger{} diff --git a/vendor/github.com/thockin/logr/LICENSE b/vendor/github.com/thockin/logr/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/github.com/thockin/logr/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/thockin/logr/README.md b/vendor/github.com/thockin/logr/README.md new file mode 100644 index 0000000..26296d0 --- /dev/null +++ b/vendor/github.com/thockin/logr/README.md @@ -0,0 +1,36 @@ +# A more minimal logging API for Go + +Before you consider this package, please read [this blog post by the inimitable +Dave Cheney](http://dave.cheney.net/2015/11/05/lets-talk-about-logging). I +really appreciate what he has to say, and it largely aligns with my own +experiences. Too many choices of levels means inconsistent logs. + +This package offers a purely abstract interface, based on these ideas but with +a few twists. Code can depend on just this interface and have the actual +logging implementation be injected from callers. Ideally only `main()` knows +what logging implementation is being used. + +# Differences from Dave's ideas + +The main differences are: + +1) Dave basically proposes doing away with the notion of a logging API in favor +of `fmt.Printf()`. I disagree, especially when you consider things like output +locations, timestamps, file and line decorations, and structured logging. I +restrict the API to just 2 types of logs: info and error. + +Info logs are things you want to tell the user which are not errors. Error +logs are, well, errors. If your code receives an `error` from a subordinate +function call and is logging that `error` *and not returning it*, use error +logs. + +2) Verbosity-levels on info logs. This gives developers a chance to indicate +arbitrary grades of importance for info logs, without assigning names with +semantic meaning such as "warning", "trace", and "debug". Superficially this +may feel very similar, but the primary difference is the lack of semantics. +Because verbosity is a numerical value, it's safe to assume that an app running +with higher verbosity means more (and less important) logs will be generated. + +This is a BETA grade API. I have implemented it for +[glog](https://godoc.org/github.com/golang/glog). Until there is a significant +2nd implementation, I don't really know how it will change. diff --git a/vendor/github.com/thockin/logr/logr.go b/vendor/github.com/thockin/logr/logr.go new file mode 100644 index 0000000..cd065be --- /dev/null +++ b/vendor/github.com/thockin/logr/logr.go @@ -0,0 +1,48 @@ +// Package logr defines abstract interfaces for logging. Packages can depend on +// these interfaces and callers can implement logging in whatever way is +// appropriate. +// +// This design derives from Dave Cheney's blog: +// http://dave.cheney.net/2015/11/05/lets-talk-about-logging +// +// This is a BETA grade API. Until there is a significant 2nd implementation, +// I don't really know how it will change. +package logr + +// TODO: consider structured logging, a la uber-go/zap +// TODO: consider other bits of glog functionality like Flush, InfoDepth, OutputStats + +// InfoLogger represents the ability to log non-error messages. +type InfoLogger interface { + // Info logs a non-error message. This is behaviorally akin to fmt.Print. + Info(args ...interface{}) + + // Infof logs a formatted non-error message. + Infof(format string, args ...interface{}) + + // Enabled test whether this InfoLogger is enabled. For example, + // commandline flags might be used to set the logging verbosity and disable + // some info logs. + Enabled() bool +} + +// Logger represents the ability to log messages, both errors and not. +type Logger interface { + // All Loggers implement InfoLogger. Calling InfoLogger methods directly on + // a Logger value is equivalent to calling them on a V(0) InfoLogger. For + // example, logger.Info() produces the same result as logger.V(0).Info. + InfoLogger + + // Error logs a error message. This is behaviorally akin to fmt.Print. + Error(args ...interface{}) + + // Errorf logs a formatted error message. + Errorf(format string, args ...interface{}) + + // V returns an InfoLogger value for a specific verbosity level. A higher + // verbosity level means a log message is less important. + V(level int) InfoLogger + + // NewWithPrefix returns a Logger which prefixes all messages. + NewWithPrefix(prefix string) Logger +} From dd09cac833f2ddba2e1149f38aba4957ae721511 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Wed, 24 Aug 2016 21:52:24 -0700 Subject: [PATCH 13/14] Default dest to leaf of repo --- main.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index c88bc93..a14f3a3 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,7 @@ var flDepth = flag.Int("depth", envInt("GIT_SYNC_DEPTH", 0), var flRoot = flag.String("root", envString("GIT_SYNC_ROOT", "/git"), "root directory for git operations") var flDest = flag.String("dest", envString("GIT_SYNC_DEST", ""), - "path at which to publish the checked-out files (a subdirectory under --root)") + "path at which to publish the checked-out files (a subdirectory under --root, defaults to leaf dir of --root)") var flWait = flag.Int("wait", envInt("GIT_SYNC_WAIT", 0), "number of seconds between syncs") var flOneTime = flag.Bool("one-time", envBool("GIT_SYNC_ONE_TIME", false), @@ -107,10 +107,14 @@ func main() { setFlagDefaults() flag.Parse() - if *flRepo == "" || *flDest == "" { + if *flRepo == "" { flag.Usage() os.Exit(1) } + if *flDest == "" { + parts := strings.Split(strings.Trim(*flRepo, "/"), "/") + *flDest = parts[len(parts)-1] + } if _, err := exec.LookPath("git"); err != nil { log.Errorf("required git executable not found: %v", err) os.Exit(1) From be407edce34111547254a8551a96a8e37dd13fb7 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Wed, 24 Aug 2016 21:53:54 -0700 Subject: [PATCH 14/14] update README --- README.md | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 722c1f4..b534bbd 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,35 @@ # git-sync -git-sync is a command that pull a git repository to a local directory. +git-sync is a simple command that pulls a git repository into a local directory. +It is a perfect "sidecar" container in Kubernetes - it can periodically pull +files down from a repository so that an application can consume them. -It can be used to source a container volume with the content of a git repo. - -In order to ensure that the git repository content is cloned and updated atomically, you cannot use a volume root directory as the directory for your local repository. - -The local repository is created in a subdirectory of /git, with the subdirectory name specified by GIT_SYNC_DEST. +git-sync can pull one time, or on a regular inteval. It can pull from the HEAD +of a branch, or from a git tag, or from a specific git hash. It will only +re-pull if the target of the run has changed in the upstream repository. When +it re-pulls, it updates the destination directory atomically. In order to do +this, it uses a git worktree in a subdirectory of the `--root` and flips a +symlink. ## Usage ``` # build the container -docker build -t yourname/git-sync . -# or -make container +make container REGISTRY=registry TAG=tag -# run the git-sync container +# run the container docker run -d \ - -e GIT_SYNC_REPO=https://github.com/kubernetes/kubernetes \ - -e GIT_SYNC_DEST=/git \ - -e GIT_SYNC_BRANCH=gh-pages \ - -v /git-data:/git \ - git-sync -# run a nginx container to serve sync'ed content -docker run -d -p 8080:80 -v /git-data:/usr/share/nginx/html nginx + -v /tmp/git-data:/git \ + registry/git-sync:tag \ + --repo=https://github.com/kubernetes/git-sync + --branch=master + --wait=30 + +# run an nginx container to serve the content +docker run -d \ + -p 8080:80 \ + -v /tmp/git-data:/usr/share/nginx/html \ + nginx ``` [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/git-sync/README.md?pixel)]()