feat: use go sdk

This commit is contained in:
Louis Seubert 2024-11-01 23:41:40 +01:00
parent a849295b46
commit b4b7a00f37
Signed by: louis9902
GPG key ID: 4B9DB28F826553BD
8 changed files with 213 additions and 162 deletions

21
Dockerfile Normal file
View file

@ -0,0 +1,21 @@
FROM docker.io/golang:1.23-alpine3.20 AS compile
ENV CGO_ENABLED=0
WORKDIR /app
COPY go.mod go.sum /app/
RUN go mod download
COPY *.go /app/
RUN go build -o app .
FROM docker.io/alpine:3.20
WORKDIR /
RUN apk add git
COPY --from=compile /app/app /
ENTRYPOINT ["/app"]

View file

View file

@ -1,20 +1 @@
# checkout # checkout
This action checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
Only a single commit is fetched by default, for the ref/sha that triggered the workflow. The auth token
is persisted in the local git config. This enables your scripts to run authenticated git commands.
This action is only written in shell (bash).
## Usage
```yaml
uses: actions/checkout:1.0.0
with:
repository: ${{ github.server_url }}/${{ github.repository }}.git
token: ${{ github.token }}
path: .
ref: ${{ github.ref || github.sha }}
```

View file

@ -1,21 +1,17 @@
# SPDX-License-Identifier: EUPL-1.2 # SPDX-License-Identifier: EUPL-1.2
name: "Checkout" name: "Checkout"
description: "Checkout a Git repository at a particular version" description: "Checkout a Git repository at a particular version"
author: "Louis Seubert"
inputs: inputs:
repository: repository:
description: > description: >
Repository name with owner. For example, actions/checkout The path to the repository to checkout. Must be accessible with the
default: ${{ github.server_url }}/${{ github.repository }}.git pipeline token on publiclly
token: default: "${{ github.server_url }}/${{ github.repository }}.git"
description: >
Personal access token (PAT) used to fetch the repository. The PAT is configured
with the local git config, which enables your scripts to run authenticated git
commands.
default: ${{ github.token }}
path: path:
description: > description: >
Relative path under $GITHUB_WORKSPACE to place the repository Relative path under $GITHUB_WORKSPACE to place the repository
default: . default: "."
ref: ref:
description: > description: >
The branch, tag or SHA to checkout. When checking out the repository that The branch, tag or SHA to checkout. When checking out the repository that
@ -23,15 +19,5 @@ inputs:
event. Otherwise, uses the default branch. event. Otherwise, uses the default branch.
default: ${{ github.ref || github.sha }} default: ${{ github.ref || github.sha }}
runs: runs:
using: composite using: 'docker'
steps: image: 'Dockerfile'
- name: Checkout
shell: bash
run: |
export PATH=${{ github.action_path }}/scripts:$PATH
checkout.sh
env:
ACTION_INPUT_TOKEN: ${{ inputs.token }}
ACTION_INPUT_REPOSITORY: ${{ inputs.repository }}
ACTION_INPUT_PATH: ${{ inputs.path }}
ACTION_INPUT_REF: ${{ inputs.ref }}

5
go.mod Normal file
View file

@ -0,0 +1,5 @@
module git.geekeey.de/actions/checkout
go 1.23.2
require git.geekeey.de/actions/sdk v1.0.1

6
go.sum Normal file
View file

@ -0,0 +1,6 @@
git.geekeey.de/actions/sdk v0.0.0-20241020125724-76a93c0d8cec h1:auq6X3d0N25UPSyjuvRDh55W4sgAsSaeDGdWOS6AG6g=
git.geekeey.de/actions/sdk v0.0.0-20241020125724-76a93c0d8cec/go.mod h1:pBcHd6afsvseZF9hSc2A0b6+MldC/Ch6CSDaVlWMAmA=
git.geekeey.de/actions/sdk v1.0.0 h1:vVEPz6ndFJt4iqtJhhFuf7XA3bf2gasZJ3f8gb94o5g=
git.geekeey.de/actions/sdk v1.0.0/go.mod h1:pBcHd6afsvseZF9hSc2A0b6+MldC/Ch6CSDaVlWMAmA=
git.geekeey.de/actions/sdk v1.0.1 h1:fecr+IILPgKFoMaQDtSvkBbaFfudOlf+2KdMVKcFA1M=
git.geekeey.de/actions/sdk v1.0.1/go.mod h1:pBcHd6afsvseZF9hSc2A0b6+MldC/Ch6CSDaVlWMAmA=

173
main.go Normal file
View file

@ -0,0 +1,173 @@
package main
import (
"context"
"encoding/base64"
"fmt"
"net/url"
"os"
"os/exec"
"os/signal"
"strings"
"git.geekeey.de/actions/sdk"
)
func main() {
action := &CheckoutAction{Action: sdk.New()}
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := action.run(ctx); err != nil {
action.Errorf("%s", err)
os.Exit(1)
}
}
type CheckoutAction struct {
*sdk.Action
repository string
path string
reference string
}
func (action *CheckoutAction) setup() error {
var err error
action.repository = action.GetInput("repository")
action.path = action.GetInput("path")
action.reference = action.GetInput("ref")
return err
}
func (action *CheckoutAction) run(ctx context.Context) error {
if err := action.setup(); err != nil {
return err
}
env := action.Context()
var sh *Shell
sh = &Shell{}
sh.Add("git", "config", "--global", "--add", "protocol.version", "2")
sh.Add("git", "init", "-q", action.path)
sh.Add("git", "-C", action.path, "config", "--local", "gc.auto", "0")
if err := sh.Run(ctx); err != nil {
return err
}
action.Noticef("Configuring git repository credentials")
server, err := url.Parse(env.ServerURL)
if err != nil {
return err
}
sh = &Shell{}
sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", env.ServerURL), fmt.Sprintf("git@%s", server.Host))
sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", env.ServerURL), fmt.Sprintf("ssh://git@%s", server.Host))
sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", env.ServerURL), fmt.Sprintf("git://%s", server.Host))
if err := sh.Run(ctx); err != nil {
return err
}
enc := base64.StdEncoding
header := fmt.Sprintf("Authorization: Basic %s", enc.EncodeToString([]byte(fmt.Sprintf("x-access-token:%s", env.Token))))
sh = &Shell{}
sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("http.%s.extraheader", env.ServerURL), header)
if err := sh.Run(ctx); err != nil {
return err
}
origin := "origin"
sh = &Shell{}
sh.Add("git", "-C", action.path, "remote", "add", origin, action.repository)
if err := sh.Run(ctx); err != nil {
return err
}
stdout, _, err := execx(ctx, "git", "-C", action.path, "ls-remote", origin, action.reference)
if err != nil {
return err
}
lines := strings.Split(stdout, "\t")
if len(lines) < 2 {
return fmt.Errorf("git ls-remore resolved nothing")
}
sha := strings.TrimSpace(lines[0])
ref := strings.TrimSpace(lines[1])
sh = &Shell{}
if strings.HasPrefix(ref, "refs/heads/") {
name := strings.TrimPrefix(ref, "refs/heads/")
remote := fmt.Sprintf("refs/remotes/%s/%s", origin, name)
branch := name
action.Debugf("Checking out branch %s@%s", name, sha)
sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1",
origin, fmt.Sprintf("+%s:%s", sha, remote))
sh.Add("git", "-C", action.path, "checkout", "--force", "-B", branch, remote)
} else if strings.HasPrefix(ref, "refs/pull/") {
name := strings.TrimPrefix(ref, "refs/pull/")
remote := fmt.Sprintf("refs/remotes/pull/%s", name)
action.Debugf("Checking out pull-request %s@%s", name, sha)
var branch string
if len(env.BaseRef) != 0 {
branch = env.BaseRef
} else if len(env.HeadRef) != 0 {
branch = env.HeadRef
} else {
return fmt.Errorf("pull request can not find base ref for branch")
}
sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1",
origin, fmt.Sprintf("+%s:%s", sha, remote))
sh.Add("git", "-C", action.path, "checkout", "--force", "-B", branch, remote)
} else if strings.HasPrefix(ref, "refs/tags/") {
name := strings.TrimPrefix(ref, "refs/tags/")
remote := fmt.Sprintf("refs/tags/%s", name)
action.Debugf("Checking out ptag %s@%s", name, sha)
sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1",
origin, fmt.Sprintf("+%s:%s", sha, remote))
sh.Add("git", "-C", action.path, "checkout", "--force", remote)
} else {
action.Debugf("Checking out detached head %s", ref)
sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1",
origin, ref)
sh.Add("git", "-C", action.path, "checkout", "--force", ref)
}
if err := sh.Run(ctx); err != nil {
return err
}
return nil
}
type Shell [][]string
func (sh *Shell) Add(line ...string) { *sh = append([][]string(*sh), line) }
func (sh *Shell) Run(ctx context.Context) error {
for _, line := range [][]string(*sh) {
if _, _, err := execx(ctx, line...); err != nil {
return err
}
}
return nil
}
func execx(ctx context.Context, args ...string) (string, string, error) {
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
var outbuf, errbuf strings.Builder
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
err := cmd.Run()
return outbuf.String(), errbuf.String(), err
}

View file

@ -1,121 +0,0 @@
#!/usr/bin/env bash
shopt -s extglob
ACTION_INPUT_TOKEN="${ACTION_INPUT_TOKEN:?}"
ACTION_INPUT_REPOSITORY="${ACTION_INPUT_REPOSITORY:?}"
ACTION_INPUT_PATH="${ACTION_INPUT_PATH:?}"
ACTION_INPUT_REF="${ACTION_INPUT_REF:?}"
error() {
echo "$*" 1>&2 ; exit 1;
}
# ensure we are in the right place
[[ -z "${GITHUB_WORKSPACE:-}" ]] || { cd "${GITHUB_WORKSPACE}" || error ; }
git config --global --add protocol.version 2
git init -q "${ACTION_INPUT_PATH}" && { cd "${ACTION_INPUT_PATH}" || error ; }
git config --local gc.auto 0
# the server url and uri are important for determining the default checkout url
GITHUB_SERVER_URL="${GITHUB_SERVER_URL:?}"
GITHUB_SERVER_URI="${GITHUB_SERVER_URL//http?(s):\/\//}"
# use https instead of everything else
git config --add url."${GITHUB_SERVER_URL}/".insteadOf "git@${GITHUB_SERVER_URI}:"
git config --add url."${GITHUB_SERVER_URL}/".insteadOf "ssh://git@${GITHUB_SERVER_URI}/"
git config --add url."${GITHUB_SERVER_URL}/".insteadOf "git://${GITHUB_SERVER_URI}/"
# prepare git config extra header for https auth
BASE64="base64 --wrap 0"
echo "" | ${BASE64} >/dev/null 2>&1 || BASE64="base64"
echo "" | ${BASE64} >/dev/null 2>&1 || BASE64="openssl base64 -A"
echo "" | ${BASE64} >/dev/null 2>&1
GIT_HTTP_EXTRAHEADER="AUTHORIZATION: basic $(echo -n "x-access-token:${ACTION_INPUT_TOKEN}" | ${BASE64})"
git config --local --add http."${GITHUB_SERVER_URL}".extraheader "${GIT_HTTP_EXTRAHEADER}"
# add the repository as remote
git remote add origin "${ACTION_INPUT_REPOSITORY}"
git_default_refspec() {
git ls-remote --symref origin HEAD | { read -r _ ref name ; echo "${ref}" ; }
}
git_resolve_refspec() {
# shellcheck disable=SC2068
git ls-remote origin $@ | { read -r sha ref _ ; echo "${sha} ${ref}" ; }
}
fetch() {
# shellcheck disable=SC2068
git fetch --no-tags --prune --no-recurse-submodules --depth=1 origin $@
}
checkout() {
# shellcheck disable=SC2068
git checkout --force $@
}
# the checkout is based on a commit hash
if [[ ${ACTION_INPUT_REF:-} =~ ^[0-9a-f]{5,40}$ ]]
then
fetch "${ACTION_INPUT_REF}" && checkout "${ACTION_INPUT_REF}"
exit
fi
# update selected ref when no input is given to remote default branch
: "${ACTION_INPUT_REF:=$( git_default_refspec )}"
# TODO: check if repo is workflow repo, if so, use commit sha from env
# like GITHUB_REF and GITHUB_SHA
read -r GIT_SHA GIT_REF _ <<< "$( git_resolve_refspec "${ACTION_INPUT_REF}" )"
: "${GIT_SHA:?}" "${GIT_REF:?}"
# we always use the refspec with the commit sha as source to prevent
# race conditions when running on a frequently used branch
checkout_head() {
local name="${GIT_REF#refs\/heads\/}"
local remote="refs/remotes/origin/${name}"
fetch "+${GIT_SHA}:${remote}"
checkout -B "${name}" "${remote}"
}
checkout_pull() {
local name="${GIT_REF#refs\/pull\/}"
local remote="refs/remotes/pull/${name}"
fetch "+${GIT_SHA}:${remote}"
# pull requests have a special treatment for the branch name. The name
# is determined by the branch the pull request is targeting.
local branch="${GITHUB_BASE_REF:-$( git_default_refspec )}"
checkout -B "${branch#refs\/heads\/}" "${remote}"
}
checkout_tag() {
local name="${GIT_REF#refs\/tags\/}"
local remote="refs/tags/${name}"
fetch "+${GIT_SHA}:${remote}"
checkout "${remote}"
}
case "${GIT_REF}" in
# heads and pull are a branch based checkout
refs/heads/*) checkout_head ;;
refs/pull/* ) checkout_pull ;;
# tags are a detached head checkout
refs/tags/* ) checkout_tag ;;
*) error "ref type '${GIT_REF}' is unknown" ;;
esac