remove: woodpecker CI — replaced by Gitea Actions
Some checks failed
CI / lint-test (push) Failing after 55s
CI / skinny-install (aco) (push) Successful in 1m26s
CI / skinny-install (api) (push) Failing after 39s
CI / skinny-install (bcda) (push) Successful in 47s
CI / skinny-install (bls) (push) Successful in 29s
CI / skinny-install (ccw) (push) Successful in 40s
CI / skinny-install (cli) (push) Successful in 43s
CI / skinny-install (cms) (push) Successful in 40s
CI / skinny-install (conf) (push) Successful in 37s
CI / skinny-install (opps) (push) Successful in 39s
CI / skinny-install (bib) (push) Successful in 5m2s
CI / skinny-install (perf) (push) Successful in 41s
CI / skinny-install (rex) (push) Successful in 40s
CI / skinny-install (pfs) (push) Successful in 53s
Infra CI / notebooks (push) Successful in 13s
Infra CI / zotero (push) Successful in 19s
Infra CI / docs (push) Successful in 1m21s
Infra CI / api (push) Successful in 12s
Infra CI / mc (push) Successful in 12s
Deploy / build-scan-report (push) Failing after 5m22s
Some checks failed
CI / lint-test (push) Failing after 55s
CI / skinny-install (aco) (push) Successful in 1m26s
CI / skinny-install (api) (push) Failing after 39s
CI / skinny-install (bcda) (push) Successful in 47s
CI / skinny-install (bls) (push) Successful in 29s
CI / skinny-install (ccw) (push) Successful in 40s
CI / skinny-install (cli) (push) Successful in 43s
CI / skinny-install (cms) (push) Successful in 40s
CI / skinny-install (conf) (push) Successful in 37s
CI / skinny-install (opps) (push) Successful in 39s
CI / skinny-install (bib) (push) Successful in 5m2s
CI / skinny-install (perf) (push) Successful in 41s
CI / skinny-install (rex) (push) Successful in 40s
CI / skinny-install (pfs) (push) Successful in 53s
Infra CI / notebooks (push) Successful in 13s
Infra CI / zotero (push) Successful in 19s
Infra CI / docs (push) Successful in 1m21s
Infra CI / api (push) Successful in 12s
Infra CI / mc (push) Successful in 12s
Deploy / build-scan-report (push) Failing after 5m22s
- Remove woodpecker-server + woodpecker-agent from compose.yml - Remove woodpecker volumes and webhook allowlist entry - Remove traefik ci.fhirworx.io route - Remove woodpecker client library, backend script, tests - Remove woodpecker CSS theme - Remove woodpecker provisioning from deploy pipeline Gitea Actions (act-runner) handles all CI/CD now.
This commit is contained in:
@@ -1,402 +0,0 @@
|
|||||||
/* HTI-5 — Federal Register editorial design system for Woodpecker CI */
|
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=Source+Serif+4:ital,wght@0,300;0,400;0,600;1,400&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
|
||||||
|
|
||||||
:root {
|
|
||||||
/* HTI-5 Color Palette */
|
|
||||||
--background: #F7F5F0;
|
|
||||||
--foreground: #1A1A18;
|
|
||||||
--card: #FAFAF7;
|
|
||||||
--primary: #1C2B3A;
|
|
||||||
--primary-foreground: #F7F5F0;
|
|
||||||
--secondary: #EDEBE6;
|
|
||||||
--muted-foreground: #6B6B68;
|
|
||||||
--border: #D4D0C8;
|
|
||||||
--sidebar: #1C2B3A;
|
|
||||||
--sidebar-foreground: #B8C5D0;
|
|
||||||
--sidebar-accent: #253748;
|
|
||||||
--sidebar-accent-foreground: #EEE9E0;
|
|
||||||
--destructive: #C0392B;
|
|
||||||
--chart-4: #2E8B6E;
|
|
||||||
--chart-5: #C8702A;
|
|
||||||
|
|
||||||
--font-display: "Playfair Display", Georgia, serif;
|
|
||||||
--font-body: "Source Serif 4", Georgia, serif;
|
|
||||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
|
||||||
|
|
||||||
/* Woodpecker color overrides — mapped from hti5 tokens */
|
|
||||||
--color-wp-primary-100: #B8C5D0;
|
|
||||||
--color-wp-primary-200: #7A96AE;
|
|
||||||
--color-wp-primary-300: #1C2B3A;
|
|
||||||
--color-wp-primary-400: #162030;
|
|
||||||
--color-wp-primary-500: #0E1520;
|
|
||||||
|
|
||||||
--color-wp-success-100: #56B397;
|
|
||||||
--color-wp-success-200: #2E8B6E;
|
|
||||||
|
|
||||||
--color-wp-error-100: #D9534F;
|
|
||||||
--color-wp-error-200: #C0392B;
|
|
||||||
|
|
||||||
--color-wp-warning-100: #DDA060;
|
|
||||||
--color-wp-warning-200: #C8702A;
|
|
||||||
|
|
||||||
--color-wp-gray-100: #1A1A18;
|
|
||||||
--color-wp-gray-200: #6B6B68;
|
|
||||||
--color-wp-gray-300: #D4D0C8;
|
|
||||||
--color-wp-gray-400: #EDEBE6;
|
|
||||||
--color-wp-gray-500: #F7F5F0;
|
|
||||||
--color-wp-gray-600: #FAFAF7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Global styles */
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
font-family: var(--font-body) !important;
|
|
||||||
background: var(--background) !important;
|
|
||||||
color: var(--foreground) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Headings */
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-family: var(--font-display) !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
line-height: 1.3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 { font-size: 22px !important; color: var(--foreground) !important; }
|
|
||||||
h2 { font-size: 17px !important; color: var(--primary) !important; }
|
|
||||||
h3 { font-size: 14px !important; color: var(--foreground) !important; }
|
|
||||||
|
|
||||||
/* Header/Navbar */
|
|
||||||
header,
|
|
||||||
nav,
|
|
||||||
.navbar,
|
|
||||||
[class*="header"],
|
|
||||||
[class*="Header"] {
|
|
||||||
background: var(--sidebar) !important;
|
|
||||||
border-bottom: 3px solid var(--foreground) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar */
|
|
||||||
aside,
|
|
||||||
[class*="sidebar"],
|
|
||||||
[class*="Sidebar"] {
|
|
||||||
background: var(--sidebar) !important;
|
|
||||||
border-right: 1px solid var(--sidebar-border, var(--sidebar-accent)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main content area */
|
|
||||||
main,
|
|
||||||
[class*="content"],
|
|
||||||
[class*="Content"] {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cards and panels */
|
|
||||||
[class*="card"],
|
|
||||||
[class*="Card"],
|
|
||||||
[class*="panel"],
|
|
||||||
[class*="Panel"],
|
|
||||||
[class*="box"],
|
|
||||||
[class*="Box"] {
|
|
||||||
background: var(--card) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons — serif body for button labels */
|
|
||||||
button,
|
|
||||||
[class*="btn"],
|
|
||||||
[class*="Btn"],
|
|
||||||
[class*="button"],
|
|
||||||
[class*="Button"] {
|
|
||||||
font-family: var(--font-body) !important;
|
|
||||||
font-weight: 500 !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
transition: all 0.15s ease !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Primary buttons */
|
|
||||||
[class*="primary"],
|
|
||||||
[class*="Primary"],
|
|
||||||
button[type="submit"] {
|
|
||||||
background: var(--primary) !important;
|
|
||||||
color: var(--primary-foreground) !important;
|
|
||||||
border: 1px solid var(--primary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="primary"]:hover,
|
|
||||||
[class*="Primary"]:hover,
|
|
||||||
button[type="submit"]:hover {
|
|
||||||
background: var(--sidebar-accent) !important;
|
|
||||||
color: var(--sidebar-accent-foreground) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Success/green buttons */
|
|
||||||
[class*="success"],
|
|
||||||
[class*="Success"],
|
|
||||||
[class*="green"],
|
|
||||||
[class*="Green"] {
|
|
||||||
background: var(--chart-4) !important;
|
|
||||||
color: #fff !important;
|
|
||||||
border: 1px solid var(--chart-4) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="success"]:hover,
|
|
||||||
[class*="Success"]:hover {
|
|
||||||
opacity: 0.85 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Danger/red buttons */
|
|
||||||
[class*="danger"],
|
|
||||||
[class*="Danger"],
|
|
||||||
[class*="error"],
|
|
||||||
[class*="Error"],
|
|
||||||
[class*="red"],
|
|
||||||
[class*="Red"] {
|
|
||||||
background: var(--destructive) !important;
|
|
||||||
color: #fff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms */
|
|
||||||
input,
|
|
||||||
textarea,
|
|
||||||
select {
|
|
||||||
font-family: var(--font-body) !important;
|
|
||||||
background: var(--card) !important;
|
|
||||||
color: var(--foreground) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
padding: 8px 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pipeline/repo names — Playfair for titles */
|
|
||||||
[class*="pipeline-name"],
|
|
||||||
[class*="PipelineName"],
|
|
||||||
[class*="repo-name"],
|
|
||||||
[class*="RepoName"] {
|
|
||||||
font-family: var(--font-display) !important;
|
|
||||||
font-weight: 700 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Commit hashes, build numbers, durations — mono */
|
|
||||||
[class*="commit-hash"],
|
|
||||||
[class*="CommitHash"],
|
|
||||||
[class*="build-number"],
|
|
||||||
[class*="BuildNumber"],
|
|
||||||
[class*="duration"],
|
|
||||||
[class*="Duration"],
|
|
||||||
[class*="timestamp"],
|
|
||||||
[class*="Timestamp"] {
|
|
||||||
font-family: var(--font-mono) !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus,
|
|
||||||
textarea:focus,
|
|
||||||
select:focus {
|
|
||||||
border-color: var(--primary) !important;
|
|
||||||
outline: none !important;
|
|
||||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tables */
|
|
||||||
table {
|
|
||||||
background: var(--card) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-collapse: collapse !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background: var(--primary) !important;
|
|
||||||
color: var(--primary-foreground) !important;
|
|
||||||
font-family: var(--font-mono) !important;
|
|
||||||
font-size: 10px !important;
|
|
||||||
text-transform: uppercase !important;
|
|
||||||
padding: 10px 12px !important;
|
|
||||||
border: 1px solid var(--sidebar-accent) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
background: var(--card) !important;
|
|
||||||
color: var(--foreground) !important;
|
|
||||||
padding: 8px 12px !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(even) td {
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover td {
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pipeline/build status colors */
|
|
||||||
[class*="running"],
|
|
||||||
[class*="Running"],
|
|
||||||
[class*="pending"],
|
|
||||||
[class*="Pending"] {
|
|
||||||
color: var(--chart-5) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="success"],
|
|
||||||
[class*="Success"],
|
|
||||||
[class*="passed"],
|
|
||||||
[class*="Passed"] {
|
|
||||||
color: var(--chart-4) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="failure"],
|
|
||||||
[class*="Failure"],
|
|
||||||
[class*="failed"],
|
|
||||||
[class*="Failed"],
|
|
||||||
[class*="error"],
|
|
||||||
[class*="Error"] {
|
|
||||||
color: var(--destructive) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Build logs */
|
|
||||||
pre,
|
|
||||||
code,
|
|
||||||
[class*="log"],
|
|
||||||
[class*="Log"],
|
|
||||||
[class*="console"],
|
|
||||||
[class*="Console"] {
|
|
||||||
font-family: var(--font-mono) !important;
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
color: var(--foreground) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inline code */
|
|
||||||
code {
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
color: var(--primary) !important;
|
|
||||||
padding: 2px 6px !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Links */
|
|
||||||
a {
|
|
||||||
color: var(--primary) !important;
|
|
||||||
text-decoration: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--foreground) !important;
|
|
||||||
text-decoration: underline !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dropdowns */
|
|
||||||
[class*="dropdown"],
|
|
||||||
[class*="Dropdown"],
|
|
||||||
[class*="menu"],
|
|
||||||
[class*="Menu"],
|
|
||||||
[class*="popover"],
|
|
||||||
[class*="Popover"] {
|
|
||||||
background: var(--card) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="dropdown"] li:hover,
|
|
||||||
[class*="menu"] li:hover,
|
|
||||||
[role="menuitem"]:hover,
|
|
||||||
[role="option"]:hover {
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Badges and tags */
|
|
||||||
[class*="badge"],
|
|
||||||
[class*="Badge"],
|
|
||||||
[class*="tag"],
|
|
||||||
[class*="Tag"],
|
|
||||||
[class*="label"],
|
|
||||||
[class*="Label"] {
|
|
||||||
font-family: var(--font-mono) !important;
|
|
||||||
font-size: 11px !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tooltips */
|
|
||||||
[role="tooltip"],
|
|
||||||
[class*="tooltip"],
|
|
||||||
[class*="Tooltip"] {
|
|
||||||
font-family: var(--font-body) !important;
|
|
||||||
font-size: 13px !important;
|
|
||||||
background: var(--card) !important;
|
|
||||||
color: var(--foreground) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Progress bars */
|
|
||||||
progress,
|
|
||||||
[class*="progress"],
|
|
||||||
[class*="Progress"] {
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress::-webkit-progress-bar {
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress::-webkit-progress-value {
|
|
||||||
background: var(--primary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollbars */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: var(--secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--border);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--muted-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Selection */
|
|
||||||
::selection {
|
|
||||||
background: var(--primary) !important;
|
|
||||||
color: var(--primary-foreground) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Icons - ensure visibility */
|
|
||||||
svg,
|
|
||||||
[class*="icon"],
|
|
||||||
[class*="Icon"] {
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Avatar images */
|
|
||||||
img[class*="avatar"],
|
|
||||||
[class*="avatar"] img {
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
border-radius: var(--radius, 0.2rem) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Repo/pipeline list items */
|
|
||||||
[class*="item"],
|
|
||||||
[class*="Item"],
|
|
||||||
[class*="list-item"],
|
|
||||||
[class*="ListItem"] {
|
|
||||||
border-bottom: 1px solid var(--border) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="item"]:hover,
|
|
||||||
[class*="Item"]:hover {
|
|
||||||
background: var(--secondary) !important;
|
|
||||||
}
|
|
||||||
57
compose.yml
57
compose.yml
@@ -181,7 +181,7 @@ services:
|
|||||||
- GITEA__server__DOMAIN=git.${DOMAIN:-fhirworx.io}
|
- GITEA__server__DOMAIN=git.${DOMAIN:-fhirworx.io}
|
||||||
- GITEA__server__ROOT_URL=https://git.${DOMAIN:-fhirworx.io}/
|
- GITEA__server__ROOT_URL=https://git.${DOMAIN:-fhirworx.io}/
|
||||||
- GITEA__server__SSH_DOMAIN=git.${DOMAIN:-fhirworx.io}
|
- GITEA__server__SSH_DOMAIN=git.${DOMAIN:-fhirworx.io}
|
||||||
- GITEA__webhook__ALLOWED_HOST_LIST=woodpecker-server,ci.${DOMAIN:-fhirworx.io},${HOST_IP:-192.168.1.192},172.19.0.0/16
|
- GITEA__webhook__ALLOWED_HOST_LIST=ci.${DOMAIN:-fhirworx.io},${HOST_IP:-192.168.1.192},172.19.0.0/16
|
||||||
- GITEA__ui__THEMES=fhirworx,fhirworx-dark
|
- GITEA__ui__THEMES=fhirworx,fhirworx-dark
|
||||||
- GITEA__ui__DEFAULT_THEME=fhirworx
|
- GITEA__ui__DEFAULT_THEME=fhirworx
|
||||||
volumes:
|
volumes:
|
||||||
@@ -198,59 +198,6 @@ services:
|
|||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
woodpecker-server:
|
|
||||||
image: woodpeckerci/woodpecker-server:v3.13.0
|
|
||||||
container_name: woodpecker-server
|
|
||||||
networks:
|
|
||||||
- gateway
|
|
||||||
- storage
|
|
||||||
- ci
|
|
||||||
dns:
|
|
||||||
- 192.168.5.53
|
|
||||||
environment:
|
|
||||||
- WOODPECKER_HOST=https://ci.${DOMAIN:-fhirworx.io}
|
|
||||||
- WOODPECKER_GRPC_ADDR=:9000
|
|
||||||
- WOODPECKER_LOG_LEVEL=debug
|
|
||||||
- WOODPECKER_ADMIN=${WOODPECKER_ADMIN:-kert}
|
|
||||||
- WOODPECKER_OPEN=true
|
|
||||||
- WOODPECKER_GITEA=true
|
|
||||||
- WOODPECKER_GITEA_URL=https://git.${DOMAIN:-fhirworx.io}
|
|
||||||
- WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
|
|
||||||
- WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}
|
|
||||||
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
|
|
||||||
- WOODPECKER_DATABASE_DRIVER=postgres
|
|
||||||
- WOODPECKER_DATABASE_DATASOURCE=postgres://woodpecker:${WOODPECKER_DB_PASSWORD}@postgres:5432/woodpecker?sslmode=disable
|
|
||||||
- WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx,woodpeckerci/plugin-trivy
|
|
||||||
- WOODPECKER_CUSTOM_CSS_FILE=/etc/woodpecker/custom.css
|
|
||||||
volumes:
|
|
||||||
- woodpecker_data:/var/lib/woodpecker
|
|
||||||
- ./assets/css/woodpecker.css:/etc/woodpecker/custom.css:ro
|
|
||||||
depends_on:
|
|
||||||
- git
|
|
||||||
- postgres
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
woodpecker-agent:
|
|
||||||
image: woodpeckerci/woodpecker-agent:v3.13.0
|
|
||||||
container_name: woodpecker-agent
|
|
||||||
networks:
|
|
||||||
- ci
|
|
||||||
privileged: true
|
|
||||||
environment:
|
|
||||||
- WOODPECKER_SERVER=woodpecker-server:9000
|
|
||||||
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
|
|
||||||
- WOODPECKER_MAX_WORKFLOWS=4
|
|
||||||
- WOODPECKER_BACKEND=docker
|
|
||||||
- WOODPECKER_BACKEND_DOCKER_NETWORK=ci
|
|
||||||
- WOODPECKER_BACKEND_DOCKER_VOLUMES=/home/kert/stack/infra/traefik/certs/docker-certs.d:/etc/docker/certs.d:ro
|
|
||||||
- DOCKER_API_VERSION=1.44
|
|
||||||
volumes:
|
|
||||||
- ${DOCKER_SOCK:-/run/user/1000/docker.sock}:/var/run/docker.sock
|
|
||||||
- woodpecker_agent_config:/etc/woodpecker
|
|
||||||
depends_on:
|
|
||||||
- woodpecker-server
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
act-runner:
|
act-runner:
|
||||||
image: gitea/act_runner:latest
|
image: gitea/act_runner:latest
|
||||||
container_name: act-runner
|
container_name: act-runner
|
||||||
@@ -804,8 +751,6 @@ volumes:
|
|||||||
rustfs_logs:
|
rustfs_logs:
|
||||||
gitea_data:
|
gitea_data:
|
||||||
gitea_config:
|
gitea_config:
|
||||||
woodpecker_data:
|
|
||||||
woodpecker_agent_config:
|
|
||||||
act_runner_data:
|
act_runner_data:
|
||||||
loki_data:
|
loki_data:
|
||||||
prometheus_data:
|
prometheus_data:
|
||||||
|
|||||||
@@ -1,745 +0,0 @@
|
|||||||
"""Woodpecker CI backend emitter.
|
|
||||||
|
|
||||||
Generates .woodpecker/*.yml pipeline files from image definitions
|
|
||||||
and platform config.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def emit(
|
|
||||||
images: list[dict],
|
|
||||||
scans: list[dict],
|
|
||||||
platform: dict,
|
|
||||||
ci_cfg: dict,
|
|
||||||
) -> dict[str, str]:
|
|
||||||
"""Return {relative_path: content} for all Woodpecker pipelines."""
|
|
||||||
ctx = _Ctx(images, scans, platform, ci_cfg)
|
|
||||||
return {
|
|
||||||
".woodpecker/ci.yml": ctx.gen_ci(),
|
|
||||||
".woodpecker/deploy.yml": ctx.gen_deploy(),
|
|
||||||
".woodpecker/harden.yml": ctx.gen_harden(),
|
|
||||||
".woodpecker/infra-ci.yml": ctx.gen_infra_ci(),
|
|
||||||
".woodpecker/rebuild-all.yml": ctx.gen_rebuild_all(),
|
|
||||||
".woodpecker/release.yml": ctx.gen_release(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class _Ctx:
|
|
||||||
"""Holds platform/image context for YAML generation."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
images: list[dict],
|
|
||||||
scans: list[dict],
|
|
||||||
platform: dict,
|
|
||||||
ci_cfg: dict,
|
|
||||||
) -> None:
|
|
||||||
self.images = images
|
|
||||||
self.scans = scans
|
|
||||||
self.registry = platform["registry"]
|
|
||||||
self.org = platform["org"]
|
|
||||||
self.image_prefix = platform["image_prefix"]
|
|
||||||
self.plugin_buildx = ci_cfg.get(
|
|
||||||
"plugin_buildx", "woodpeckerci/plugin-docker-buildx:5-insecure"
|
|
||||||
)
|
|
||||||
self.plugin_buildx_dryrun = ci_cfg.get(
|
|
||||||
"plugin_buildx_dryrun", "woodpeckerci/plugin-docker-buildx"
|
|
||||||
)
|
|
||||||
self.plugin_s3 = ci_cfg.get("plugin_s3", "woodpeckerci/plugin-s3")
|
|
||||||
self.plugin_comment = ci_cfg.get(
|
|
||||||
"plugin_comment", "woodpeckerci/plugin-gitea-comment"
|
|
||||||
)
|
|
||||||
self.plugin_release = ci_cfg.get(
|
|
||||||
"plugin_release", "woodpeckerci/plugin-gitea-release"
|
|
||||||
)
|
|
||||||
self.docker_socket = ci_cfg.get("docker_socket", "/run/user/1000/docker.sock")
|
|
||||||
self.uv_image = ci_cfg.get(
|
|
||||||
"uv_image", "ghcr.io/astral-sh/uv:python3.13-bookworm-slim"
|
|
||||||
)
|
|
||||||
self.coverage_threshold = ci_cfg.get("coverage_threshold", 99)
|
|
||||||
|
|
||||||
# ── shared fragments ─────────────────────────────────────────
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _variables_block(self) -> str:
|
|
||||||
return f"""\
|
|
||||||
variables:
|
|
||||||
- &buildx_image {self.plugin_buildx}
|
|
||||||
- ®istry {self.registry}
|
|
||||||
- &org {self.org}
|
|
||||||
- &image_prefix {self.image_prefix}
|
|
||||||
- &buildx_base
|
|
||||||
buildkit_config: |
|
|
||||||
[registry."{self.registry}"]
|
|
||||||
http = true
|
|
||||||
registry: *registry
|
|
||||||
username:
|
|
||||||
from_secret: registry_user
|
|
||||||
password:
|
|
||||||
from_secret: registry_pass
|
|
||||||
- &trivy_scan
|
|
||||||
image: aquasec/trivy:latest
|
|
||||||
environment:
|
|
||||||
REGISTRY: *registry
|
|
||||||
ORG: *org
|
|
||||||
- &diag_env
|
|
||||||
UV_PYTHON_PREFERENCE: only-system
|
|
||||||
UV_LINK_MODE: copy
|
|
||||||
UV_PROJECT_ENVIRONMENT: .venv
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: gitea_token
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _failure_reporter(self) -> str:
|
|
||||||
return f"""\
|
|
||||||
# ── Failure reporter ─────────────────────────────────────────
|
|
||||||
- name: report-failure
|
|
||||||
image: {self.uv_image}
|
|
||||||
failure: ignore
|
|
||||||
environment:
|
|
||||||
<<: *diag_env
|
|
||||||
commands:
|
|
||||||
- uv sync --no-dev
|
|
||||||
- uv run python -m api.diag
|
|
||||||
when:
|
|
||||||
- status: [failure]
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _build_step(
|
|
||||||
self,
|
|
||||||
img: dict,
|
|
||||||
tags: list[str],
|
|
||||||
*,
|
|
||||||
no_cache: bool = False,
|
|
||||||
name_prefix: str = "build",
|
|
||||||
depends: str = "",
|
|
||||||
) -> str:
|
|
||||||
n = img["name"]
|
|
||||||
lines = [
|
|
||||||
f" - name: {name_prefix}-{n}",
|
|
||||||
" image: *buildx_image",
|
|
||||||
" settings:",
|
|
||||||
" <<: *buildx_base",
|
|
||||||
f" repo: {self.registry}/{self.org}/{n}",
|
|
||||||
f" dockerfile: {img['dockerfile']}",
|
|
||||||
f" context: {img['context']}",
|
|
||||||
" tags:",
|
|
||||||
]
|
|
||||||
for t in tags:
|
|
||||||
lines.append(f' - "{t}"')
|
|
||||||
if no_cache:
|
|
||||||
lines.append(" no_cache: true")
|
|
||||||
if depends:
|
|
||||||
lines.append(" depends_on:")
|
|
||||||
lines.append(f" - {depends}")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
def _prep_step(self, img: dict, *, env_block: str = "") -> str:
|
|
||||||
n = img["name"]
|
|
||||||
cmds = img.get("prep_commands", [])
|
|
||||||
if not cmds:
|
|
||||||
return ""
|
|
||||||
lines = [
|
|
||||||
f" - name: prep-{n}-context",
|
|
||||||
" image: alpine:3",
|
|
||||||
]
|
|
||||||
if env_block:
|
|
||||||
lines.append(env_block)
|
|
||||||
if n == "docs":
|
|
||||||
lines.append(" volumes:")
|
|
||||||
lines.append(" - ${CI_WORKSPACE}/data:/host-data:ro")
|
|
||||||
lines.append(" commands:")
|
|
||||||
for cmd in cmds:
|
|
||||||
lines.append(f" - {cmd}")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
def _scan_step(self, img: dict, tag_expr: str) -> str:
|
|
||||||
n = img["name"]
|
|
||||||
sev = img.get("trivy_severity", "HIGH,CRITICAL")
|
|
||||||
ec = img.get("trivy_exit_code", 0)
|
|
||||||
return f"""\
|
|
||||||
- name: scan-{n}
|
|
||||||
<<: *trivy_scan
|
|
||||||
commands:
|
|
||||||
- trivy image --image-src remote --insecure --severity {sev}
|
|
||||||
--exit-code {ec} --format json -o {n}-scan.json
|
|
||||||
"$REGISTRY/$ORG/{n}:{tag_expr}"
|
|
||||||
depends_on: [build-push-{n}]"""
|
|
||||||
|
|
||||||
def _s3_upload_step(self, img: dict) -> str:
|
|
||||||
n = img["name"]
|
|
||||||
return f"""\
|
|
||||||
- name: upload-{n}-scan
|
|
||||||
image: {self.plugin_s3}
|
|
||||||
failure: ignore
|
|
||||||
settings:
|
|
||||||
endpoint: http://rustfs:9000
|
|
||||||
bucket: gitea
|
|
||||||
access_key:
|
|
||||||
from_secret: s3_access_key
|
|
||||||
secret_key:
|
|
||||||
from_secret: s3_secret_key
|
|
||||||
source: "{n}-scan.json"
|
|
||||||
target: /ci/${{CI_REPO}}/${{CI_COMMIT_SHA:0:8}}/
|
|
||||||
path_style: true
|
|
||||||
depends_on:
|
|
||||||
- scan-{n}"""
|
|
||||||
|
|
||||||
def _scan_report_loop(self, imgs: list[dict]) -> str:
|
|
||||||
files = " ".join(f"{i['name']}-scan.json" for i in imgs)
|
|
||||||
return f"""\
|
|
||||||
- |
|
|
||||||
for f in {files}; do
|
|
||||||
[ -f "$f" ] && uv run python -m api.diag.vuln "$f" || true
|
|
||||||
done"""
|
|
||||||
|
|
||||||
def _harden_close_loop(self, imgs: list[dict]) -> str:
|
|
||||||
files = " ".join(f"{i['name']}-scan.json" for i in imgs)
|
|
||||||
return f"""\
|
|
||||||
- |
|
|
||||||
for f in {files}; do
|
|
||||||
if [ -f "$f" ]; then
|
|
||||||
uv run python -m api.diag.vuln --close "$f" || \\
|
|
||||||
uv run python -m api.diag.vuln "$f" || true
|
|
||||||
fi
|
|
||||||
done"""
|
|
||||||
|
|
||||||
def _path_block(self, pf: list[str]) -> str:
|
|
||||||
if not pf:
|
|
||||||
return ""
|
|
||||||
if len(pf) == 1:
|
|
||||||
return f' - path: "{pf[0]}"'
|
|
||||||
return " - path:\n" + "\n".join(f' - "{p}"' for p in pf)
|
|
||||||
|
|
||||||
# ── pipeline generators ──────────────────────────────────────
|
|
||||||
|
|
||||||
def gen_ci(self) -> str:
|
|
||||||
return f"""\
|
|
||||||
# ── Quality gate (generated by gen_config.py — woodpecker backend) ──
|
|
||||||
# DO NOT EDIT — edit stack.toml and run gen_config.py
|
|
||||||
|
|
||||||
when:
|
|
||||||
- event: [push, pull_request, manual]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: lint
|
|
||||||
image: {self.uv_image}
|
|
||||||
environment:
|
|
||||||
UV_PYTHON_PREFERENCE: only-system
|
|
||||||
UV_LINK_MODE: copy
|
|
||||||
UV_PROJECT_ENVIRONMENT: .venv
|
|
||||||
commands:
|
|
||||||
- uv sync --dev
|
|
||||||
- uv run ruff check src/ tests/ --output-format=concise
|
|
||||||
- uv run ruff format --check src/ tests/
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
image: {self.uv_image}
|
|
||||||
environment:
|
|
||||||
UV_PYTHON_PREFERENCE: only-system
|
|
||||||
UV_LINK_MODE: copy
|
|
||||||
UV_PROJECT_ENVIRONMENT: .venv
|
|
||||||
commands:
|
|
||||||
- uv run pytest tests/ --cov=src --cov-report=term-missing --cov-fail-under={self.coverage_threshold} -q 2>&1 | tee pytest.out
|
|
||||||
- uv run python dev/scripts/coverage_badge.py < pytest.out > assets/icons/coverage.svg
|
|
||||||
depends_on:
|
|
||||||
- lint
|
|
||||||
|
|
||||||
- name: coverage-badge
|
|
||||||
image: {self.uv_image}
|
|
||||||
failure: ignore
|
|
||||||
environment:
|
|
||||||
UV_PYTHON_PREFERENCE: only-system
|
|
||||||
UV_LINK_MODE: copy
|
|
||||||
UV_PROJECT_ENVIRONMENT: .venv
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: gitea_token
|
|
||||||
GITEA_URL:
|
|
||||||
from_secret: gitea_url
|
|
||||||
commands:
|
|
||||||
- uv run python dev/scripts/coverage_badge.py --post pytest.out
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
- name: commit-badge
|
|
||||||
image: alpine/git
|
|
||||||
failure: ignore
|
|
||||||
environment:
|
|
||||||
REGISTRY_USER:
|
|
||||||
from_secret: registry_user
|
|
||||||
REGISTRY_PASS:
|
|
||||||
from_secret: registry_pass
|
|
||||||
commands:
|
|
||||||
- git config user.name woodpecker-ci
|
|
||||||
- git config user.email ci@${{CI_REPO_OWNER}}.io
|
|
||||||
- git add assets/icons/coverage.svg
|
|
||||||
- git diff --cached --quiet && echo "no change" && exit 0
|
|
||||||
- git commit -m "update coverage badge [skip ci]"
|
|
||||||
- git push http://$REGISTRY_USER:$REGISTRY_PASS@git:3000/${{CI_REPO}}.git HEAD:main
|
|
||||||
when:
|
|
||||||
- branch: main
|
|
||||||
event: push
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
- name: upload-coverage-badge
|
|
||||||
image: {self.plugin_s3}
|
|
||||||
failure: ignore
|
|
||||||
settings:
|
|
||||||
endpoint: http://rustfs:9000
|
|
||||||
bucket: gitea
|
|
||||||
access_key:
|
|
||||||
from_secret: s3_access_key
|
|
||||||
secret_key:
|
|
||||||
from_secret: s3_secret_key
|
|
||||||
source: "assets/icons/coverage.svg"
|
|
||||||
target: /badges/${{CI_REPO}}/
|
|
||||||
path_style: true
|
|
||||||
overwrite: true
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
- name: pr-comment
|
|
||||||
image: {self.plugin_comment}
|
|
||||||
settings:
|
|
||||||
gitea_url:
|
|
||||||
from_secret: gitea_url
|
|
||||||
api_key:
|
|
||||||
from_secret: gitea_token
|
|
||||||
message: >
|
|
||||||
**CI** for `${{CI_COMMIT_SHA:0:8}}` —
|
|
||||||
[pipeline #${{CI_PIPELINE_NUMBER}}](${{CI_PIPELINE_URL}})
|
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
- coverage-badge
|
|
||||||
|
|
||||||
- name: validate-compose
|
|
||||||
image: docker:cli
|
|
||||||
volumes:
|
|
||||||
- {self.docker_socket}:/var/run/docker.sock
|
|
||||||
commands:
|
|
||||||
- docker compose config --quiet
|
|
||||||
|
|
||||||
- name: validate-gen-config
|
|
||||||
image: {self.uv_image}
|
|
||||||
environment:
|
|
||||||
UV_PYTHON_PREFERENCE: only-system
|
|
||||||
UV_LINK_MODE: copy
|
|
||||||
UV_PROJECT_ENVIRONMENT: .venv
|
|
||||||
commands:
|
|
||||||
- uv run python dev/scripts/gen_config.py --check
|
|
||||||
|
|
||||||
- name: skinny-install
|
|
||||||
image: {self.uv_image}
|
|
||||||
environment:
|
|
||||||
UV_PYTHON_PREFERENCE: only-system
|
|
||||||
UV_LINK_MODE: copy
|
|
||||||
UV_PROJECT_ENVIRONMENT: .venv
|
|
||||||
commands:
|
|
||||||
- |
|
|
||||||
for extra in conf aco api bcda bib bls ccw cli cms opps perf pfs rex; do
|
|
||||||
echo "=== stack[$extra] ==="
|
|
||||||
uv sync --no-dev --extra "$extra"
|
|
||||||
uv run python -c "import $extra"
|
|
||||||
if [ -d "tests/$extra" ]; then
|
|
||||||
uv run pytest "tests/$extra/" -x -q || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
{self._failure_reporter}
|
|
||||||
"""
|
|
||||||
|
|
||||||
def gen_deploy(self) -> str:
|
|
||||||
tag_expr = "${CI_COMMIT_SHA:0:8}"
|
|
||||||
parts = [
|
|
||||||
"# ── Deploy pipeline (generated by gen_config.py — woodpecker backend) ──",
|
|
||||||
"# DO NOT EDIT — edit stack.toml [images] and run gen_config.py",
|
|
||||||
"",
|
|
||||||
"when:",
|
|
||||||
" - event: push",
|
|
||||||
" branch: main",
|
|
||||||
"",
|
|
||||||
self._variables_block,
|
|
||||||
"steps:",
|
|
||||||
" # ── Python package ──────────────────────────────────────────",
|
|
||||||
" - name: build-package",
|
|
||||||
f" image: {self.uv_image}",
|
|
||||||
" commands:",
|
|
||||||
" - BASE=$(grep '^version' pyproject.toml | head -1 | sed 's/.*\"\\(.*\\)\"/\\1/')",
|
|
||||||
' - uv version "$BASE.dev${CI_PIPELINE_NUMBER}" --no-sync',
|
|
||||||
" - uv build --out-dir dist/",
|
|
||||||
" - ls -lh dist/",
|
|
||||||
"",
|
|
||||||
" - name: publish-package",
|
|
||||||
f" image: {self.uv_image}",
|
|
||||||
" environment:",
|
|
||||||
" REGISTRY_USER:",
|
|
||||||
" from_secret: registry_user",
|
|
||||||
" REGISTRY_PASS:",
|
|
||||||
" from_secret: registry_pass",
|
|
||||||
" commands:",
|
|
||||||
' - uv publish --publish-url http://git:3000/api/packages/${CI_REPO_OWNER}/pypi --username "$REGISTRY_USER" --password "$REGISTRY_PASS" dist/*',
|
|
||||||
" depends_on:",
|
|
||||||
" - build-package",
|
|
||||||
"",
|
|
||||||
" # ── Container images ──────────────────────────────────────",
|
|
||||||
]
|
|
||||||
|
|
||||||
for img in self.images:
|
|
||||||
prep = self._prep_step(img)
|
|
||||||
if prep:
|
|
||||||
parts.append(prep)
|
|
||||||
parts.append("")
|
|
||||||
parts.append(
|
|
||||||
self._build_step(
|
|
||||||
img,
|
|
||||||
[tag_expr, "latest"],
|
|
||||||
name_prefix="build-push",
|
|
||||||
depends=f"prep-{img['name']}-context",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
parts.append(
|
|
||||||
self._build_step(
|
|
||||||
img, [tag_expr, "latest"], name_prefix="build-push"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Scan images ──────────────────────────────────────────")
|
|
||||||
for img in self.scans:
|
|
||||||
parts.append(self._scan_step(img, tag_expr))
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Upload scan results ─────────────────────────────────")
|
|
||||||
for img in self.scans:
|
|
||||||
parts.append(self._s3_upload_step(img))
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Report vulnerabilities ──────────────────────────────")
|
|
||||||
parts.append(" - name: report-vulns")
|
|
||||||
parts.append(f" image: {self.uv_image}")
|
|
||||||
parts.append(" failure: ignore")
|
|
||||||
parts.append(" environment:")
|
|
||||||
parts.append(" <<: *diag_env")
|
|
||||||
parts.append(" commands:")
|
|
||||||
parts.append(" - uv sync --no-dev")
|
|
||||||
parts.append(self._scan_report_loop(self.scans))
|
|
||||||
parts.append(" depends_on:")
|
|
||||||
for i in self.scans:
|
|
||||||
parts.append(f" - scan-{i['name']}")
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Deploy (disabled — manual only) ────────────────────")
|
|
||||||
parts.append(" - name: deploy")
|
|
||||||
parts.append(" image: alpine:3")
|
|
||||||
parts.append(" commands:")
|
|
||||||
parts.append(
|
|
||||||
' - echo "Deploy disabled — run manually after green pipeline"'
|
|
||||||
)
|
|
||||||
parts.append(" depends_on:")
|
|
||||||
parts.append(" - publish-package")
|
|
||||||
for i in self.scans:
|
|
||||||
parts.append(f" - scan-{i['name']}")
|
|
||||||
for i in self.images:
|
|
||||||
if not i.get("scan", True):
|
|
||||||
parts.append(f" - build-push-{i['name']}")
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(self._failure_reporter)
|
|
||||||
return "\n".join(parts)
|
|
||||||
|
|
||||||
def gen_harden(self) -> str:
|
|
||||||
parts = [
|
|
||||||
"# ── Hardening pipeline (generated by gen_config.py — woodpecker backend) ──",
|
|
||||||
"# DO NOT EDIT — edit stack.toml [images] and run gen_config.py",
|
|
||||||
"",
|
|
||||||
"when:",
|
|
||||||
" - event: [manual, cron]",
|
|
||||||
"",
|
|
||||||
self._variables_block,
|
|
||||||
"steps:",
|
|
||||||
" # ── Build hardened images ──────────────────────────────────",
|
|
||||||
]
|
|
||||||
|
|
||||||
for img in self.images:
|
|
||||||
parts.append(self._build_step(img, ["hardened", "latest"], no_cache=True))
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Scan hardened images ───────────────────────────────────")
|
|
||||||
for img in self.scans:
|
|
||||||
parts.append(self._scan_step(img, "hardened"))
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Close resolved vuln issues OR file new ones ────────────")
|
|
||||||
parts.append(" - name: close-or-report-vulns")
|
|
||||||
parts.append(f" image: {self.uv_image}")
|
|
||||||
parts.append(" environment:")
|
|
||||||
parts.append(" <<: *diag_env")
|
|
||||||
parts.append(" commands:")
|
|
||||||
parts.append(" - uv sync --no-dev")
|
|
||||||
parts.append(self._harden_close_loop(self.scans))
|
|
||||||
parts.append(" depends_on:")
|
|
||||||
for i in self.scans:
|
|
||||||
parts.append(f" - scan-{i['name']}")
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(self._failure_reporter)
|
|
||||||
return "\n".join(parts)
|
|
||||||
|
|
||||||
def gen_rebuild_all(self) -> str:
|
|
||||||
tag_expr = "${CI_COMMIT_SHA:0:8}"
|
|
||||||
svc_list = " ".join(i["name"] for i in self.images)
|
|
||||||
|
|
||||||
parts = [
|
|
||||||
"# ── Rebuild-all pipeline (generated by gen_config.py — woodpecker backend) ──",
|
|
||||||
"# DO NOT EDIT — edit stack.toml [images] and run gen_config.py",
|
|
||||||
"",
|
|
||||||
"when:",
|
|
||||||
" - event: manual",
|
|
||||||
"",
|
|
||||||
self._variables_block,
|
|
||||||
"steps:",
|
|
||||||
]
|
|
||||||
|
|
||||||
for img in self.images:
|
|
||||||
prep = self._prep_step(
|
|
||||||
img,
|
|
||||||
env_block=" environment:\n STACK_ROOT:\n from_secret: stack_root",
|
|
||||||
)
|
|
||||||
if prep:
|
|
||||||
parts.append(prep)
|
|
||||||
parts.append("")
|
|
||||||
parts.append(
|
|
||||||
self._build_step(
|
|
||||||
img,
|
|
||||||
[tag_expr, "latest"],
|
|
||||||
name_prefix="build-push",
|
|
||||||
depends=f"prep-{img['name']}-context",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
parts.append(
|
|
||||||
self._build_step(
|
|
||||||
img, [tag_expr, "latest"], name_prefix="build-push"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Scan images ──────────────────────────────────────────")
|
|
||||||
for img in self.scans:
|
|
||||||
parts.append(self._scan_step(img, tag_expr))
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Report vulnerabilities ──────────────────────────────")
|
|
||||||
parts.append(" - name: report-vulns")
|
|
||||||
parts.append(f" image: {self.uv_image}")
|
|
||||||
parts.append(" failure: ignore")
|
|
||||||
parts.append(" environment:")
|
|
||||||
parts.append(" <<: *diag_env")
|
|
||||||
parts.append(" commands:")
|
|
||||||
parts.append(" - uv sync --no-dev")
|
|
||||||
parts.append(self._scan_report_loop(self.scans))
|
|
||||||
parts.append(" depends_on:")
|
|
||||||
for i in self.scans:
|
|
||||||
parts.append(f" - scan-{i['name']}")
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" - name: deploy")
|
|
||||||
parts.append(" image: docker:cli")
|
|
||||||
parts.append(" environment:")
|
|
||||||
parts.append(" REGISTRY: *registry")
|
|
||||||
parts.append(" ORG: *org")
|
|
||||||
parts.append(" IMAGE_PREFIX: *image_prefix")
|
|
||||||
parts.append(" STACK_ROOT:")
|
|
||||||
parts.append(" from_secret: stack_root")
|
|
||||||
parts.append(" volumes:")
|
|
||||||
parts.append(f" - {self.docker_socket}:/var/run/docker.sock")
|
|
||||||
parts.append(
|
|
||||||
" - ${STACK_ROOT:-/home/kert/stack}:${STACK_ROOT:-/home/kert/stack}"
|
|
||||||
)
|
|
||||||
parts.append(" commands:")
|
|
||||||
parts.append(" - STACK_ROOT=${STACK_ROOT:-/home/kert/stack}")
|
|
||||||
parts.append(" - cd $STACK_ROOT")
|
|
||||||
parts.append(" - TAG=${CI_COMMIT_SHA:0:8}")
|
|
||||||
parts.append(f" - for SVC in {svc_list}; do")
|
|
||||||
parts.append(" docker pull $REGISTRY/$ORG/$SVC:$TAG &&")
|
|
||||||
parts.append(
|
|
||||||
" docker tag $REGISTRY/$ORG/$SVC:$TAG $IMAGE_PREFIX/$SVC:$TAG;"
|
|
||||||
)
|
|
||||||
parts.append(" done")
|
|
||||||
parts.append(
|
|
||||||
' - sed -i "s/^COMMIT_SHA=.*/COMMIT_SHA=$TAG/" .env 2>/dev/null || echo "COMMIT_SHA=$TAG" >> .env'
|
|
||||||
)
|
|
||||||
parts.append(" - docker compose up -d --remove-orphans")
|
|
||||||
parts.append(" depends_on:")
|
|
||||||
for i in self.scans:
|
|
||||||
parts.append(f" - scan-{i['name']}")
|
|
||||||
for i in self.images:
|
|
||||||
if not i.get("scan", True):
|
|
||||||
parts.append(f" - build-push-{i['name']}")
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(self._failure_reporter)
|
|
||||||
return "\n".join(parts)
|
|
||||||
|
|
||||||
def gen_infra_ci(self) -> str:
|
|
||||||
parts = [
|
|
||||||
"# ── Infrastructure quality gate (generated by gen_config.py — woodpecker backend) ──",
|
|
||||||
"# DO NOT EDIT — edit stack.toml [images] and run gen_config.py",
|
|
||||||
"",
|
|
||||||
"when:",
|
|
||||||
" - event: [push, pull_request, manual]",
|
|
||||||
"",
|
|
||||||
"steps:",
|
|
||||||
" - name: infra-gate",
|
|
||||||
" image: alpine:3",
|
|
||||||
" commands:",
|
|
||||||
' - echo "infra-ci gate passed"',
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
|
|
||||||
for img in self.images:
|
|
||||||
if not img.get("hadolint", True):
|
|
||||||
continue
|
|
||||||
n = img["name"]
|
|
||||||
pb = self._path_block(img.get("path_filter", []))
|
|
||||||
|
|
||||||
parts.append(f" - name: hadolint-{n}")
|
|
||||||
parts.append(" image: hadolint/hadolint:latest-debian")
|
|
||||||
parts.append(" commands:")
|
|
||||||
parts.append(f" - hadolint {img['dockerfile']}")
|
|
||||||
if pb:
|
|
||||||
parts.append(" when:")
|
|
||||||
parts.append(pb)
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(f" - name: build-{n}")
|
|
||||||
parts.append(f" image: {self.plugin_buildx_dryrun}")
|
|
||||||
parts.append(" settings:")
|
|
||||||
parts.append(f" dockerfile: {img['dockerfile']}")
|
|
||||||
parts.append(f" context: {img['context']}")
|
|
||||||
parts.append(' tags: ["ci-test"]')
|
|
||||||
parts.append(" daemon_off: true")
|
|
||||||
parts.append(" dry_run: true")
|
|
||||||
parts.append(" volumes:")
|
|
||||||
parts.append(f" - {self.docker_socket}:/var/run/docker.sock")
|
|
||||||
parts.append(" depends_on:")
|
|
||||||
parts.append(f" - hadolint-{n}")
|
|
||||||
if pb:
|
|
||||||
parts.append(" when:")
|
|
||||||
parts.append(pb)
|
|
||||||
parts.append("")
|
|
||||||
|
|
||||||
parts.append(" # ── Config validation ────────────────────────────────────")
|
|
||||||
parts.append("""\
|
|
||||||
- name: validate-nginx
|
|
||||||
image: nginx:alpine
|
|
||||||
commands:
|
|
||||||
- cp infra/nginx/nginx.conf /etc/nginx/nginx.conf
|
|
||||||
- nginx -t
|
|
||||||
when:
|
|
||||||
- path: "infra/nginx/**"
|
|
||||||
|
|
||||||
- name: validate-prometheus
|
|
||||||
image: prom/prometheus:latest
|
|
||||||
commands:
|
|
||||||
- promtool check config infra/prometheus/prometheus.yml
|
|
||||||
when:
|
|
||||||
- path: "infra/prometheus/**"
|
|
||||||
|
|
||||||
- name: validate-traefik-static
|
|
||||||
image: cytopia/yamllint:latest
|
|
||||||
commands:
|
|
||||||
- yamllint -d relaxed infra/traefik/traefik.yml
|
|
||||||
when:
|
|
||||||
- path: "infra/traefik/traefik.yml"
|
|
||||||
|
|
||||||
- name: validate-traefik-template
|
|
||||||
image: traefik:v3.3
|
|
||||||
environment:
|
|
||||||
DOMAIN: ci-test.example.com
|
|
||||||
commands:
|
|
||||||
- |
|
|
||||||
timeout 5 traefik \\
|
|
||||||
--providers.file.directory=infra/traefik/dynamic \\
|
|
||||||
--api.dashboard=false \\
|
|
||||||
--log.level=DEBUG 2>&1 | head -80 || true
|
|
||||||
- echo "Traefik template syntax OK"
|
|
||||||
when:
|
|
||||||
- path: "infra/traefik/dynamic/**"
|
|
||||||
|
|
||||||
- name: validate-loki
|
|
||||||
image: cytopia/yamllint:latest
|
|
||||||
commands:
|
|
||||||
- yamllint -d relaxed infra/loki/
|
|
||||||
when:
|
|
||||||
- path: "infra/loki/**"
|
|
||||||
|
|
||||||
- name: validate-trino
|
|
||||||
image: alpine:3
|
|
||||||
commands:
|
|
||||||
- |
|
|
||||||
OK=true
|
|
||||||
for f in infra/trino/etc/config.properties infra/trino/etc/node.properties infra/trino/etc/jvm.config; do
|
|
||||||
if [ ! -f "$f" ]; then echo "MISSING: $f"; OK=false; fi
|
|
||||||
done
|
|
||||||
ls infra/trino/etc/catalog/*.properties >/dev/null 2>&1 || { echo "MISSING: no catalog properties"; OK=false; }
|
|
||||||
for f in $(find infra/trino/etc -name '*.properties'); do
|
|
||||||
while IFS= read -r line; do
|
|
||||||
case "$line" in ''|'#'*|'!'*) continue ;; esac
|
|
||||||
echo "$line" | grep -q '=' || { echo "BAD LINE in $f: $line"; OK=false; }
|
|
||||||
done < "$f"
|
|
||||||
done
|
|
||||||
$OK && echo "Trino config validation passed"
|
|
||||||
$OK
|
|
||||||
when:
|
|
||||||
- path: "infra/trino/etc/**"
|
|
||||||
""")
|
|
||||||
|
|
||||||
parts.append(self._failure_reporter)
|
|
||||||
return "\n".join(parts)
|
|
||||||
|
|
||||||
def gen_release(self) -> str:
|
|
||||||
return f"""\
|
|
||||||
# ── Release pipeline (generated by gen_config.py — woodpecker backend) ──
|
|
||||||
# DO NOT EDIT — edit stack.toml and run gen_config.py
|
|
||||||
|
|
||||||
when:
|
|
||||||
- event: tag
|
|
||||||
|
|
||||||
variables:
|
|
||||||
- &diag_env
|
|
||||||
UV_PYTHON_PREFERENCE: only-system
|
|
||||||
UV_LINK_MODE: copy
|
|
||||||
UV_PROJECT_ENVIRONMENT: .venv
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: gitea_token
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build-package
|
|
||||||
image: {self.uv_image}
|
|
||||||
commands:
|
|
||||||
- uv build --out-dir dist/
|
|
||||||
- ls -lh dist/
|
|
||||||
|
|
||||||
- name: release
|
|
||||||
image: {self.plugin_release}
|
|
||||||
settings:
|
|
||||||
api_key:
|
|
||||||
from_secret: gitea_token
|
|
||||||
base_url:
|
|
||||||
from_secret: gitea_url
|
|
||||||
files:
|
|
||||||
- dist/*.whl
|
|
||||||
- dist/*.tar.gz
|
|
||||||
title: "${{CI_COMMIT_TAG}}"
|
|
||||||
depends_on:
|
|
||||||
- build-package
|
|
||||||
|
|
||||||
{self._failure_reporter}
|
|
||||||
"""
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
"dashboard" (dict "port" "80" "theme" true "extra_hosts" (list $domain) "mw" "secure-headers")
|
"dashboard" (dict "port" "80" "theme" true "extra_hosts" (list $domain) "mw" "secure-headers")
|
||||||
"docs" (dict "port" "80" "theme" true "mw" "git-sso,secure-headers")
|
"docs" (dict "port" "80" "theme" true "mw" "git-sso,secure-headers")
|
||||||
"git" (dict "port" "3000" "theme" false "mw" "secure-headers")
|
"git" (dict "port" "3000" "theme" false "mw" "secure-headers")
|
||||||
"woodpecker-server" (dict "port" "8000" "theme" true "subdomain" "ci" "mw" "git-sso,secure-headers")
|
|
||||||
"notebooks" (dict "port" "2718" "theme" true "mw" "git-sso,secure-headers")
|
"notebooks" (dict "port" "2718" "theme" true "mw" "git-sso,secure-headers")
|
||||||
"zotero" (dict "port" "8080" "theme" true "mw" "git-sso,secure-headers")
|
"zotero" (dict "port" "8080" "theme" true "mw" "git-sso,secure-headers")
|
||||||
"webdav" (dict "port" "8080" "theme" false "mw" "secure-headers")
|
"webdav" (dict "port" "8080" "theme" false "mw" "secure-headers")
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from api.auth.provision import (
|
|||||||
_retry,
|
_retry,
|
||||||
derive_all,
|
derive_all,
|
||||||
provision_gitea,
|
provision_gitea,
|
||||||
provision_woodpecker,
|
|
||||||
write_env,
|
write_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -214,15 +213,6 @@ def deploy(
|
|||||||
# Wait for services to be ready
|
# Wait for services to be ready
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
# Woodpecker secrets
|
|
||||||
try:
|
|
||||||
_retry(lambda: provision_woodpecker(values), "woodpecker")
|
|
||||||
result.woodpecker = True
|
|
||||||
log.info("Phase 3: Woodpecker secrets synced")
|
|
||||||
except Exception as e:
|
|
||||||
result.errors.append(("woodpecker", e))
|
|
||||||
log.error("Phase 3: Woodpecker sync failed: %s", e)
|
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
health_errors = verify_all(values)
|
health_errors = verify_all(values)
|
||||||
if health_errors:
|
if health_errors:
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ from .base import Client as Client
|
|||||||
from .gitea import GiteaClient as GiteaClient
|
from .gitea import GiteaClient as GiteaClient
|
||||||
from .rustfs import RustFSAdmin as RustFSAdmin
|
from .rustfs import RustFSAdmin as RustFSAdmin
|
||||||
from .rustfs import RustFSClient as RustFSClient
|
from .rustfs import RustFSClient as RustFSClient
|
||||||
from .woodpecker import WoodpeckerClient as WoodpeckerClient
|
|
||||||
from .zotero import ZoteroClient as ZoteroClient
|
from .zotero import ZoteroClient as ZoteroClient
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
"""Woodpecker CI API client."""
|
|
||||||
|
|
||||||
from .client import WoodpeckerClient as WoodpeckerClient
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
"""Woodpecker CI API client."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from api.clients.base import Client
|
|
||||||
|
|
||||||
|
|
||||||
class WoodpeckerClient(Client):
|
|
||||||
"""Client for the Woodpecker CI REST API.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
token : str
|
|
||||||
Personal API token.
|
|
||||||
base_url : str
|
|
||||||
Woodpecker API base URL.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
token: str,
|
|
||||||
*,
|
|
||||||
base_url: str = "",
|
|
||||||
**kw,
|
|
||||||
) -> None:
|
|
||||||
if not base_url:
|
|
||||||
from conf import cfg
|
|
||||||
|
|
||||||
base_url = f"{cfg.services.woodpecker}/api"
|
|
||||||
super().__init__(base_url, **kw)
|
|
||||||
self._token = token
|
|
||||||
|
|
||||||
def _default_headers(self) -> dict[str, str]:
|
|
||||||
return {"Authorization": f"Bearer {self._token}"}
|
|
||||||
|
|
||||||
# ── Repos ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
def list_repos(self) -> list[dict]:
|
|
||||||
return self.get("/repos").json()
|
|
||||||
|
|
||||||
def get_repo(self, repo_id: int) -> dict:
|
|
||||||
return self.get(f"/repos/{repo_id}").json()
|
|
||||||
|
|
||||||
# ── Pipelines ────────────────────────────────────────────
|
|
||||||
|
|
||||||
def list_pipelines(self, repo_id: int) -> list[dict]:
|
|
||||||
return self.get(f"/repos/{repo_id}/pipelines").json()
|
|
||||||
|
|
||||||
def get_pipeline(self, repo_id: int, number: int) -> dict:
|
|
||||||
return self.get(f"/repos/{repo_id}/pipelines/{number}").json()
|
|
||||||
|
|
||||||
def get_logs(self, repo_id: int, number: int, step: int) -> list[dict]:
|
|
||||||
return self.get(f"/repos/{repo_id}/logs/{number}/{step}").json()
|
|
||||||
|
|
||||||
# ── Secrets ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
def list_secrets(self, repo_id: int) -> list[dict]:
|
|
||||||
return self.get(f"/repos/{repo_id}/secrets").json()
|
|
||||||
|
|
||||||
def create_secret(self, repo_id: int, body: dict) -> dict:
|
|
||||||
return self.post(f"/repos/{repo_id}/secrets", json=body).json()
|
|
||||||
|
|
||||||
def update_secret(self, repo_id: int, name: str, body: dict) -> dict:
|
|
||||||
return self.patch(f"/repos/{repo_id}/secrets/{name}", json=body).json()
|
|
||||||
|
|
||||||
def delete_secret(self, repo_id: int, name: str) -> None:
|
|
||||||
self.delete(f"/repos/{repo_id}/secrets/{name}")
|
|
||||||
|
|
||||||
# ── Global secrets ─────────────────────────────────────
|
|
||||||
|
|
||||||
def list_global_secrets(self) -> list[dict]:
|
|
||||||
return self.get("/secrets").json()
|
|
||||||
|
|
||||||
def create_global_secret(self, body: dict) -> dict:
|
|
||||||
return self.post("/secrets", json=body).json()
|
|
||||||
|
|
||||||
def update_global_secret(self, name: str, body: dict) -> dict:
|
|
||||||
return self.patch(f"/secrets/{name}", json=body).json()
|
|
||||||
|
|
||||||
# ── Server ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
def version(self) -> dict:
|
|
||||||
return self.get("/version").json()
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
"""Tests for WoodpeckerClient."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from api.clients.woodpecker import WoodpeckerClient
|
|
||||||
|
|
||||||
|
|
||||||
class TestWoodpeckerAuth:
|
|
||||||
def test_bearer_header(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("wp-tok", _transport=cap.transport())
|
|
||||||
c.list_repos()
|
|
||||||
assert cap.requests[0].headers["authorization"] == "Bearer wp-tok"
|
|
||||||
|
|
||||||
|
|
||||||
class TestWoodpeckerRoutes:
|
|
||||||
def test_list_repos(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport([]))
|
|
||||||
c.list_repos()
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "GET"
|
|
||||||
assert req.url.path == "/api/repos"
|
|
||||||
|
|
||||||
def test_get_pipeline(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport({}))
|
|
||||||
c.get_pipeline(42, 7)
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "GET"
|
|
||||||
assert req.url.path == "/api/repos/42/pipelines/7"
|
|
||||||
|
|
||||||
def test_create_secret(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport({}))
|
|
||||||
c.create_secret(1, {"name": "s", "value": "v"})
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "POST"
|
|
||||||
assert req.url.path == "/api/repos/1/secrets"
|
|
||||||
|
|
||||||
def test_version(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport({"version": "2.0"}))
|
|
||||||
result = c.version()
|
|
||||||
assert result == {"version": "2.0"}
|
|
||||||
assert cap.requests[0].url.path == "/api/version"
|
|
||||||
|
|
||||||
def test_get_repo(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport({}))
|
|
||||||
c.get_repo(42)
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "GET"
|
|
||||||
assert req.url.path == "/api/repos/42"
|
|
||||||
|
|
||||||
def test_list_pipelines(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport([]))
|
|
||||||
c.list_pipelines(10)
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "GET"
|
|
||||||
assert req.url.path == "/api/repos/10/pipelines"
|
|
||||||
|
|
||||||
def test_get_logs(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport([]))
|
|
||||||
c.get_logs(10, 5, 1)
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "GET"
|
|
||||||
assert req.url.path == "/api/repos/10/logs/5/1"
|
|
||||||
|
|
||||||
def test_list_secrets(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport([]))
|
|
||||||
c.list_secrets(10)
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "GET"
|
|
||||||
assert req.url.path == "/api/repos/10/secrets"
|
|
||||||
|
|
||||||
def test_update_secret(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport({}))
|
|
||||||
c.update_secret(10, "my_secret", {"value": "new"})
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "PATCH"
|
|
||||||
assert req.url.path == "/api/repos/10/secrets/my_secret"
|
|
||||||
|
|
||||||
def test_delete_secret(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport(None, status=204))
|
|
||||||
c.delete_secret(10, "old_secret")
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "DELETE"
|
|
||||||
assert req.url.path == "/api/repos/10/secrets/old_secret"
|
|
||||||
|
|
||||||
def test_list_global_secrets(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport([]))
|
|
||||||
c.list_global_secrets()
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "GET"
|
|
||||||
assert req.url.path == "/api/secrets"
|
|
||||||
|
|
||||||
def test_create_global_secret(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport({}))
|
|
||||||
c.create_global_secret({"name": "s", "value": "v"})
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "POST"
|
|
||||||
assert req.url.path == "/api/secrets"
|
|
||||||
|
|
||||||
def test_update_global_secret(self, capture_transport):
|
|
||||||
cap = capture_transport
|
|
||||||
c = WoodpeckerClient("t", _transport=cap.transport({}))
|
|
||||||
c.update_global_secret("root_key", {"value": "new"})
|
|
||||||
req = cap.requests[0]
|
|
||||||
assert req.method == "PATCH"
|
|
||||||
assert req.url.path == "/api/secrets/root_key"
|
|
||||||
Reference in New Issue
Block a user