Skip to content

Commit 82df0fc

Browse files
committed
initial commit
1 parent a160569 commit 82df0fc

File tree

7 files changed

+1665
-0
lines changed

7 files changed

+1665
-0
lines changed

.github/workflows/test.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
on: [push]
2+
3+
jobs:
4+
test-otlp-output:
5+
runs-on: ubuntu-latest
6+
name: A job to test OTLP output
7+
steps:
8+
# To use this repository's private action,
9+
# you must check out the repository
10+
- name: Checkout
11+
uses: actions/checkout@v2
12+
- name: Send data to OTLP backend
13+
uses: codeboten/github-action-to-otlp
14+
with:
15+
endpoint: "ingest.lightstep.com:443"
16+
env:
17+
LS_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM golang:latest
2+
WORKDIR /app
3+
COPY go.mod go.sum ./
4+
RUN go mod download
5+
COPY main.go .
6+
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o github-action-to-otlp .
7+
8+
FROM alpine:latest
9+
RUN apk --no-cache add ca-certificates
10+
WORKDIR /root/
11+
COPY --from=0 /app/github-action-to-otlp .
12+
ENTRYPOINT ["/root/github-action-to-otlp"]

action.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: "Github Action to OTLP"
2+
description: "Record job information as trace and transmit via OTLP"
3+
inputs:
4+
endpoint: # endpoint of OTLP backend
5+
description: "Endpoint to send OTLP to"
6+
required: true
7+
# headers: # endpoint of OTLP backend
8+
# description: "Headers to configure OTLP"
9+
# required: true
10+
runs:
11+
using: "docker"
12+
image: "Dockerfile"
13+
env:
14+
OTEL_ENDPOINT: ${{ inputs.endpoint }}

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/codeboten/github-action-to-otlp
2+
3+
go 1.15
4+
5+
require (
6+
github.com/google/go-github/v39 v39.0.0
7+
github.com/lightstep/otel-launcher-go v1.0.0-RC3
8+
github.com/stretchr/testify v1.7.0
9+
go.opentelemetry.io/otel v1.0.0-RC3
10+
go.opentelemetry.io/otel/trace v1.0.0-RC3
11+
)

go.sum

Lines changed: 1455 additions & 0 deletions
Large diffs are not rendered by default.

main.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"log"
8+
"os"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/google/go-github/v39/github"
13+
"github.com/lightstep/otel-launcher-go/launcher"
14+
"go.opentelemetry.io/otel"
15+
"go.opentelemetry.io/otel/codes"
16+
"go.opentelemetry.io/otel/trace"
17+
)
18+
19+
type actionConfig struct {
20+
workflow string
21+
githubRepository string
22+
owner string
23+
repo string
24+
runID string
25+
}
26+
27+
// TODO: add attributes using https://docs.github.com/en/actions/learn-github-actions/environment-variables
28+
// TODO: add user-agent
29+
// TODO: add support for auth
30+
31+
func getSteps(ctx context.Context, conf actionConfig) error {
32+
tracer := otel.Tracer(conf.githubRepository)
33+
client := github.NewClient(nil)
34+
id, err := strconv.ParseInt(conf.runID, 10, 64)
35+
if err != nil {
36+
return err
37+
}
38+
workflow, _, err := client.Actions.GetWorkflowRunByID(ctx, conf.owner, conf.repo, id)
39+
if err != nil {
40+
return err
41+
}
42+
43+
ctx, workflowSpan := tracer.Start(ctx, *workflow.Name, trace.WithTimestamp(workflow.CreatedAt.Time))
44+
jobs, _, err := client.Actions.ListWorkflowJobs(context.Background(), conf.owner, conf.repo, id, &github.ListWorkflowJobsOptions{})
45+
if err != nil {
46+
return err
47+
}
48+
49+
for _, job := range jobs.Jobs {
50+
ctx, jobSpan := tracer.Start(ctx, *job.Name, trace.WithTimestamp(job.StartedAt.Time))
51+
if err != nil {
52+
return err
53+
}
54+
for _, step := range job.Steps {
55+
_, stepSpan := tracer.Start(ctx, *step.Name, trace.WithTimestamp(step.StartedAt.Time))
56+
stepSpan.End(trace.WithTimestamp(step.CompletedAt.Time))
57+
}
58+
jobSpan.End(trace.WithTimestamp(job.CompletedAt.Time))
59+
}
60+
workflowSpan.End(trace.WithTimestamp(workflow.UpdatedAt.Time))
61+
return nil
62+
}
63+
64+
func parseConfig() (actionConfig, error) {
65+
githubRepository, ok := os.LookupEnv("GITHUB_REPOSITORY")
66+
if !ok {
67+
return actionConfig{}, errors.New("missing variable: GITHUB_REPOSITORY")
68+
}
69+
70+
runID, ok := os.LookupEnv("GITHUB_RUN_ID")
71+
if !ok {
72+
return actionConfig{}, errors.New("missing variable: GITHUB_RUN_ID")
73+
}
74+
75+
workflowName, ok := os.LookupEnv("GITHUB_WORKFLOW")
76+
if !ok {
77+
return actionConfig{}, errors.New("missing variable: GITHUB_WORKFLOW")
78+
}
79+
80+
parts := strings.Split(githubRepository, "/")
81+
if len(parts) < 2 {
82+
return actionConfig{}, fmt.Errorf("invalid variable GITHUB_REPOSITORY: %s", githubRepository)
83+
}
84+
conf := actionConfig{
85+
workflow: workflowName,
86+
githubRepository: githubRepository,
87+
owner: parts[0],
88+
repo: parts[1],
89+
runID: runID,
90+
}
91+
92+
return conf, nil
93+
}
94+
95+
func main() {
96+
97+
conf, err := parseConfig()
98+
if err != nil {
99+
log.Fatal(err)
100+
}
101+
lsOtel := launcher.ConfigureOpentelemetry(
102+
launcher.WithServiceName(conf.githubRepository),
103+
)
104+
defer lsOtel.Shutdown()
105+
tracer := otel.Tracer(conf.githubRepository)
106+
ctx, span := tracer.Start(context.Background(), conf.workflow)
107+
defer span.End()
108+
109+
if err != nil {
110+
log.Printf("%v", err)
111+
}
112+
113+
err = getSteps(ctx, conf)
114+
if err != nil {
115+
span.SetStatus(codes.Error, err.Error())
116+
log.Println(err)
117+
}
118+
}

main_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestParseConfig(t *testing.T) {
11+
_, err := parseConfig()
12+
require.Error(t, err)
13+
require.Contains(t, err.Error(), "missing variable: GITHUB_REPOSITORY")
14+
15+
os.Setenv("GITHUB_REPOSITORY", "garbage")
16+
17+
_, err = parseConfig()
18+
require.Error(t, err)
19+
require.Contains(t, err.Error(), "missing variable: GITHUB_JOB")
20+
21+
os.Setenv("GITHUB_JOB", "123")
22+
23+
_, err = parseConfig()
24+
require.Error(t, err)
25+
require.Contains(t, err.Error(), "missing variable: GITHUB_WORKFLOW")
26+
27+
os.Setenv("GITHUB_WORKFLOW", "test name")
28+
29+
_, err = parseConfig()
30+
require.Error(t, err)
31+
require.Contains(t, err.Error(), "invalid variable GITHUB_REPOSITORY: garbage")
32+
33+
os.Setenv("GITHUB_REPOSITORY", "test/code")
34+
conf, err := parseConfig()
35+
require.NoError(t, err)
36+
require.Equal(t, "test", conf.owner)
37+
require.Equal(t, "code", conf.repo)
38+
}

0 commit comments

Comments
 (0)