Skip to content

Commit 8d1e2a1

Browse files
committed
wip
1 parent f1160d2 commit 8d1e2a1

File tree

6 files changed

+298
-0
lines changed

6 files changed

+298
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Container image that runs your code
2+
# FROM python:3-slim AS builder
3+
4+
# # Copies your code file from your action repository to the filesystem path `/` of the container
5+
# ADD . /app
6+
# WORKDIR /app
7+
8+
# RUN pip install --target=/app requests
9+
10+
# Code file to execute when the docker container starts up (`entrypoint.sh`)
11+
# FROM gcr.io/distroless/python3-debian10
12+
FROM registry.access.redhat.com/ubi9/python-312:latest
13+
14+
ADD . /app
15+
WORKDIR /app
16+
17+
RUN pip install requests
18+
19+
# COPY --from=builder /app /app
20+
ENV PYTHONPATH=/app
21+
CMD [ "python", "/app/main.py" ]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Splunk GitHub
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# POST GitHub Workflow Logs to Splunk HTTP Event Collector
2+
test
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# action.yml
2+
name: 'Send Workflow Logs to Splunk'
3+
description: 'Upload GitHub Workflow logs to Splunk HEC'
4+
inputs:
5+
splunk_url:
6+
description: 'Full URL for Splunk HEC endpoint'
7+
required: true
8+
hec_token:
9+
description: 'Splunk HEC Token'
10+
required: true
11+
github_token:
12+
description: 'Github PAT'
13+
required: true
14+
sourcetype:
15+
description: 'Splunk Sourcetype'
16+
default: 'github_workflow_log_action'
17+
source:
18+
description: 'GitHub Workflow name'
19+
default: ${{ github.workflow }}
20+
workflow_id:
21+
description: 'The Workflow Run number'
22+
default: ${{ github.run_number}}
23+
outputs:
24+
status:
25+
description: 'value is success/fail based on POST result'
26+
runs:
27+
using: 'docker'
28+
image: 'Dockerfile'
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import os
2+
import requests
3+
import json
4+
import zipfile
5+
import io
6+
import glob
7+
import re
8+
from datetime import datetime
9+
10+
def main():
11+
GITHUB_REF=os.environ["GITHUB_REF"]
12+
GITHUB_REPOSITORY=os.environ["GITHUB_REPOSITORY"]
13+
GITHUB_RUN_ID=os.environ["GITHUB_RUN_ID"]
14+
GITHUB_API_URL=os.environ["GITHUB_API_URL"]
15+
GITHUB_WORKFLOWID=os.environ["INPUT_WORKFLOW_ID"]
16+
GITHUB_TOKEN = os.environ.get("INPUT_GITHUB_TOKEN")
17+
18+
SPLUNK_HEC_URL=os.environ["INPUT_SPLUNK_URL"]+"services/collector/event"
19+
SPLUNK_HEC_TOKEN=os.environ["INPUT_HEC_TOKEN"]
20+
SPLUNK_SOURCE=os.environ["INPUT_SOURCE"]
21+
SPLUNK_SOURCETYPE=os.environ["INPUT_SOURCETYPE"]
22+
23+
batch = count = 0
24+
eventBatch = ""
25+
headers = {"Authorization": "Splunk "+SPLUNK_HEC_TOKEN}
26+
host=os.uname()[1]
27+
28+
summary_url = "{url}/repos/{repo}/actions/runs/{run_id}".format(url=GITHUB_API_URL,repo=GITHUB_REPOSITORY,run_id=GITHUB_WORKFLOWID)
29+
30+
print("######################")
31+
print(f"GITHUB_REF: {GITHUB_REF}")
32+
print(f"GITHUB_REPOSITORY: {GITHUB_REPOSITORY}")
33+
print(f"GITHUB_RUN_ID: {GITHUB_RUN_ID}")
34+
print(f"GITHUB_API_URL: {GITHUB_API_URL}")
35+
print(f"GITHUB_WORKFLOWID: {GITHUB_WORKFLOWID}")
36+
print(f"GITHUB_TOKEN: {GITHUB_TOKEN}")
37+
print(f"SPLUNK_HEC_URL: {SPLUNK_HEC_URL}")
38+
print(f"SPLUNK_HEC_TOKEN: {SPLUNK_HEC_TOKEN}")
39+
print(f"SPLUNK_SOURCE: {SPLUNK_SOURCE}")
40+
print(f"SPLUNK_SOURCETYPE: {SPLUNK_SOURCETYPE}")
41+
print(f"host: {host}")
42+
print(f"headers: {headers}")
43+
print(f"summary_url: {summary_url}")
44+
print("######################")
45+
for key, value in os.environ.items():
46+
print(f'{key}={value}')
47+
print("######################")
48+
49+
try:
50+
x = requests.get(summary_url, stream=True, auth=('token',GITHUB_TOKEN))
51+
x.raise_for_status()
52+
except requests.exceptions.HTTPError as errh:
53+
output = "GITHUB API Http Error:" + str(errh)
54+
print(f"Error: {output}")
55+
print(f"::set-output name=result::{output}")
56+
return x.status_code
57+
except requests.exceptions.ConnectionError as errc:
58+
output = "GITHUB API Error Connecting:" + str(errc)
59+
print(f"Error: {output}")
60+
print(f"::set-output name=result::{output}")
61+
return x.status_code
62+
except requests.exceptions.Timeout as errt:
63+
output = "Timeout Error:" + str(errt)
64+
print(f"Error: {output}")
65+
print(f"::set-output name=result::{output}")
66+
return x.status_code
67+
except requests.exceptions.RequestException as err:
68+
output = "GITHUB API Non catched error conecting:" + str(err)
69+
print(f"Error: {output}")
70+
print(f"::set-output name=result::{output}")
71+
return x.status_code
72+
except Exception as e:
73+
print("Internal error", e)
74+
return x.status_code
75+
76+
summary = x.json()
77+
78+
summary.pop('repository')
79+
80+
summary["repository"]=summary["head_repository"]["name"]
81+
summary["repository_full"]=summary["head_repository"]["full_name"]
82+
83+
summary.pop('head_repository')
84+
85+
utc_time = datetime.strptime(summary["updated_at"], "%Y-%m-%dT%H:%M:%SZ")
86+
epoch_time = (utc_time - datetime(1970, 1, 1)).total_seconds()
87+
88+
event={'event':json.dumps(summary),'sourcetype':SPLUNK_SOURCETYPE,'source':'workflow_summary','host':host,'time':epoch_time}
89+
event=json.dumps(event)
90+
91+
#x=requests.post(SPLUNK_HEC_URL, data=event, headers=headers)
92+
93+
94+
url = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/actions/runs/{GITHUB_WORKFLOWID}/logs"
95+
print(url)
96+
97+
try:
98+
x = requests.get(url, stream=True, auth=('token',GITHUB_TOKEN))
99+
100+
except requests.exceptions.HTTPError as errh:
101+
output = "GITHUB API Http Error:" + str(errh)
102+
print(f"Error: {output}")
103+
print(f"::set-output name=result::{output}")
104+
return
105+
except requests.exceptions.ConnectionError as errc:
106+
output = "GITHUB API Error Connecting:" + str(errc)
107+
print(f"Error: {output}")
108+
print(f"::set-output name=result::{output}")
109+
return
110+
except requests.exceptions.Timeout as errt:
111+
output = "Timeout Error:" + str(errt)
112+
print(f"Error: {output}")
113+
print(f"::set-output name=result::{output}")
114+
return
115+
except requests.exceptions.RequestException as err:
116+
output = "GITHUB API Non catched error conecting:" + str(err)
117+
print(f"Error: {output}")
118+
print(f"::set-output name=result::{output}")
119+
return
120+
121+
z = zipfile.ZipFile(io.BytesIO(x.content))
122+
# z.extractall('/app')
123+
#log_folder = '/Users/ykoer/Workspace/ykoer/github-actions-example-workflows/.github/actions/log_to_splunk/tmp'
124+
log_folder = '/tmp'
125+
z.extractall(log_folder)
126+
127+
timestamp = batch = count = 0
128+
129+
for name in glob.glob(f'{log_folder}/*.txt'):
130+
if os.path.basename(name).startswith('-'):
131+
continue
132+
logfile = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), name.replace('./','')),'r')
133+
lines = logfile.readlines()
134+
count = 0
135+
batch_number = 1
136+
for line in lines:
137+
if line:
138+
count+=1
139+
if timestamp:
140+
t2=timestamp
141+
timestamp = re.search(r"\d{4}-\d{2}-\d{2}T\d+:\d+:\d+\.\d+Z", line.strip())
142+
143+
if timestamp:
144+
timestamp = re.sub(r"\dZ","",timestamp.group())
145+
timestamp = datetime.strptime(timestamp,"%Y-%m-%dT%H:%M:%S.%f")
146+
timestamp = (timestamp - datetime(1970,1,1)).total_seconds()
147+
else:
148+
timestamp=t2
149+
150+
# find empty lines and skip them
151+
x = re.sub(r"\d{4}-\d{2}-\d{2}T\d+:\d+:\d+.\d+Z","",line.strip())
152+
x=x.strip()
153+
job_name=re.search(r"\/\d+\_(?P<job>.*)\.txt",name)
154+
job_name=job_name.group('job')
155+
fields = {'github_run_id':GITHUB_RUN_ID,'github_workflow_id':GITHUB_WORKFLOWID,'github_job_name':job_name,'line_number':count}
156+
if x:
157+
batch+=1
158+
event={'event':x,'sourcetype':SPLUNK_SOURCETYPE,'source':SPLUNK_SOURCE,'host':host,'time':timestamp,'fields':fields}
159+
eventBatch=eventBatch+json.dumps(event)
160+
# else:
161+
# print("skipped line "+str(count))
162+
163+
# push every 1000 log lines to splunk as a batch
164+
if batch>=1000:
165+
print(f'log_file={name}, batch_number={batch_number}, line_number={count}')
166+
batch=0
167+
168+
# x=requests.post(SPLUNK_HEC_URL, data=eventBatch, headers=headers)
169+
# print(f'log_file={name}, batch_number={batch_number}, line_number={count}, request_status_code:{x.status_code}')
170+
eventBatch=""
171+
batch_number+=1
172+
break
173+
174+
# push the last batch
175+
if batch>0:
176+
print(f'log_file={name}, batch_number={batch_number}, line_number={count}')
177+
# x=requests.post(SPLUNK_HEC_URL, data=eventBatch, headers=headers)
178+
# print(f'log_file={name}, batch_number={batch_number}, line_number={count}, request_status_code:{x.status_code}')
179+
eventBatch=""
180+
batch_number+=1
181+
182+
if __name__ == '__main__':
183+
main()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Send Workflow Logs to Splunk
2+
3+
# Controls when the action will run.
4+
on:
5+
workflow_dispatch:
6+
workflow_run:
7+
workflows: ["*"]
8+
types:
9+
- completed
10+
11+
env:
12+
triggerID: ${{ github.event.workflow_run.id }}
13+
triggerJob: ${{ github.event.workflow_run.name }}
14+
15+
jobs:
16+
WriteLogs:
17+
runs-on: ubuntu-latest
18+
# if: ${{ github.event.workflow_run.name!='WriteLogs'}}
19+
20+
steps:
21+
- name: Debug Workflow Information
22+
run: |
23+
echo "Trigger ID: ${{ env.triggerID }}"
24+
echo "Trigger Job: ${{ env.triggerJob }}"
25+
echo "Event type: ${{ github.event_name }}"
26+
echo "Workflow ID that triggered this: ${{ github.event.workflow_run.id }}"
27+
echo "Workflow Name that triggered this: ${{ github.event.workflow_run.name }}"
28+
echo "Workflow file: ${{ github.event.workflow_run.path }}"
29+
30+
- uses: actions/checkout@v2
31+
32+
- name: Output Job ID
33+
run: echo ${{ github.event.workflow_run.id }}
34+
35+
- name: Send Workflow logs to Splunk
36+
if: ${{ always() }}
37+
uses: ./.github/actions/log_to_splunk
38+
with:
39+
splunk_url: ${{ vars.HEC_URL }}
40+
hec_token: ${{ secrets.HEC_TOKEN }}
41+
github_token: ${{ secrets.GITHUB_TOKEN }}
42+
workflow_id: ${{ env.triggerID }}
43+
source: ${{ env.triggerJob }}

0 commit comments

Comments
 (0)