Merge pull request #1 from thockin/master

Move git-sync to a new repo of its very own
This commit is contained in:
Mike Danese 2016-08-22 09:27:38 -07:00 committed by GitHub
commit 2ed0e3fcba
23 changed files with 815 additions and 0 deletions

21
Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM gcr.io/google_containers/ubuntu-slim:0.1
ENV GIT_SYNC_DEST /git
VOLUME ["/git"]
RUN apt-get update && \
apt-get install -y git ca-certificates --no-install-recommends && \
apt-get install -y openssh-client && \
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
RUN chmod 755 /usr/bin/ssh
RUN mkdir /nonexistent && chmod 777 /nonexistent
ENTRYPOINT ["/git-sync"]

8
Godeps/Godeps.json generated Normal file
View File

@ -0,0 +1,8 @@
{
"ImportPath": "k8s.io/git-sync",
"GoVersion": "go1.5",
"Packages": [
"./..."
],
"Deps": []
}

5
Godeps/Readme generated Normal file
View File

@ -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.

2
Godeps/_workspace/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
/pkg
/bin

17
Makefile Normal file
View File

@ -0,0 +1,17 @@
all: push
# 0.0 shouldn't clobber any released builds
TAG = 0.0
PREFIX = gcr.io/google_containers/git-sync
binary: main.go
CGO_ENABLED=0 GOOS=linux godep go build -a -installsuffix cgo -ldflags '-w' -o git-sync
container: binary
docker build -t $(PREFIX):$(TAG) .
push: container
gcloud docker push $(PREFIX):$(TAG)
clean:
docker rmi -f $(PREFIX):$(TAG) || true

4
OWNERS Normal file
View File

@ -0,0 +1,4 @@
assignees:
- mikedanese
- paulbakker

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# git-sync
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.
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 .
# 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
# run a nginx container to serve sync'ed content
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)]()

33
demo/README.md Normal file
View File

@ -0,0 +1,33 @@
# git-blog-demo
This demo shows how to use the `git-sync` sidekick container along side `volumes` and `volumeMounts` to create a markdown powered blog.
## How it works
The pod is composed of 3 containers that share directories using 2 volumes:
- The `git-sync` container clones a git repo into the `markdown` volume
- The `hugo` container read from the `markdown` volume and render it into the `html` volume.
- The `nginx` container serve the content from the `html` volume.
## Usage
Build the demo containers, and push them to a registry
```
docker build -t <some-registry>/git-sync ..
docker build -t <some-registry>/hugo hugo/
docker push <some-registry>/hugo <some-registry>/git-sync
```
Create the pod and the service for the blog
```
kubectl pods create config/pod.html
kubectl services create config/pod.html
```
Open the service external ip in your browser
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/git-sync/demo/README.md?pixel)]()

View File

3
demo/blog/config.toml Normal file
View File

@ -0,0 +1,3 @@
baseurl = "http://example.com"
languageCode = "en-us"
title = "example blog"

View File

@ -0,0 +1,12 @@
+++
date = "2014-12-19T15:29:48-08:00"
draft = true
title = "about"
+++
## A headline
Some content about the blog.
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/git-sync/demo/blog/content/about.md?pixel)]()

View File

@ -0,0 +1,12 @@
+++
date = "2014-12-19T15:30:18-08:00"
draft = true
title = "first"
+++
## first port
This is the first post.
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/git-sync/demo/blog/content/post/first.md?pixel)]()

0
demo/blog/layouts/.keep Normal file
View File

0
demo/blog/static/.keep Normal file
View File

46
demo/config/pod.yaml Normal file
View File

@ -0,0 +1,46 @@
apiVersion: v1
kind: Pod
metadata:
labels:
name: blog
name: blog-pod
spec:
containers:
- name: git-sync
image: gcr.io/google_containers/git-sync
imagePullPolicy: Always
volumeMounts:
- name: markdown
mountPath: /git
env:
- name: GIT_SYNC_REPO
value: https://github.com/GoogleCloudPlatform/kubernetes.git
- name: GIT_SYNC_DEST
value: /git
- name: hugo
image: gcr.io/google_containers/hugo
imagePullPolicy: Always
volumeMounts:
- name: markdown
mountPath: /src
- name: html
mountPath: /dest
env:
- name: HUGO_SRC
value: /src/git-sync/demo/blog
- name: HUGO_BUILD_DRAFT
value: "true"
- name: HUGO_BASE_URL
value: example.com
- name: nginx
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumes:
- name: markdown
emptyDir: {}
- name: html
emptyDir: {}

10
demo/config/service.yaml Normal file
View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: blog-service
spec:
type: "LoadBalancer"
ports:
- port: 80
selector:
name: blog

26
demo/hugo/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
# Copyright 2014 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.
FROM golang
RUN go get -v github.com/spf13/hugo
RUN git clone --recursive https://github.com/spf13/hugoThemes.git /themes
VOLUME ["/src", "/dest"]
EXPOSE 1313
ENV HUGO_SRC /src
ENV HUGO_DEST /dest
ENV HUGO_THEME hyde
ENV HUGO_BUILD_DRAFT false
ENV HUGO_BASE_URL ""
ADD run-hugo /run-hugo
ENTRYPOINT ["/run-hugo"]
CMD ["server", "--source=${HUGO_SRC}", "--theme=${HUGO_THEME}", "--buildDrafts=${HUGO_BUILD_DRAFT}", "--baseUrl=${HUGO_BASE_URL}", "--watch", "--destination=${HUGO_DEST}", "--appendPort=false"]

15
demo/hugo/README.md Normal file
View File

@ -0,0 +1,15 @@
# hugo
`hugo` is a container that convert markdown into html using [hugo static site generator](http://gohugo.io/).
## Usage
```
# build the container
docker build -t hugo .
# run the hugo container
docker run -e HUGO_BASE_URL=example.com -v /path/to/md:/src -v /path/to/html:/dest hugo
```
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/git-sync/demo/hugo/README.md?pixel)]()

21
demo/hugo/run-hugo Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
# Copyright 2014 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.
set -ex
if [ ! -d ${HUGO_SRC}/themes ]; then
ln -s /themes ${HUGO_SRC}/themes
fi
hugo $(eval echo $*) # force default CMD env expansion

78
docs/ssh.md Normal file
View File

@ -0,0 +1,78 @@
# Using SSH with git-sync
Git-sync supports using the SSH protocol for pulling git content.
## Step 1: Create Secret
Create a Secret to store your SSH private key, with the Secret keyed as "ssh". This can be done one of two ways:
***Method 1:***
Use the ``kubectl create secret`` command and point to the file on your filesystem that stores the key. Ensure that the file is mapped to "ssh" as shown (the file can be located anywhere).
```
kubectl create secret generic git-creds --from-file=ssh=~/.ssh/id_rsa
```
***Method 2:***
Write a config file for a Secret that holds your SSH private key, with the key (pasted as plaintext) mapped to the "ssh" field.
```
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "git-creds"
},
"data": {
"ssh": <private-key>
}
```
Create the Secret using ``kubectl create -f``.
```
kubectl create -f /path/to/secret-config.json
```
## Step 2: Configure Pod/Deployment Volume
In your Pod or Deployment configuration, specify a Volume for mounting the Secret. Ensure that secretName matches the name you used when creating the Secret (e.g. "git-creds" used in both above examples).
```
volumes: [
{
"name": "git-secret",
"secret": {
"secretName": "git-creds"
}
},
...
],
```
## Step 3: Configure git-sync container
In your git-sync container configuration, mount the Secret Volume at "/etc/git-secret". Ensure that the environment variable GIT_SYNC_REPO is set to use a URL with the SSH protocol, and set GIT_SYNC_SSH to true.
```
{
name: "git-sync",
...
env: [
{
name: "GIT_SYNC_REPO",
value: "git@github.com:kubernetes/kubernetes.git",
}, {
name: "GIT_SYNC_SSH",
value: "true",
},
...
]
volumeMounts: [
{
"name": "git-secret",
"mountPath": "/etc/git-secret"
},
...
],
}
```
**Note: Do not mount the Secret Volume with "readOnly: true".** Kubernetes mounts the Secret with permissions 0444 by default (not restrictive enough to be used as an SSH key), so the container runs a chmod command on the Secret. Mounting the Secret Volume as a read-only filesystem prevents chmod and thus prevents the use of the Secret as an SSH key.
***TODO***: Remove the chmod command once Kubernetes allows for specifying permissions for a Secret Volume. See https://github.com/kubernetes/kubernetes/pull/28936.

355
main.go Normal file
View File

@ -0,0 +1,355 @@
/*
Copyright 2014 The Kubernetes Authors 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.
*/
// git-sync is a command that pull a git repository to a local directory.
package main // import "k8s.io/git-sync"
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"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 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 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)`)
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
}
return def
}
func envBool(key string, def bool) bool {
if env := os.Getenv(key); env != "" {
res, err := strconv.ParseBool(env)
if err != nil {
return def
}
return res
}
return def
}
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)
return def
}
return val
}
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() {
flag.Parse()
if *flRepo == "" || *flDest == "" {
flag.Usage()
log.Fatal(usage)
}
if _, err := exec.LookPath("git"); err != nil {
log.Fatalf("required git executable not found: %v", err)
}
if *flUsername != "" && *flPassword != "" {
if err := setupGitAuth(*flUsername, *flPassword, *flRepo); err != nil {
log.Fatalf("error creating .netrc file: %v", err)
}
}
if *flSSH {
if err := setupGitSSH(); err != nil {
log.Fatalf("error configuring SSH: %v", err)
}
}
initialSync := true
failCount := 0
for {
if err := syncRepo(*flRepo, *flDest, *flBranch, *flRev, *flDepth); err != nil {
if initialSync || failCount >= *flMaxSyncFailures {
log.Fatalf("error syncing repo: %v", err)
}
failCount++
log.Printf("unexpected error syncing repo: %v", err)
log.Printf("waiting %d seconds before retryng", *flWait)
time.Sleep(time.Duration(*flWait) * time.Second)
continue
}
initialSync = false
failCount = 0
if *flOneTime {
os.Exit(0)
}
log.Printf("waiting %d seconds", *flWait)
time.Sleep(time.Duration(*flWait) * time.Second)
log.Println("done")
}
}
// updateSymlink atomically swaps the symlink to point at the specified directory and cleans up the previous worktree.
func updateSymlink(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))
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)
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 {
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 {
return fmt.Errorf("error replacing symlink: %v", err)
}
log.Printf("rename symlink %v to %v", "tmp-link", link)
// Clean up previous worktree
if len(currentDir) > 0 {
if err = os.RemoveAll(currentDir); err != nil {
return fmt.Errorf("error removing directory: %v", err)
}
log.Printf("remove %v", currentDir)
output, err := runCommand("git", volMount, []string{"worktree", "prune"})
if err != nil {
return err
}
log.Printf("worktree prune %v", string(output))
}
return nil
}
// 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 {
// fetch branch
output, err := runCommand("git", volMount, []string{"fetch", "origin", branch})
if err != nil {
return err
}
log.Printf("fetch %q: %s", branch, string(output))
// 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})
if err != nil {
return err
}
log.Printf("add worktree origin/%q: %v", branch, string(output))
// .git file in worktree directory holds a reference to /git/.git/worktrees/<worktree-dir-name>
// 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)
if err != nil {
return err
}
gitDirRef := []byte(path.Join("gitdir: ../.git/worktrees", worktreePathRelative) + "\n")
if err = ioutil.WriteFile(path.Join(worktreePath, ".git"), gitDirRef, 0644); err != nil {
return err
}
// reset working copy
output, err = runCommand("git", worktreePath, []string{"reset", "--hard", rev})
if err != nil {
return err
}
log.Printf("reset %q: %v", rev, string(output))
if *flChmod != 0 {
// set file permissions
_, err = runCommand("chmod", "", []string{"-R", string(*flChmod), worktreePath})
if err != nil {
return err
}
}
return updateSymlink(dest, worktreePath)
}
func initRepo(repo, dest, branch, rev string, depth int) error {
// clone repo
args := []string{"clone", "--no-checkout", "-b", branch}
if depth != 0 {
args = append(args, "-depth")
args = append(args, string(depth))
}
args = append(args, repo)
args = append(args, volMount)
output, err := runCommand("git", "", args)
if err != nil {
return err
}
log.Printf("clone %q: %s", repo, string(output))
return nil
}
// 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)
gitRepoPath := path.Join(target, ".git")
_, err := os.Stat(gitRepoPath)
switch {
case os.IsNotExist(err):
err = initRepo(repo, target, branch, rev, depth)
if err != nil {
return err
}
case err != nil:
return fmt.Errorf("error checking if repo exist %q: %v", gitRepoPath, err)
default:
needUpdate, err := gitRemoteChanged(target, branch)
if err != nil {
return err
}
if !needUpdate {
log.Printf("No change")
return nil
}
}
return addWorktreeAndSwap(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("git", localDir, []string{"remote", "update"})
if err != nil {
return false, err
}
localHead, err := runCommand("git", localDir, []string{"rev-parse", "HEAD"})
if err != nil {
return false, err
}
remoteHead, err := runCommand("git", localDir, []string{"rev-parse", fmt.Sprintf("origin/%v", branch)})
if err != nil {
return false, err
}
return (strings.Compare(string(localHead), string(remoteHead)) != 0), nil
}
func runCommand(command, cwd string, args []string) ([]byte, error) {
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 output, nil
}
func setupGitAuth(username, password, gitURL string) error {
log.Println("setting up the git credential cache")
cmd := exec.Command("git", "config", "--global", "credential.helper", "cache")
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error setting up git credentials %v: %s", err, string(output))
}
cmd = exec.Command("git", "credential", "approve")
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
creds := fmt.Sprintf("url=%v\nusername=%v\npassword=%v\n", gitURL, username, password)
io.Copy(stdin, bytes.NewBufferString(creds))
stdin.Close()
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error setting up git credentials %v: %s", err, string(output))
}
return nil
}
func setupGitSSH() error {
log.Println("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)
}
// Kubernetes mounts Secret as 0444 by default, which is not restrictive enough to use as an SSH key.
// TODO: Remove this command once Kubernetes allows for specifying permissions for a Secret Volume.
// See https://github.com/kubernetes/kubernetes/pull/28936.
if err := os.Chmod("/etc/git-secret/ssh", 0400); err != nil {
// If the Secret Volume is mounted as readOnly, the read-only filesystem nature prevents the necessary chmod.
return fmt.Errorf("error running chmod on Secret (make sure Secret Volume is NOT mounted with readOnly=true): %v", err)
}
return nil
}

100
main_test.go Normal file
View File

@ -0,0 +1,100 @@
/*
Copyright 2015 The Kubernetes Authors 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 main
import (
"os"
"testing"
)
const (
testKey = "KEY"
)
func TestEnvBool(t *testing.T) {
cases := []struct {
value string
def bool
exp bool
}{
{"true", true, true},
{"true", false, true},
{"", true, true},
{"", false, false},
{"false", true, false},
{"false", false, false},
{"", true, true},
{"", false, false},
{"no true", true, true},
{"no false", true, true},
}
for _, testCase := range cases {
os.Setenv(testKey, testCase.value)
val := envBool(testKey, testCase.def)
if val != testCase.exp {
t.Fatalf("expected %v but %v returned", testCase.exp, val)
}
}
}
func TestEnvString(t *testing.T) {
cases := []struct {
value string
def string
exp string
}{
{"true", "true", "true"},
{"true", "false", "true"},
{"", "true", "true"},
{"", "false", "false"},
{"false", "true", "false"},
{"false", "false", "false"},
{"", "true", "true"},
{"", "false", "false"},
}
for _, testCase := range cases {
os.Setenv(testKey, testCase.value)
val := envString(testKey, testCase.def)
if val != testCase.exp {
t.Fatalf("expected %v but %v returned", testCase.exp, val)
}
}
}
func TestEnvInt(t *testing.T) {
cases := []struct {
value string
def int
exp int
}{
{"0", 1, 0},
{"", 0, 0},
{"-1", 0, -1},
{"abcd", 0, 0},
{"abcd", 1, 1},
}
for _, testCase := range cases {
os.Setenv(testKey, testCase.value)
val := envInt(testKey, testCase.def)
if val != testCase.exp {
t.Fatalf("expected %v but %v returned", testCase.exp, val)
}
}
}

25
ssh-wrapper.sh Normal file
View File

@ -0,0 +1,25 @@
#!/bin/sh
# Copyright 2016 The Kubernetes Authors 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.
# This script wraps the standard SSH binary so that the mounted SSH key can be used without user confirmation.
# In the Dockerfile, the original SSH binary is moved to /usr/bin/ssh-binary (and is then used as the base command here).
# This script is moved to /usr/bin/ssh so that Git uses it by default.
# The "UserKnownHostsFile" and "StrictHostKeyChecking" options avoid the user confirmation check.
# The -i flag specifies where the SSH key is located.
secret_path=/etc/git-secret/ssh
ssh-binary -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $secret_path "$@"