Skip to content

Commit 486d3f7

Browse files
arschlesAaron Schlesinger
authored andcommitted
feat(gitreceive): begin go-based git-receive hook
not feature complete
1 parent 2e6b237 commit 486d3f7

5 files changed

Lines changed: 356 additions & 0 deletions

File tree

gitreceive/build.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package main
2+
3+
// #!/usr/bin/env bash
4+
// #
5+
// # builder hook called on every git receive-pack
6+
// # NOTE: this script must be run as root (for docker access)
7+
// #
8+
// set -eo pipefail
9+
//
10+
// ARGS=3
11+
// HOST=`ifconfig eth0 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'`
12+
// indent() {
13+
// echo " $@"
14+
// }
15+
//
16+
// puts-step() {
17+
// echo "-----> $@"
18+
// }
19+
//
20+
// puts-step-sameline() {
21+
// echo -n "-----> $@"
22+
// }
23+
//
24+
// puts-warn() {
25+
// echo " ! $@"
26+
// }
27+
//
28+
// usage() {
29+
// echo "Usage: $0 <user> <repo> <sha>"
30+
// }
31+
//
32+
// parse-string(){
33+
// # helper to avoid the single quote escape
34+
// # occurred in command substitution
35+
// local args=() idx=0 IFS=' ' c
36+
// for c; do printf -v args[idx++] '%s ' "$c"; done
37+
// printf "%s\n" "${args[*]}"
38+
// }
39+
//
40+
// if [ $# -ne $ARGS ]; then
41+
// usage
42+
// exit 1
43+
// fi
44+
//
45+
// USER=$1
46+
// REPO=$2
47+
// GIT_SHA=$3
48+
// SHORT_SHA=${GIT_SHA:0:8}
49+
// APP_NAME="${REPO%.*}"
50+
//
51+
// cd $(dirname $0) # ensure we are in the root dir
52+
//
53+
// ROOT_DIR=$(pwd)
54+
// REPO_DIR="${ROOT_DIR}/${REPO}"
55+
// BUILD_DIR="${REPO_DIR}/build"
56+
// CACHE_DIR="${REPO_DIR}/cache"
57+
//
58+
// # define image names
59+
// SLUG_NAME="$APP_NAME:git-$SHORT_SHA"
60+
// META_NAME=`echo ${SLUG_NAME}| tr ":" "-"`
61+
// TMP_IMAGE="$DEIS_REGISTRY_SERVICE_HOST:$DEIS_REGISTRY_SERVICE_PORT/$IMAGE_NAME"
62+
// # create app directories
63+
// mkdir -p $BUILD_DIR $CACHE_DIR
64+
// # create temporary directory inside the build dir for this push
65+
// TMP_DIR=$(mktemp -d -p $BUILD_DIR)
66+
//
67+
// cd $REPO_DIR
68+
// # use Procfile if provided, otherwise try default process types from ./release
69+
// git archive --format=tar.gz ${GIT_SHA} > ${APP_NAME}.tar.gz
70+
// tar -xzf ${APP_NAME}.tar.gz -C $TMP_DIR/
71+
// USING_DOCKERFILE=true
72+
// if [ -f $TMP_DIR/Procfile ]; then
73+
// PROCFILE=$(cat $TMP_DIR/Procfile | yaml2json-procfile)
74+
// USING_DOCKERFILE=false
75+
// else
76+
// PROCFILE="{}"
77+
// fi
78+
//
79+
// if [[ ! -f /var/run/secrets/object/store/access-key-id ]]; then
80+
// if $USING_DOCKERFILE ; then
81+
// l1=`grep -n "object-store" /etc/deis-dockerbuilder.yaml | head -n1 |cut -d ":" -f1`
82+
// l2=$(($l1+3))
83+
// sed "$l1,$l2 d" /etc/deis-dockerbuilder.yaml > /etc/${SLUG_NAME}.yaml.tmp
84+
// l1=`grep -n "object-store" /etc/deis-dockerbuilder.yaml.tmp | head -n1 |cut -d ":" -f1`
85+
// l2=$(($l1+3))
86+
// sed "$l1,$l2 d" /etc/${SLUG_NAME}.yaml.tmp > /etc/${SLUG_NAME}.yaml
87+
// sed -i -- "s#repo_name#$TMP_IMAGE#g" /etc/${SLUG_NAME}.yaml
88+
// else
89+
// head -n 21 /etc/deis-slugbuilder.yaml > /etc/${SLUG_NAME}.yaml
90+
// fi
91+
// else
92+
// if $USING_DOCKERFILE ; then
93+
// cp /etc/deis-dockerbuilder.yaml /etc/${SLUG_NAME}.yaml
94+
// sed -i -- "s#repo_name#$TMP_IMAGE#g" /etc/${SLUG_NAME}.yaml
95+
// else
96+
// cp /etc/deis-slugbuilder.yaml /etc/${SLUG_NAME}.yaml
97+
// fi
98+
// fi
99+
//
100+
// git archive --format=tar.gz ${GIT_SHA} > ${APP_NAME}.tar.gz
101+
//
102+
// HTTP_PREFIX="http"
103+
// REMOTE_STORAGE="0"
104+
// # if minio is in the cluster, use it. otherwise use fetcher
105+
// # TODO: figure out something for using S3 also
106+
// if [[ -n "$DEIS_MINIO_SERVICE_HOST" && -n "$DEIS_MINIO_SERVICE_PORT" ]]; then
107+
// S3EP=${DEIS_MINIO_SERVICE_HOST}:${DEIS_MINIO_SERVICE_PORT}
108+
// REMOTE_STORAGE="1"
109+
// elif [[ -n "$DEIS_OUTSIDE_STORAGE_HOST" && -n "$DEIS_OUTSIDE_STORAGE_PORT" ]]; then
110+
// HTTP_PREFIX="https"
111+
// S3EP=${DEIS_OUTSIDE_STORAGE_HOST}:${DEIS_OUTSIDE_STORAGE_PORT}
112+
// REMOTE_STORAGE="1"
113+
// elif [ -z "$S3EP" ]; then
114+
// S3EP=${HOST}:3000
115+
// fi
116+
//
117+
// TAR_URL=$HTTP_PREFIX://$S3EP/git/home/${SLUG_NAME}/tar
118+
// PUSH_URL=$HTTP_PREFIX://$S3EP/git/home/${SLUG_NAME}/push
119+
//
120+
// sed -i -- "s#repo_name#$META_NAME#g" /etc/${SLUG_NAME}.yaml
121+
// sed -i -- "s#puturl#$PUSH_URL#g" /etc/${SLUG_NAME}.yaml
122+
// sed -i -- "s#tar-url#$TAR_URL#g" /etc/${SLUG_NAME}.yaml
123+
//
124+
// ACCESS_KEY=`cat /var/run/secrets/object/store/access-key-id`
125+
// ACCESS_SECRET=`cat /var/run/secrets/object/store/access-secret-key`
126+
// # copy the self signed cert into the CA directory for alpine.
127+
// # note: we're not running minio with SSL at all right now, so no need for this.
128+
// # future SSL rollouts for in-cluster storage may not need it either if we set up an intermediate CA
129+
// # CERT_FILE="/var/run/secrets/object/ssl/access-cert"
130+
// # cp $CERT_FILE /etc/ssl/certs/deis-minio-self-signed-cert.crt
131+
// mkdir -p /var/minio-conf
132+
// CONFIG_DIR=/var/minio-conf
133+
// MC_PREFIX="mc -C $CONFIG_DIR --quiet"
134+
// $MC_PREFIX config host add "$HTTP_PREFIX://$S3EP" $ACCESS_KEY $ACCESS_SECRET &>/dev/null
135+
// $MC_PREFIX mb "$HTTP_PREFIX://${S3EP}/git" &>/dev/null
136+
// $MC_PREFIX cp ${APP_NAME}.tar.gz $TAR_URL &>/dev/null
137+
//
138+
// puts-step "Starting build"
139+
// kubectl --namespace=${POD_NAMESPACE} create -f /etc/${SLUG_NAME}.yaml >/dev/null
140+
//
141+
// # wait for pod to be running and then pull its logs
142+
// until [ "`kubectl --namespace=${POD_NAMESPACE} get pods -o yaml ${META_NAME} | grep "phase: " | awk {'print $2'}`" == "Running" ]; do
143+
// sleep 0.1
144+
// done
145+
// kubectl --namespace=${POD_NAMESPACE} logs -f ${META_NAME} 2>/dev/null &
146+
//
147+
// #check for image creation or slug existence in S3EP
148+
//
149+
// if [[ "$REMOTE_STORAGE" == "1" ]]; then
150+
// LS_CMD="$MC_PREFIX ls $PUSH_URL"
151+
// until $LS_CMD &> /dev/null; do
152+
// echo -ne "."
153+
// sleep 2
154+
// done
155+
// else
156+
// while [ ! -f /apps/${SLUG_NAME}/slug.tgz ]
157+
// do
158+
// echo -ne "."
159+
// sleep 2
160+
// done
161+
// fi
162+
//
163+
// # build completed
164+
//
165+
// puts-step "Build complete."
166+
// puts-step "Launching app."
167+
//
168+
// URL="http://$DEIS_WORKFLOW_SERVICE_HOST:$DEIS_WORKFLOW_SERVICE_PORT/v2/hooks/config"
169+
// RESPONSE=$(get-app-config -url="$URL" -key="{{ getv "/deis/controller/builderKey" }}" -user=$USER -app=$APP_NAME)
170+
// CODE=$?
171+
// if [ $CODE -ne 0 ]; then
172+
// puts-warn $RESPONSE
173+
// exit 1
174+
// fi
175+
//
176+
// # use Procfile if provided, otherwise try default process types from ./release
177+
//
178+
// puts-step "Launching... "
179+
// URL="http://$DEIS_WORKFLOW_SERVICE_HOST:$DEIS_WORKFLOW_SERVICE_PORT/v2/hooks/build"
180+
// DATA="$(generate-buildhook "$SHORT_SHA" "$USER" "$APP_NAME" "$APP_NAME" "$PROCFILE" "$USING_DOCKERFILE")"
181+
// PUBLISH_RELEASE=$(echo "$DATA" | publish-release-controller -url=$URL -key={{ getv "/deis/controller/builderKey" }})
182+
// CODE=$?
183+
// if [ $CODE -ne 0 ]; then
184+
// puts-warn "ERROR: Failed to launch container"
185+
// puts-warn $PUBLISH_RELEASE
186+
// exit 1
187+
// fi
188+
//
189+
// RELEASE=$(echo $PUBLISH_RELEASE | extract-version)
190+
// DOMAIN=$(echo $PUBLISH_RELEASE | extract-domain)
191+
// indent "done, $APP_NAME:v$RELEASE deployed to Deis"
192+
// echo
193+
// indent "http://$DOMAIN"
194+
// echo
195+
// indent "To learn more, use \`deis help\` or visit http://deis.io"
196+
// echo
197+
//
198+
// # cleanup
199+
// cd $REPO_DIR
200+
// git gc &>/dev/null

gitreceive/config.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"github.com/kelseyhightower/envconfig"
5+
)
6+
7+
type config struct {
8+
WorkflowHost string `envconfig:"deis_workflow_service_host"`
9+
WorkflowPort string `envconfig:"deis_workflow_service_port"`
10+
GitHome string `envconfig:"git_home"`
11+
SSHConnection string `envconfig:"ssh_connection"`
12+
SSHOriginalCommand string `envconfig:"ssh_original_command"`
13+
Repository string `envconfig:"repository"`
14+
SHA string `envconfing:"sha"`
15+
Username string `envconfig:"username"`
16+
App string `envconfing:"app"`
17+
Fingerprint string `envconfing:"fingerprint"`
18+
}
19+
20+
func getConfig(appName string) (*config, error) {
21+
conf := &config{}
22+
if err := envconfig(appName, conf); err != nil {
23+
return nil, err
24+
}
25+
return conf, nil
26+
}

gitreceive/main.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"github.com/helm/helm/log"
7+
"io"
8+
"os"
9+
)
10+
11+
// #!/bin/bash
12+
// strip_remote_prefix() {
13+
// stdbuf -i0 -o0 -e0 sed "s/^/"$'\e[1G'"/"
14+
// }
15+
//
16+
// while read oldrev newrev refname
17+
// do
18+
// LOCKFILE="/tmp/$RECEIVE_REPO.lock"
19+
// if ( set -o noclobber; echo "$$" > "$LOCKFILE" ) 2> /dev/null; then
20+
// trap 'rm -f "$LOCKFILE"; exit 1' INT TERM EXIT
21+
//
22+
// # check for authorization on this repo
23+
// {{.GitHome}}/receiver "$RECEIVE_REPO" "$newrev" "$RECEIVE_USER" "$RECEIVE_FINGERPRINT"
24+
// rc=$?
25+
// if [[ $rc != 0 ]] ; then
26+
// echo " ERROR: failed on rev $newrev - push denied"
27+
// exit $rc
28+
// fi
29+
// # builder assumes that we are running this script from $GITHOME
30+
// cd {{.GitHome}}
31+
// # if we're processing a receive-pack on an existing repo, run a build
32+
// if [[ $SSH_ORIGINAL_COMMAND == git-receive-pack* ]]; then
33+
// {{.GitHome}}/builder "$RECEIVE_USER" "$RECEIVE_REPO" "$newrev" 2>&1 | strip_remote_prefix
34+
// fi
35+
//
36+
// rm -f "$LOCKFILE"
37+
// trap - INT TERM EXIT
38+
// else
39+
// echo "Another git push is ongoing. Aborting..."
40+
// exit 1
41+
// fi
42+
// done
43+
44+
const newline = "\n"
45+
46+
// readLine reads from bio until it reaches a "\n". returns the line, not including the "\n"
47+
func getLine(bio *bufio.Reader) (string, error) {
48+
line, err := bio.ReadString(newline)
49+
if err != nil {
50+
return "", err
51+
}
52+
if strings.HasSuffix(line, newline) {
53+
return line[0 : len(line)-len(newline)]
54+
}
55+
}
56+
57+
func readLine(line string) (string, string, string, error) {
58+
spl := strings.Split(line, " ")
59+
if len(spl) != 3 {
60+
return "", "", "", fmt.Errorf("malformed line [%s]", line)
61+
}
62+
return spl[0], spl[1], spl[2], nil
63+
}
64+
65+
func main() {
66+
conf, err := getConfig("gitreceive")
67+
if err != nil {
68+
log.Msg("config error [%s]", err)
69+
os.Exit(1)
70+
}
71+
bio := bufio.NewReader(os.Stdin)
72+
for line, err := getLine(bio); err != nil; {
73+
oldRev, newRev, refName, err := readLine(line)
74+
if err := receive(conf, newRev); err != nil {
75+
log.Die("failed on rev %s - push denied", newRev)
76+
os.Exit(1)
77+
}
78+
if err := build(conf, newRev); err != nil {
79+
log.Die("error building %s [%s]", newRev, err)
80+
os.Exit(1)
81+
}
82+
}
83+
}

gitreceive/receive.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
)
7+
8+
// #!/usr/bin/env bash
9+
// set -eo pipefail
10+
//
11+
// repository=$1
12+
// app=${1%.git}
13+
// sha=$2
14+
// username=$3
15+
// fingerprint=$4
16+
//
17+
// curl \
18+
// -X 'POST' --fail \
19+
// -H 'Content-Type: application/json' \
20+
// -H "X-Deis-Builder-Auth: {{ getv "/deis/controller/builderKey" }}" \
21+
// -d "{\"receive_user\": \"$username\", \"receive_repo\": \"$app\", \"sha\": \"$sha\", \"fingerprint\": \"$fingerprint\", \"ssh_connection\": \"$SSH_CONNECTION\", \"ssh_original_command\": \"$SSH_ORIGINAL_COMMAND\"}" \
22+
// --silent "http://$DEIS_WORKFLOW_SERVICE_HOST:$DEIS_WORKFLOW_SERVICE_PORT/v2/hooks/push" >/dev/null
23+
24+
func receive(conf *config, newRev string) error {
25+
urlStr := fmt.Sprintf("http://%s:%s/v2/hooks/push", conf.WorkflowHost, conf.WorkflowPort)
26+
bodyMap := map[string]string{
27+
"receive_user": conf.User,
28+
"receive_repo": conf.App,
29+
"sha": conf.SHA,
30+
"fingerprint": conf.Fingerprint,
31+
"ssh_connection": conf.SSHConnection,
32+
"ssh_original_command": conf.SSHOriginalCommand,
33+
}
34+
var body bytes.Buffer
35+
if err := json.NewEncoder().Encode(&body, bodyMap); err != nil {
36+
return err
37+
}
38+
req, err := http.NewRequest("POST", urlStr, &body)
39+
if err != nil {
40+
return err
41+
}
42+
resp, err := http.Do(req)
43+
if err != nil {
44+
return err
45+
}
46+
}

glide.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ import:
2020
subpackages:
2121
- /ssh
2222
- package: gopkg.in/yaml.v2
23+
- package: github.com/helm/helm/log

0 commit comments

Comments
 (0)