Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8030693db8
|
||
|
|
6bd8ac8331
|
||
|
|
6a950afa1a
|
||
|
|
fd5dcffeba
|
||
|
|
534b3121c8
|
||
|
|
dd596dd36d | ||
|
|
af9d5403ce
|
||
|
|
8abbe6ffb0
|
||
|
|
c4b6251344
|
||
|
|
70958f16f5
|
||
|
|
d4a5fe3c68
|
||
|
|
16b3435ca5
|
||
|
|
93dc9c274d
|
||
|
|
8fed18ee3b | ||
|
|
3110a88740 | ||
|
|
8c63969d2f | ||
|
|
6927174050 | ||
|
|
7febdd3330 | ||
|
|
1ffd25af7e | ||
|
|
a1c6c4171a | ||
|
|
69d14364f3 | ||
|
|
0ca4e9cf02
|
||
|
|
a143d31deb | ||
|
|
c811f8630b
|
||
|
|
a15846e6b7
|
||
|
|
212a12c304
|
||
|
|
bdd0139ddb | ||
|
|
14a46dca18
|
||
|
|
f113075a5b | ||
|
|
36980c848f
|
No files matched your search
+11
-57
@@ -1,60 +1,14 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
|
setup: true
|
||||||
orbs:
|
orbs:
|
||||||
qqq-orb: kingsrook/qqq-orb@0.6.9
|
qqq-orb: kingsrook/qqq-orb@0.8
|
||||||
|
|
||||||
commands:
|
|
||||||
configure-package-install:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Keep package installation unattended
|
|
||||||
command: |
|
|
||||||
# These build machines are discarded after the job.
|
|
||||||
sudo install -d /etc/needrestart/conf.d
|
|
||||||
printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
test_only:
|
setup:
|
||||||
jobs:
|
jobs:
|
||||||
- qqq-orb/mvn_test_only:
|
- qqq-orb/generate_pipeline:
|
||||||
pre-steps:
|
orb_version: '0.8'
|
||||||
- configure-package-install
|
filters:
|
||||||
context: [qqq-maven-registry-credentials]
|
branches:
|
||||||
filters:
|
ignore: automation/qbit-rc-pending
|
||||||
branches:
|
tags:
|
||||||
ignore: /(develop|release\/.*)/
|
only: /^v\d+\.\d+\.\d+$/
|
||||||
|
|
||||||
publish_snapshot:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: snapshot
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: [develop]
|
|
||||||
|
|
||||||
publish_release_candidate:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: release_candidate
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: [/release\/.*/]
|
|
||||||
|
|
||||||
publish_release:
|
|
||||||
jobs:
|
|
||||||
- qqq-orb/mvn_publish:
|
|
||||||
pre-steps:
|
|
||||||
- configure-package-install
|
|
||||||
context: [qqq-maven-registry-credentials]
|
|
||||||
branch_type: release
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
tags:
|
|
||||||
only: [/^v\d+\.\d+\.\d+$/]
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
name: build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
maven:
|
||||||
|
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
|
||||||
|
secrets: inherit
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: CodeQL
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
- release/**
|
||||||
|
- hotfix/**
|
||||||
|
pull_request: {}
|
||||||
|
schedule:
|
||||||
|
- cron: 41 16 * * 0
|
||||||
|
workflow_dispatch: {}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
security-events: write
|
||||||
|
packages: read
|
||||||
|
concurrency:
|
||||||
|
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
|
||||||
@@ -1,25 +1,74 @@
|
|||||||
# Cut the next release candidate (QRun-IO/qqq docs/QRUN-QBIT-REPO-STANDARD.md, C6):
|
|
||||||
# run this workflow by hand, or merge a develop PR labelled `release:rc`. It merges develop into
|
|
||||||
# the release line and the release/* push runs CircleCI publish_release_candidate.
|
|
||||||
name: Release candidate
|
name: Release candidate
|
||||||
|
'on':
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
dry_run:
|
dry_run:
|
||||||
description: Print the plan without merging or creating branches
|
description: Plan a cut without changing release state
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
finalize:
|
||||||
|
description: Retry finalization of an existing cut; do not cut another RC
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
recover:
|
||||||
|
description: Recover the same published RC after a deployment timeout; never
|
||||||
|
reupload
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
circle_build:
|
||||||
|
description: Original Circle publisher build number (recovery only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
merge_sha:
|
||||||
|
description: Existing cut merge_sha (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
bump_sha:
|
||||||
|
description: Existing cut bump_sha (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
branch:
|
||||||
|
description: Existing cut branch (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
version:
|
||||||
|
description: Existing cut version (finalize retry only)
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types:
|
||||||
branches: [develop]
|
- closed
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
repository_dispatch:
|
||||||
|
types:
|
||||||
|
- qbit-rc-published
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
actions: read
|
||||||
|
checks: read
|
||||||
jobs:
|
jobs:
|
||||||
release-rc:
|
release-rc:
|
||||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release:rc'))
|
if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch'
|
||||||
uses: QRun-IO/.github/.github/workflows/qbit-release-rc.yml@main
|
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
|
||||||
|
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
|
||||||
|
'release:rc'))
|
||||||
|
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
|
||||||
with:
|
with:
|
||||||
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
|
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
|
||||||
|
}}
|
||||||
|
finalize: ${{ github.event_name == 'repository_dispatch' || (github.event_name
|
||||||
|
== 'workflow_dispatch' && inputs.finalize) }}
|
||||||
|
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
|
||||||
|
}}
|
||||||
|
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|
||||||
|
|| '' }}
|
||||||
|
merge_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.merge_sha
|
||||||
|
|| inputs.merge_sha || '' }}
|
||||||
|
bump_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.bump_sha
|
||||||
|
|| inputs.bump_sha || '' }}
|
||||||
|
branch: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch
|
||||||
|
|| inputs.branch || '' }}
|
||||||
|
version: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version
|
||||||
|
|| inputs.version || '' }}
|
||||||
|
secrets: inherit
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
profile: qbit
|
||||||
|
orb_version: '0.8'
|
||||||
|
default_branch: develop
|
||||||
|
java_version: '21'
|
||||||
|
browsers: true
|
||||||
|
api_checks: false
|
||||||
|
docs_only: false
|
||||||
|
release_on_main: false
|
||||||
|
release_tag_policy: stable
|
||||||
|
test_branch_policy: non-release
|
||||||
|
publish:
|
||||||
|
snapshot: true
|
||||||
|
release_candidate: true
|
||||||
|
release: true
|
||||||
|
asciidoc: false
|
||||||
|
release_completion: true
|
||||||
@@ -5,6 +5,7 @@ All notable changes are documented here. This project follows Keep a Changelog a
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- When the host declares the QQQ `release-notes` service principal (`ReleaseNotesQBitConfig.withServicePrincipal`), detection, drafting and publishing run as that principal instead of the system user. It uses its granted permissions, is audited as `service:release-notes`, and fails closed if unavailable. With no principal declared, behavior is unchanged. Needs the QQQ release with service identities (qqq#1255).
|
||||||
- Release records, items, settings and per-user read state as standard QQQ tables (`release_notes_*`) with a Liquibase changelog (item-to-release foreign key) and seeded settings (require approval, all users, notify).
|
- Release records, items, settings and per-user read state as standard QQQ tables (`release_notes_*`) with a Liquibase changelog (item-to-release foreign key) and seeded settings (require approval, all users, notify).
|
||||||
- Once-per-version detection across pods and restarts (unique app + version), started by the host after startup.
|
- Once-per-version detection across pods and restarts (unique app + version), started by the host after startup.
|
||||||
- `ReleaseSource` SPI with `GitHubReleaseSource` (TAG and WINDOW modes; token read from the environment at call time) and `ComponentVersionsSource`.
|
- `ReleaseSource` SPI with `GitHubReleaseSource` (TAG and WINDOW modes; token read from the environment at call time) and `ComponentVersionsSource`.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
|
||||||
<revision>0.1.0-SNAPSHOT</revision>
|
<revision>0.1.0-SNAPSHOT</revision>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.kingsrook</groupId>
|
<groupId>com.kingsrook</groupId>
|
||||||
<artifactId>qbit-build-parent</artifactId>
|
<artifactId>qbit-build-parent</artifactId>
|
||||||
<version>2.1.1</version>
|
<version>2.2.1</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.kingsrook.qbits</groupId>
|
<groupId>com.kingsrook.qbits</groupId>
|
||||||
@@ -219,7 +220,7 @@
|
|||||||
<activeByDefault>true</activeByDefault>
|
<activeByDefault>true</activeByDefault>
|
||||||
</activation>
|
</activation>
|
||||||
<properties>
|
<properties>
|
||||||
<qqq.snapshot.version>4.1.0-RC.13</qqq.snapshot.version>
|
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class ReleaseNotesQBitConfig implements QBitConfig
|
|||||||
private static final Pattern RELEASE_VERSION = Pattern.compile("^\\d+\\.\\d+\\.\\d+$");
|
private static final Pattern RELEASE_VERSION = Pattern.compile("^\\d+\\.\\d+\\.\\d+$");
|
||||||
|
|
||||||
private String backendName;
|
private String backendName;
|
||||||
|
private String servicePrincipal = "release-notes";
|
||||||
private String appName = "releaseNotes";
|
private String appName = "releaseNotes";
|
||||||
private String parentAppName;
|
private String parentAppName;
|
||||||
private String permissionBaseName = "release_notes";
|
private String permissionBaseName = "release_notes";
|
||||||
@@ -485,4 +486,26 @@ public class ReleaseNotesQBitConfig implements QBitConfig
|
|||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for servicePrincipal: when the host declares this QQQ service
|
||||||
|
** principal, release detection, drafting and publishing run as it (its
|
||||||
|
** granted permissions, audited as service:{name}) instead of the system user.
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getServicePrincipal()
|
||||||
|
{
|
||||||
|
return (this.servicePrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for servicePrincipal (default release-notes).
|
||||||
|
*******************************************************************************/
|
||||||
|
public ReleaseNotesQBitConfig withServicePrincipal(String servicePrincipal)
|
||||||
|
{
|
||||||
|
this.servicePrincipal = servicePrincipal;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,9 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||||
|
import com.kingsrook.qqq.backend.core.serviceidentity.ServiceIdentities;
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
@@ -160,12 +162,18 @@ public class ReleaseNotesRuntime implements QSupplementalInstanceMetaData
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Run as the system user.
|
** Run qbit work with no signed-in user: as the release-notes service
|
||||||
|
** principal when the host declares it (QQQ service identities; an
|
||||||
|
** unavailable principal fails, never falling back), else as the system user
|
||||||
|
** (previous behavior).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public <T> T asSystem(Work<T> work) throws Exception
|
public <T> T asSystem(Work<T> work) throws Exception
|
||||||
{
|
{
|
||||||
AtomicReference<T> result = new AtomicReference<>();
|
AtomicReference<T> result = new AtomicReference<>();
|
||||||
QContext.withTemporaryContext(new CapturedContext(instance, new QSystemUserSession(instance)), () -> result.set(work.run()));
|
String principal = config.getServicePrincipal();
|
||||||
|
QSession session = principal != null && ServiceIdentities.isDeclared(instance, principal)
|
||||||
|
? ServiceIdentities.session(instance, principal) : new QSystemUserSession(instance);
|
||||||
|
QContext.withTemporaryContext(new CapturedContext(instance, session), () -> result.set(work.run()));
|
||||||
return (result.get());
|
return (result.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,44 @@
|
|||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
BasicErrorResponseV1:
|
||||||
|
description: "The error body of every UI API operation (core and extensions)."
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
description: "The error message (the same as message; kept for clients of\
|
||||||
|
\ UI API 1.0 to 1.5)."
|
||||||
|
type: "string"
|
||||||
|
code:
|
||||||
|
description: "A stable, machine-readable code. Well-known values: BAD_REQUEST\
|
||||||
|
\ (400), UNAUTHENTICATED (401),\nPERMISSION_DENIED (403), NOT_FOUND (404),\
|
||||||
|
\ CONFLICT (409), RATE_LIMITED (429), INTERNAL_ERROR (500),\nERROR (other).\
|
||||||
|
\ Extensions may add codes; treat an unknown code by the HTTP status.\
|
||||||
|
\ Since 1.6.0."
|
||||||
|
type: "string"
|
||||||
|
message:
|
||||||
|
description: "A message to show the user. Since 1.6.0."
|
||||||
|
type: "string"
|
||||||
|
fieldErrors:
|
||||||
|
description: "Errors about particular request fields, when known. Since\
|
||||||
|
\ 1.6.0."
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
field:
|
||||||
|
description: "The field (or parameter) name."
|
||||||
|
type: "string"
|
||||||
|
message:
|
||||||
|
description: "The error about that field."
|
||||||
|
type: "string"
|
||||||
|
code:
|
||||||
|
description: "An optional machine-readable code, e.g. REQUIRED, TOO_LONG,\
|
||||||
|
\ PATTERN."
|
||||||
|
type: "string"
|
||||||
|
type: "object"
|
||||||
|
type: "array"
|
||||||
|
traceId:
|
||||||
|
description: "Identifies this request in the server logs (also the X-QQQ-Trace-Id\
|
||||||
|
\ response header); quote it when reporting a problem. Since 1.6.0."
|
||||||
|
type: "string"
|
||||||
|
type: "object"
|
||||||
ReleaseNotesErrorV1:
|
ReleaseNotesErrorV1:
|
||||||
properties:
|
properties:
|
||||||
error:
|
error:
|
||||||
@@ -110,9 +149,42 @@ components:
|
|||||||
type: "object"
|
type: "object"
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
sessionUuidCookie:
|
sessionUuidCookie:
|
||||||
|
description: "The session cookie set by POST /qqq/v1/manageSession (web clients)."
|
||||||
in: "cookie"
|
in: "cookie"
|
||||||
name: "sessionUUID"
|
name: "sessionUUID"
|
||||||
type: "apiKey"
|
type: "apiKey"
|
||||||
|
bearerAuth:
|
||||||
|
bearerFormat: "JWT or session UUID"
|
||||||
|
description: "`Authorization: Bearer <token>`, where the token is either\n-\
|
||||||
|
\ an OAuth2 access token (a signed JWT) issued by the app's identity provider\
|
||||||
|
\ to the app's client,\n or to its native client (`values.nativeClientId`\
|
||||||
|
\ in GET /qqq/v1/metaData/authentication), for\n example from the device-authorization\
|
||||||
|
\ grant or auth-code + PKCE. It is verified against the\n provider's keys\
|
||||||
|
\ (issuer, audience, expiry, signature) and gives the same user and permissions\n\
|
||||||
|
\ as signing in on the web. When it expires (401), refresh it at the provider\
|
||||||
|
\ and retry; or\n- a session UUID returned by POST /qqq/v1/manageSession,\
|
||||||
|
\ which the server keeps refreshed."
|
||||||
|
scheme: "bearer"
|
||||||
|
type: "http"
|
||||||
|
oauth2:
|
||||||
|
description: "The app's OAuth2 / OpenID Connect provider. The real endpoints\
|
||||||
|
\ come from the provider's discovery\ndocument: `{values.baseUrl}.well-known/openid-configuration`,\
|
||||||
|
\ with values from\nGET /qqq/v1/metaData/authentication. Native clients use\
|
||||||
|
\ `values.nativeClientId` (a public client)\nwith auth-code + PKCE (loopback\
|
||||||
|
\ or custom-scheme redirect URI registered at the provider) or the\ndevice-authorization\
|
||||||
|
\ grant (`device_authorization_endpoint`, RFC 8628), then send the access\
|
||||||
|
\ token\nas `Authorization: Bearer`."
|
||||||
|
flows:
|
||||||
|
authorizationCode:
|
||||||
|
authorizationUrl: "/authorize"
|
||||||
|
scopes:
|
||||||
|
email: "Email address"
|
||||||
|
offline_access: "A refresh token, to renew the access token without signing\
|
||||||
|
\ in again"
|
||||||
|
openid: "OpenID Connect sign-in"
|
||||||
|
profile: "Name, username and groups (permissions come from the groups)"
|
||||||
|
tokenUrl: "/token"
|
||||||
|
type: "oauth2"
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
email: "contact@kingsrook.com"
|
email: "contact@kingsrook.com"
|
||||||
@@ -158,10 +230,44 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ReleaseNotesReleasesV1"
|
$ref: "#/components/schemas/ReleaseNotesReleasesV1"
|
||||||
description: "Successful Response"
|
description: "Successful Response"
|
||||||
|
403:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "The user (or the credential's scope) may not do this (code\
|
||||||
|
\ PERMISSION_DENIED)."
|
||||||
|
404:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Not found, or not visible to this user (code NOT_FOUND)."
|
||||||
|
409:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Conflict with the current state, e.g. an edit conflict or\
|
||||||
|
\ an invalid transition (code CONFLICT)."
|
||||||
|
429:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Too many requests (code RATE_LIMITED); retry later."
|
||||||
security:
|
security:
|
||||||
- sessionUuidCookie:
|
- sessionUuidCookie:
|
||||||
- "N/A"
|
- "N/A"
|
||||||
|
- bearerAuth:
|
||||||
|
- "N/A"
|
||||||
|
- oauth2:
|
||||||
|
- "openid"
|
||||||
|
- "profile"
|
||||||
|
- "email"
|
||||||
summary: "Published release notes"
|
summary: "Published release notes"
|
||||||
|
tags:
|
||||||
|
- "release-notes"
|
||||||
/qqq/v1/qbit/release-notes/summary:
|
/qqq/v1/qbit/release-notes/summary:
|
||||||
get:
|
get:
|
||||||
description: "How many published releases are new to the signed-in user, and\
|
description: "How many published releases are new to the signed-in user, and\
|
||||||
@@ -186,10 +292,44 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ReleaseNotesSummaryV1"
|
$ref: "#/components/schemas/ReleaseNotesSummaryV1"
|
||||||
description: "Successful Response"
|
description: "Successful Response"
|
||||||
|
403:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "The user (or the credential's scope) may not do this (code\
|
||||||
|
\ PERMISSION_DENIED)."
|
||||||
|
404:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Not found, or not visible to this user (code NOT_FOUND)."
|
||||||
|
409:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Conflict with the current state, e.g. an edit conflict or\
|
||||||
|
\ an invalid transition (code CONFLICT)."
|
||||||
|
429:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Too many requests (code RATE_LIMITED); retry later."
|
||||||
security:
|
security:
|
||||||
- sessionUuidCookie:
|
- sessionUuidCookie:
|
||||||
- "N/A"
|
- "N/A"
|
||||||
|
- bearerAuth:
|
||||||
|
- "N/A"
|
||||||
|
- oauth2:
|
||||||
|
- "openid"
|
||||||
|
- "profile"
|
||||||
|
- "email"
|
||||||
summary: "What's new summary"
|
summary: "What's new summary"
|
||||||
|
tags:
|
||||||
|
- "release-notes"
|
||||||
/qqq/v1/qbit/release-notes/seen:
|
/qqq/v1/qbit/release-notes/seen:
|
||||||
post:
|
post:
|
||||||
description: "Clears the signed-in user's new indicator. Takes no body."
|
description: "Clears the signed-in user's new indicator. Takes no body."
|
||||||
@@ -213,7 +353,47 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ReleaseNotesSeenV1"
|
$ref: "#/components/schemas/ReleaseNotesSeenV1"
|
||||||
description: "Successful Response"
|
description: "Successful Response"
|
||||||
|
403:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "The user (or the credential's scope) may not do this (code\
|
||||||
|
\ PERMISSION_DENIED)."
|
||||||
|
404:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Not found, or not visible to this user (code NOT_FOUND)."
|
||||||
|
409:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Conflict with the current state, e.g. an edit conflict or\
|
||||||
|
\ an invalid transition (code CONFLICT)."
|
||||||
|
429:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicErrorResponseV1"
|
||||||
|
description: "Too many requests (code RATE_LIMITED); retry later."
|
||||||
security:
|
security:
|
||||||
- sessionUuidCookie:
|
- sessionUuidCookie:
|
||||||
- "N/A"
|
- "N/A"
|
||||||
|
- bearerAuth:
|
||||||
|
- "N/A"
|
||||||
|
- oauth2:
|
||||||
|
- "openid"
|
||||||
|
- "profile"
|
||||||
|
- "email"
|
||||||
summary: "Mark What's new as seen"
|
summary: "Mark What's new as seen"
|
||||||
|
tags:
|
||||||
|
- "release-notes"
|
||||||
|
servers:
|
||||||
|
- description: "The QQQ server that serves this document (paths include /qqq/v1)."
|
||||||
|
url: "/"
|
||||||
|
tags:
|
||||||
|
- description: "Operations of the release-notes extension."
|
||||||
|
name: "release-notes"
|
||||||
@@ -45,6 +45,8 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
|||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||||
|
import com.kingsrook.qqq.backend.core.serviceidentity.QServicePrincipalMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.serviceidentity.ServiceIdentitiesMetaData;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static com.kingsrook.qbits.releasenotes.ReleaseNotesTestSupport.NOTIFICATIONS;
|
import static com.kingsrook.qbits.releasenotes.ReleaseNotesTestSupport.NOTIFICATIONS;
|
||||||
import static com.kingsrook.qbits.releasenotes.ReleaseNotesTestSupport.pr;
|
import static com.kingsrook.qbits.releasenotes.ReleaseNotesTestSupport.pr;
|
||||||
@@ -97,6 +99,24 @@ class ReleaseLifecycleTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** With the release-notes principal declared, qbit work runs as it, never
|
||||||
|
** as the system user.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void detectsAsTheDeclaredPrincipal() throws Exception
|
||||||
|
{
|
||||||
|
QInstance instance = ReleaseNotesTestSupport.instance("1.2.3", CHANGES);
|
||||||
|
ReleaseNotesRuntime runtime = ReleaseNotesTestSupport.runtime(instance, "2026-10-15T12:00:00Z");
|
||||||
|
ServiceIdentitiesMetaData.declare(instance, new QServicePrincipalMetaData().withName("release-notes"));
|
||||||
|
String[] ranAs = new String[1];
|
||||||
|
runtime.asSystem(() -> ranAs[0] = QContext.getQSession().getUser().getIdReference());
|
||||||
|
assertThat(ranAs[0]).isEqualTo("service:release-notes");
|
||||||
|
assertThat(runtime.service().detect()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Pods starting together record the version exactly once.
|
** Pods starting together record the version exactly once.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|||||||
@@ -166,7 +166,10 @@ class ReleaseNotesMetaDataTest
|
|||||||
assertThat(UiApiContract.check(generated, Files.readString(path), null, 1, "mvnw -Dtest=ReleaseNotesMetaDataTest -Dreleasenotes.openapi.update=true test")).isEmpty();
|
assertThat(UiApiContract.check(generated, Files.readString(path), null, 1, "mvnw -Dtest=ReleaseNotesMetaDataTest -Dreleasenotes.openapi.update=true test")).isEmpty();
|
||||||
var model = version.generateExtensionOpenAPIModel("qqq", extension);
|
var model = version.generateExtensionOpenAPIModel("qqq", extension);
|
||||||
assertThat(model.getPaths().keySet()).containsExactlyInAnyOrder("/qqq/v1/qbit/release-notes/releases", "/qqq/v1/qbit/release-notes/summary", "/qqq/v1/qbit/release-notes/seen");
|
assertThat(model.getPaths().keySet()).containsExactlyInAnyOrder("/qqq/v1/qbit/release-notes/releases", "/qqq/v1/qbit/release-notes/summary", "/qqq/v1/qbit/release-notes/seen");
|
||||||
assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> name.startsWith("ReleaseNotes") && name.endsWith("V1"));
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// QQQ UI API 1.6.0 adds the shared error schema to every extension. //
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
assertThat(model.getComponents().getSchemas().keySet()).allMatch(name -> name.equals("BasicErrorResponseV1") || (name.startsWith("ReleaseNotes") && name.endsWith("V1")));
|
||||||
assertThat(generated).doesNotContain("sourceRef", "sourceLog", "commitSha", "componentVersions");
|
assertThat(generated).doesNotContain("sourceRef", "sourceLog", "commitSha", "componentVersions");
|
||||||
QInstance instance = ReleaseNotesTestSupport.instance("1.0.0", List.of());
|
QInstance instance = ReleaseNotesTestSupport.instance("1.0.0", List.of());
|
||||||
assertThat(MiddlewareSpecExtensionsMetaData.of(instance).getExtensions()).containsOnlyKeys("release-notes");
|
assertThat(MiddlewareSpecExtensionsMetaData.of(instance).getExtensions()).containsOnlyKeys("release-notes");
|
||||||
|
|||||||
Reference in new issue
Block a user