Compare commits
19 Commits
3dff28add1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
cc640700a4
|
|||
|
a84fc769ac
|
|||
|
2b00dcc305
|
|||
|
cc526eb592
|
|||
|
b9616bb19e
|
|||
|
63e72559be
|
|||
|
0b543c5b70
|
|||
|
4de3431e50
|
|||
|
1c9b946e49
|
|||
|
373532d739
|
|||
|
dc978900b8
|
|||
|
08947259ae
|
|||
|
766dc118f9
|
|||
|
b2f68ef9b8
|
|||
|
e39bae3131
|
|||
|
86e21ce2d3
|
|||
|
33b019f694
|
|||
|
d632af5c8a
|
|||
|
f06500dd17
|
@@ -1,6 +1,6 @@
|
||||
variables:
|
||||
- &file Containerfile
|
||||
- &repo dev.shielddagger.com/infra/discord-notifier
|
||||
- &repo dev.shielddagger.com/opensource/discord-notifier
|
||||
|
||||
when:
|
||||
- event: [push, pull_request]
|
||||
@@ -15,8 +15,6 @@ steps:
|
||||
- echo ${CI_COMMIT_SHA:0:8} > .version
|
||||
- name: dryrun
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
environment:
|
||||
IMAGE_REPO_URL: *repo
|
||||
backend_options:
|
||||
kubernetes:
|
||||
securityContext:
|
||||
@@ -24,7 +22,7 @@ steps:
|
||||
settings:
|
||||
dockerfile: *file
|
||||
platforms: linux/arm64,linux/amd64
|
||||
cache_from: type=registry,ref=${IMAGE_REPO_URL}
|
||||
cache_from: type=registry,ref=dev.shielddagger.com/opensource/discord-notifier
|
||||
cache_to: type=inline
|
||||
dry_run: true
|
||||
repo: *repo
|
||||
@@ -45,7 +43,7 @@ steps:
|
||||
settings:
|
||||
dockerfile: *file
|
||||
platforms: linux/arm64,linux/amd64
|
||||
cache_from: type=registry,ref=dev.shielddagger.com/infra/discord-notifier
|
||||
cache_from: type=registry,ref=dev.shielddagger.com/opensource/discord-notifier
|
||||
cache_to: type=inline
|
||||
repo: *repo
|
||||
auto_tag: true
|
||||
@@ -58,6 +56,21 @@ steps:
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
- name: gather-digests
|
||||
image: quay.io/skopeo/stable:latest
|
||||
environment:
|
||||
DOCKER_USER:
|
||||
from_secret: registry_username
|
||||
DOCKER_PASS:
|
||||
from_secret: registry_password
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
commands:
|
||||
- dnf install -y jq
|
||||
- skopeo login dev.shielddagger.com --username $DOCKER_USER --password $DOCKER_PASS
|
||||
- skopeo inspect --raw docker://dev.shielddagger.com/opensource/discord-notifier:latest | jq -r .'manifests[] | select(.platform.architecture=="arm64").digest' > digest-arm64
|
||||
- skopeo inspect --raw docker://dev.shielddagger.com/opensource/discord-notifier:latest | jq -r .'manifests[] | select(.platform.architecture=="amd64").digest' > digest-amd64
|
||||
- name: image-scan
|
||||
image: aquasec/trivy
|
||||
environment:
|
||||
@@ -65,13 +78,17 @@ steps:
|
||||
from_secret: registry_username
|
||||
TRIVY_PASSWORD:
|
||||
from_secret: registry_password
|
||||
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db
|
||||
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db
|
||||
TRIVY_CHECKS_BUNDLE_REPOSITORY: public.ecr.aws/aquasecurity/trivy-checks
|
||||
commands:
|
||||
- trivy image dev.shielddagger.com/infra/discord-notifier --exit-code 1 --username $TRIVY_USER --severity HIGH,CRITICAL
|
||||
- export ARM64_DIGEST=$(cat digest-arm64)
|
||||
- trivy image --platform linux/arm64 --debug dev.shielddagger.com/opensource/discord-notifier@$ARM64_DIGEST --exit-code 1 --username $TRIVY_USER --severity HIGH,CRITICAL
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
- name: notify
|
||||
image: dev.shielddagger.com/infra/discord-notifier
|
||||
image: dev.shielddagger.com/opensource/discord-notifier
|
||||
failure: ignore
|
||||
settings:
|
||||
webhook_url:
|
||||
@@ -79,7 +96,7 @@ steps:
|
||||
woodpecker_url: https://ci.shielddagger.com/api
|
||||
woodpecker_token:
|
||||
from_secret: woodpecker_token
|
||||
icon_url: https://dev.shielddagger.com/repo-avatars/273e88fa2afde290121dc7b5987dc366b88325f147bf1e5766bca26296bbc1f9
|
||||
icon_url: https://discord.com/api/webhooks/1231848304694919270/1ApQzOPMfNosxhQ62HbYScBT5s94m0bIUn1IFGQlT6d8Ru2ImcHHjjkFA_SaonBNU3yz
|
||||
when:
|
||||
- status: [success, failure]
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ steps:
|
||||
- name: scan-repo
|
||||
depends_on: []
|
||||
image: aquasec/trivy
|
||||
environment:
|
||||
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db
|
||||
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db
|
||||
TRIVY_CHECKS_BUNDLE_REPOSITORY: public.ecr.aws/aquasecurity/trivy-checks
|
||||
commands:
|
||||
- trivy repo . --exit-code 1
|
||||
when:
|
||||
@@ -12,12 +16,16 @@ steps:
|
||||
- name: scan-conf
|
||||
depends_on: []
|
||||
image: aquasec/trivy
|
||||
environment:
|
||||
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db
|
||||
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db
|
||||
TRIVY_CHECKS_BUNDLE_REPOSITORY: public.ecr.aws/aquasecurity/trivy-checks
|
||||
commands:
|
||||
- trivy config . --exit-code 1
|
||||
when:
|
||||
- event: [push, pull_request]
|
||||
- name: notify
|
||||
image: dev.shielddagger.com/infra/discord-notifier
|
||||
image: dev.shielddagger.com/opensource/discord-notifier
|
||||
failure: ignore
|
||||
depends_on:
|
||||
- scan-repo
|
||||
@@ -28,6 +36,6 @@ steps:
|
||||
woodpecker_url: https://ci.shielddagger.com/api
|
||||
woodpecker_token:
|
||||
from_secret: woodpecker_token
|
||||
icon_url: https://dev.shielddagger.com/repo-avatars/273e88fa2afde290121dc7b5987dc366b88325f147bf1e5766bca26296bbc1f9
|
||||
icon_url: https://dev.shielddagger.com/repo-avatars/1a648399aed58ef1c440bde43fc3f0f98f237a8d8f68074febe98517a86e1887
|
||||
when:
|
||||
- status: [success, failure]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.12 as builder
|
||||
FROM python:3.12 AS builder
|
||||
|
||||
RUN pip install poetry
|
||||
|
||||
@@ -14,7 +14,7 @@ RUN touch README.md
|
||||
|
||||
RUN --mount=type=cache,target=$POETRY_CACHE_DIR poetry install --without dev --no-root
|
||||
|
||||
FROM python:3.12-slim as runtime
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
RUN pip install poetry
|
||||
|
||||
|
||||
30
poetry.lock
generated
30
poetry.lock
generated
@@ -1,14 +1,14 @@
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.2.2"
|
||||
version = "2024.8.30"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
|
||||
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
|
||||
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
|
||||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -129,24 +129,24 @@ async = ["httpx (>=0.23.0,<0.24.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.7"
|
||||
version = "3.8"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
{file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"},
|
||||
{file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.31.0"
|
||||
version = "2.32.3"
|
||||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
||||
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -161,13 +161,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
|
||||
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
||||
@@ -10,6 +10,13 @@ from urllib.parse import urljoin, urlparse
|
||||
from requests import Session
|
||||
from requests.models import Response
|
||||
|
||||
logfile_webhook = DiscordWebhook(environ["PLUGIN_WEBHOOK_URL"], rate_limit_retry=True)
|
||||
logfile_webhook.username = "Woodpecker CI"
|
||||
logfile_webhook.avatar_url = "https://ci.shielddagger.com/favicons/favicon-dark-error.png"
|
||||
|
||||
webhook = DiscordWebhook(environ["PLUGIN_WEBHOOK_URL"], rate_limit_retry=True)
|
||||
webhook.username = "Woodpecker CI"
|
||||
|
||||
|
||||
class APISession(Session):
|
||||
def __init__(self, base_url) -> None:
|
||||
@@ -19,13 +26,44 @@ class APISession(Session):
|
||||
def request(self, method: str | bytes, url: str | bytes, *args, **kwargs) -> Response:
|
||||
url = urljoin(self.base_url, url)
|
||||
return super().request(method, url, *args, **kwargs)
|
||||
try:
|
||||
ci_client = APISession(environ["PLUGIN_WOODPECKER_URL"].rstrip("/"))
|
||||
ci_client.headers.setdefault("Authorization", f"Bearer {environ["PLUGIN_WOODPECKER_TOKEN"]}")
|
||||
except KeyError:
|
||||
webhook.add_embed(DiscordEmbed(
|
||||
"API Error",
|
||||
"API access has not been configured.\n"\
|
||||
"As a result, Discord Notifier is unable to retrieve job information like logs or status.\n"\
|
||||
"Please use the `woodpecker_url` and `woodpecker_token` settings to allow API access.",
|
||||
color="ED4345",
|
||||
timestamp=datetime.now(UTC),
|
||||
footer={
|
||||
"text": "Discord Notifier",
|
||||
"icon_url": "https://dev.shielddagger.com/repo-avatars/273e88fa2afde290121dc7b5987dc366b88325f147bf1e5766bca26296bbc1f9"
|
||||
}
|
||||
))
|
||||
webhook.execute()
|
||||
exit(1)
|
||||
|
||||
logfile_webhook = None
|
||||
webhook = DiscordWebhook(environ["PLUGIN_WEBHOOK_URL"], rate_limit_retry=True)
|
||||
pipeline_url = urlparse(environ["CI_PIPELINE_URL"]).path.lstrip("/").split("/")
|
||||
repo_id = pipeline_url[1]
|
||||
pipeline_number = pipeline_url[3]
|
||||
|
||||
success = environ["CI_PIPELINE_STATUS"] != "failure"
|
||||
pipeline_info = ci_client.get(f"/api/repos/{repo_id}/pipelines/{pipeline_number}").json()
|
||||
success = None
|
||||
for workflow in pipeline_info["workflows"]:
|
||||
if workflow["name"] != environ["CI_WORKFLOW_NAME"]:
|
||||
continue
|
||||
for step in workflow["children"]:
|
||||
if step["state"] != "failure":
|
||||
continue
|
||||
loginfo = ci_client.get(f"/api/repos/{repo_id}/logs/{pipeline_number}/{step['id']}").json()
|
||||
logdata = b""
|
||||
for logline in loginfo:
|
||||
logdata += b64decode(logline["data"]) + b"\n"
|
||||
logfile_webhook.add_file(logdata, f"{step['name']}.ansi")
|
||||
success = all(step["state"] != "failure" for step in workflow["children"])
|
||||
|
||||
webhook.username = "Woodpecker CI"
|
||||
webhook.avatar_url = "https://ci.shielddagger.com/favicons/favicon-dark-success.png" if success else \
|
||||
"https://ci.shielddagger.com/favicons/favicon-dark-error.png"
|
||||
|
||||
@@ -39,8 +77,8 @@ webhook.add_embed(DiscordEmbed(
|
||||
"url": environ["CI_REPO_URL"]
|
||||
},
|
||||
footer={
|
||||
"text": environ["CI_COMMIT_AUTHOR"],
|
||||
"icon_url": environ["CI_COMMIT_AUTHOR_AVATAR"]
|
||||
"text": environ.get("CI_COMMIT_AUTHOR", "Woodpecker"),
|
||||
"icon_url": environ.get("CI_COMMIT_AUTHOR_AVATAR", "https://ci.shielddagger.com/favicons/favicon-dark-default.png")
|
||||
},
|
||||
thumbnail={
|
||||
"url": getenv("PLUGIN_ICON_URL"),
|
||||
@@ -65,47 +103,10 @@ webhook.add_embed(DiscordEmbed(
|
||||
}]
|
||||
))
|
||||
|
||||
try:
|
||||
if not success:
|
||||
logfile_webhook = DiscordWebhook(environ["PLUGIN_WEBHOOK_URL"], rate_limit_retry=True)
|
||||
logfile_webhook.username = "Woodpecker CI"
|
||||
logfile_webhook.avatar_url = "https://ci.shielddagger.com/favicons/favicon-dark-error.png"
|
||||
with APISession(environ["PLUGIN_WOODPECKER_URL"].rstrip("/")) as client:
|
||||
client.headers.setdefault("Authorization", f"Bearer {environ["PLUGIN_WOODPECKER_TOKEN"]}")
|
||||
|
||||
pipeline_url = urlparse(environ["CI_PIPELINE_URL"]).path.lstrip("/").split("/")
|
||||
repo_id = pipeline_url[1]
|
||||
pipeline_number = pipeline_url[3]
|
||||
|
||||
pipeline_info = client.get(f"/api/repos/{repo_id}/pipelines/{pipeline_number}").json()
|
||||
for workflow in pipeline_info["workflows"]:
|
||||
if workflow["name"] != environ["CI_WORKFLOW_NAME"]:
|
||||
continue
|
||||
for step in workflow["children"]:
|
||||
if step["state"] != "failure":
|
||||
continue
|
||||
loginfo = client.get(f"/api/repos/{repo_id}/logs/{pipeline_number}/{step['id']}").json()
|
||||
logdata = b""
|
||||
for logline in loginfo:
|
||||
logdata += b64decode(logline["data"])
|
||||
logfile_webhook.add_file(logdata, f"{step['name']}.ansi")
|
||||
except KeyError:
|
||||
webhook.add_embed(DiscordEmbed(
|
||||
"API Error",
|
||||
"API access has not been configured.\n"\
|
||||
"As a result, Discord Notifier is unable to retrieve failed job logs to attach here.\n"\
|
||||
"Please use the `woodpecker_url` and `woodpecker_token` settings to allow API access.",
|
||||
color="ED4345",
|
||||
timestamp=datetime.now(UTC),
|
||||
footer={
|
||||
"text": "Discord Notifier",
|
||||
"icon_url": "https://dev.shielddagger.com/repo-avatars/273e88fa2afde290121dc7b5987dc366b88325f147bf1e5766bca26296bbc1f9"
|
||||
}
|
||||
))
|
||||
|
||||
pprint(webhook.json)
|
||||
webhook.execute()
|
||||
|
||||
if logfile_webhook is not None:
|
||||
if not success:
|
||||
pprint(logfile_webhook.json)
|
||||
logfile_webhook.execute()
|
||||
|
||||
Reference in New Issue
Block a user