From 06b099969870c577d6e0e640b429f8c4a8d9697d Mon Sep 17 00:00:00 2001
From: Yanks Yoon <37652070+yanksyoon@users.noreply.github.com>
Date: Tue, 3 Dec 2024 09:23:21 +0800
Subject: [PATCH] chore: update java version (#198)
* debug
* debug (typo)
* fix: rockcraft version
* update identity bundle versions
* trivy ignore unpatched vuln
* test: use juju 3.5
* build command and fix comments
* try manual deploy
* test: public ingress relation jenkins
* fix: typo & lint
* test: add oathkeeper relations
* test: remove wait for idle before deploy
* test: fix: oathkeeper relation fix
* test: traefik certificates
* test: use model.integrate instead of deprecated model.relatet
* test: model wait for idle to get around buggy charms
* await status code
* fix tests for auth proxy
* fix lint
* fix lint
* proper auth address
* fix: auth proxy test
* test: fix thinbackup plugin request
* remove debug
* fix type hint
* split plugins test
* try 1.29 microk8s
* debug
* kubeconfig dir & debug log
* revert to 1.28 microk8s
* remove testing microk8s config
* chore: remove patched CVEs
* chore: add unpatched CVE
* fix: plugins fix
* remove debug
---
.github/workflows/integration_test.yaml | 6 +-
.trivyignore | 10 +-
jenkins_rock/rockcraft.yaml | 23 +-
src-docs/jenkins.py.md | 35 +--
src/jenkins.py | 3 +-
tests/integration/conftest.py | 180 ++++++++----
tests/integration/constants.py | 9 +
tests/integration/dex.py | 1 +
.../files/identity-bundle-edge-patched.yaml | 76 -----
tests/integration/helpers.py | 3 +-
tests/integration/requirements.txt | 6 +-
tests/integration/test_auth_proxy.py | 56 +++-
...test_plugins.py => test_plugins_part_1.py} | 261 +----------------
tests/integration/test_plugins_part_2.py | 266 ++++++++++++++++++
tests/integration/test_upgrade.py | 4 +-
tests/unit/conftest.py | 4 +-
tox.ini | 17 +-
17 files changed, 508 insertions(+), 452 deletions(-)
delete mode 100644 tests/integration/files/identity-bundle-edge-patched.yaml
rename tests/integration/{test_plugins.py => test_plugins_part_1.py} (58%)
create mode 100644 tests/integration/test_plugins_part_2.py
diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml
index d0ada1b5..498b2de8 100644
--- a/.github/workflows/integration_test.yaml
+++ b/.github/workflows/integration_test.yaml
@@ -15,12 +15,12 @@ jobs:
channel: 1.28-strict/stable
extra-arguments: |
--kube-config=${GITHUB_WORKSPACE}/kube-config
- modules: '["test_auth_proxy.py", "test_cos.py", "test_ingress.py", "test_jenkins.py", "test_k8s_agent.py", "test_machine_agent.py", "test_plugins.py", "test_proxy.py", "test_upgrade.py", "test_external_agent.py"]'
+ modules: '["test_auth_proxy.py", "test_cos.py", "test_ingress.py", "test_jenkins.py", "test_k8s_agent.py", "test_machine_agent.py", "test_plugins_part_1.py", "test_plugins_part_2.py", "test_proxy.py", "test_upgrade.py", "test_external_agent.py"]'
pre-run-script: |
-c "sudo microk8s config > ${GITHUB_WORKSPACE}/kube-config
chmod +x tests/integration/pre_run_script.sh
./tests/integration/pre_run_script.sh"
- juju-channel: 3.1/stable
+ juju-channel: 3/stable
self-hosted-runner: true
self-hosted-runner-label: "xlarge"
- microk8s-addons: "dns ingress rbac storage metallb:10.15.119.2-10.15.119.4 registry"
+ microk8s-addons: "dns ingress rbac hostpath-storage metallb:10.15.119.2-10.15.119.4 registry"
diff --git a/.trivyignore b/.trivyignore
index b9c9d71e..2591634c 100644
--- a/.trivyignore
+++ b/.trivyignore
@@ -1,8 +1,2 @@
-# Pebble CVEs
-CVE-2024-24790
-CVE-2023-45288
-CVE-2024-34156
-# Jenkins plugin manager CVEs
-CVE-2023-5072
-# Jenkins CVEs
-CVE-2016-1000027
+# Jenkins executable
+CVE-2024-47072
diff --git a/jenkins_rock/rockcraft.yaml b/jenkins_rock/rockcraft.yaml
index 33da6922..374bdaad 100644
--- a/jenkins_rock/rockcraft.yaml
+++ b/jenkins_rock/rockcraft.yaml
@@ -36,17 +36,30 @@ parts:
- curl
- libnss3
- unzip
- - default-jdk-headless
+ - openjdk-21-jdk-headless
+ # Referred from https://github.com/jenkinsci/docker/blob/master/debian/bookworm/hotspot/Dockerfile
overlay-packages:
- bash
+ - ca-certificates
- ca-certificates-java
+ - curl
- fonts-dejavu-core
- - libfontconfig1
- - default-jre-headless
- git
+ - gnupg
+ - gpg
+ - libfontconfig1
+ - libfreetype6
+ - libharfbuzz0b
+ - openjdk-21-jre-headless
+ - procps
+ - ssh-client
+ - tini
+ - tzdata
+ - wget
+ - unzip
build-environment:
- - JENKINS_VERSION: 2.462.2
- - JENKINS_PLUGIN_MANAGER_VERSION: 2.12.13
+ - JENKINS_VERSION: 2.479.1
+ - JENKINS_PLUGIN_MANAGER_VERSION: 2.13.2
override-build: |
mkdir -p ${CRAFT_PART_INSTALL}/{srv/jenkins/,etc/default/jenkins/}
# Use jenkins war rather than apt install for easier Jenkins version control.
diff --git a/src-docs/jenkins.py.md b/src-docs/jenkins.py.md
index fb02817e..d9cf0d0d 100644
--- a/src-docs/jenkins.py.md
+++ b/src-docs/jenkins.py.md
@@ -8,6 +8,7 @@ Functions to operate Jenkins.
**Global Variables**
---------------
- **WEB_PORT**
+- **JENKINS_PLUGIN_MANAGER_VERSION**
- **LOGIN_PATH**
- **JUJU_API_TOKEN**
- **REQUIRED_PLUGINS**
@@ -30,7 +31,7 @@ Functions to operate Jenkins.
---
-
+
## function `get_admin_credentials`
@@ -54,7 +55,7 @@ Retrieve admin credentials.
---
-
+
## function `is_storage_ready`
@@ -84,7 +85,7 @@ Return whether the Jenkins home directory is mounted and owned by jenkins.
---
-
+
## function `install_default_config`
@@ -103,7 +104,7 @@ Install default jenkins-config.xml.
---
-
+
## function `install_auth_proxy_config`
@@ -122,7 +123,7 @@ Install jenkins-config.xml for auth_proxy.
---
-
+
## function `install_logging_config`
@@ -141,7 +142,7 @@ Install logging config.
---
-
+
## function `get_agent_name`
@@ -202,7 +203,7 @@ Wrapper for Jenkins functionality.
Attrs: environment: the Jenkins environment configuration. web_url: the Jenkins web URL. login_url: the Jenkins login URL. version: the Jenkins version.
-
+
### function `__init__`
@@ -256,7 +257,7 @@ Returns: the web URL.
---
-
+
### function `add_agent_node`
@@ -281,7 +282,7 @@ Add a Jenkins agent node.
---
-
+
### function `bootstrap`
@@ -311,7 +312,7 @@ Initialize and install Jenkins.
---
-
+
### function `get_node_secret`
@@ -341,7 +342,7 @@ Get node secret from jenkins.
---
-
+
### function `remove_agent_node`
@@ -366,7 +367,7 @@ Remove a Jenkins agent node.
---
-
+
### function `remove_unlisted_plugins`
@@ -396,7 +397,7 @@ Remove plugins that are not in the list of desired plugins.
---
-
+
### function `rotate_credentials`
@@ -425,7 +426,7 @@ Invalidate all Jenkins sessions and create new password for admin account.
---
-
+
### function `safe_restart`
@@ -449,7 +450,7 @@ Safely restart Jenkins server after all jobs are done executing.
---
-
+
### function `update_prefix`
@@ -467,7 +468,7 @@ Update jenkins prefix.
---
-
+
### function `wait_ready`
@@ -529,7 +530,7 @@ Represents an error probing for Jenkins storage mount.
- `msg`: Explanation of the error.
-
+
### function `__init__`
diff --git a/src/jenkins.py b/src/jenkins.py
index e949eeff..95cf99a6 100644
--- a/src/jenkins.py
+++ b/src/jenkins.py
@@ -31,6 +31,7 @@
logger = logging.getLogger(__name__)
WEB_PORT = 8080
+JENKINS_PLUGIN_MANAGER_VERSION = "2.13.2"
LOGIN_PATH = "/login?from=%2F"
EXECUTABLES_PATH = Path("/srv/jenkins/")
JENKINS_HOME_PATH = Path("/var/lib/jenkins")
@@ -841,7 +842,7 @@ def _install_plugins(
"java",
*proxy_args,
"-jar",
- "jenkins-plugin-manager-2.12.13.jar",
+ f"jenkins-plugin-manager-{JENKINS_PLUGIN_MANAGER_VERSION}.jar",
"-w",
"jenkins.war",
"-d",
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index 368f8189..b06a43f4 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -3,6 +3,7 @@
"""Fixtures for Jenkins-k8s-operator charm integration tests."""
+import asyncio
import os
import random
import secrets
@@ -40,7 +41,7 @@
from .helpers import generate_jenkins_client_from_application, get_pod_ip
from .types_ import KeycloakOIDCMetadata, LDAPSettings, ModelAppUnit, UnitWebClient
-KUBECONFIG = os.environ.get("TESTING_KUBECONFIG", "~/.kube/config")
+KUBECONFIG = os.environ.get("TESTING_KUBECONFIG", "./kube-config")
IDENTITY_PLATFORM_APPS = [
"traefik-admin",
"traefik-public",
@@ -199,23 +200,21 @@ async def jenkins_k8s_agents_fixture(model: Model):
@pytest_asyncio.fixture(scope="module", name="k8s_agent_related_app")
async def k8s_agent_related_app_fixture(
- jenkins_k8s_agents: Application,
- application: Application,
+ jenkins_k8s_agents: Application, application: Application, model: Model
):
"""The Jenkins-k8s server charm related to Jenkins-k8s agent charm through agent relation."""
- await application.relate(
- state.AGENT_RELATION, f"{jenkins_k8s_agents.name}:{state.AGENT_RELATION}"
+ await model.integrate(
+ f"{application.name}:{state.AGENT_RELATION}",
+ f"{jenkins_k8s_agents.name}:{state.AGENT_RELATION}",
)
- await application.model.wait_for_idle(
+ await model.wait_for_idle(
apps=[application.name, jenkins_k8s_agents.name], wait_for_active=True, check_freq=5
)
return application
@pytest_asyncio.fixture(scope="function", name="extra_jenkins_k8s_agents")
-async def extra_jenkins_k8s_agents_fixture(
- model: Model,
-) -> AsyncGenerator[Application, None]:
+async def extra_jenkins_k8s_agents_fixture(model: Model) -> AsyncGenerator[Application, None]:
"""The Jenkins k8s agent."""
agent_app: Application = await model.deploy(
"jenkins-agent-k8s",
@@ -230,12 +229,13 @@ async def extra_jenkins_k8s_agents_fixture(
@pytest_asyncio.fixture(scope="function", name="k8s_deprecated_agent_related_app")
async def k8s_deprecated_agent_related_app_fixture(
- jenkins_k8s_agents: Application,
- application: Application,
+ jenkins_k8s_agents: Application, application: Application, model: Model
):
"""The Jenkins-k8s charm related to Jenkins-k8s agent through deprecated agent relation."""
- await application.relate(state.DEPRECATED_AGENT_RELATION, jenkins_k8s_agents.name)
- await application.model.wait_for_idle(
+ await model.integrate(
+ f"{application.name}:{state.DEPRECATED_AGENT_RELATION}", jenkins_k8s_agents.name
+ )
+ await model.wait_for_idle(
apps=[application.name, jenkins_k8s_agents.name], wait_for_active=True
)
yield application
@@ -288,16 +288,14 @@ async def jenkins_machine_agents_fixture(
@pytest_asyncio.fixture(scope="function", name="machine_agent_related_app")
async def machine_agent_related_app_fixture(
- jenkins_machine_agents: Application,
- application: Application,
+ jenkins_machine_agents: Application, application: Application, model: Model
):
"""The Jenkins-k8s server charm related to Jenkins agent charm through agent relation."""
- model: Model = application.model
machine_model: Model = jenkins_machine_agents.model
await machine_model.wait_for_idle(
apps=[jenkins_machine_agents.name], wait_for_active=True, check_freq=5
)
- await model.relate(
+ await model.integrate(
f"{application.name}:{state.AGENT_RELATION}",
f"localhost:admin/{machine_model.name}.{state.AGENT_RELATION}",
)
@@ -310,13 +308,11 @@ async def machine_agent_related_app_fixture(
@pytest_asyncio.fixture(scope="function", name="machine_deprecated_agent_related_app")
async def machine_deprecated_agent_related_app_fixture(
- jenkins_machine_agents: Application,
- application: Application,
+ jenkins_machine_agents: Application, application: Application, model: Model
):
"""The Jenkins-k8s server charm related to Jenkins agent charm through agent relation."""
- model: Model = application.model
machine_model: Model = jenkins_machine_agents.model
- await model.relate(
+ await model.integrate(
f"{application.name}:{state.DEPRECATED_AGENT_RELATION}",
f"localhost:admin/{machine_model.name}.{state.DEPRECATED_AGENT_RELATION}",
)
@@ -520,11 +516,10 @@ async def jenkins_with_proxy_client_fixture(
@pytest_asyncio.fixture(scope="function", name="app_with_allowed_plugins")
async def app_with_allowed_plugins_fixture(
- application: Application, web_address: str
+ application: Application, web_address: str, model: Model
) -> AsyncGenerator[Application, None]:
"""Jenkins charm with plugins configured."""
await application.set_config({"allowed-plugins": ",".join(ALLOWED_PLUGINS)})
- model: Model = application.model
await model.wait_for_idle(apps=[application.name], wait_for_active=True)
await model.block_until(
lambda: requests.get(web_address, timeout=10).status_code == 403,
@@ -794,66 +789,129 @@ async def traefik_application_fixture(model: Model):
@pytest_asyncio.fixture(scope="module", name="oathkeeper_related")
async def oathkeeper_application_related_fixture(
- ops_test: OpsTest, application: Application, client: Client, ext_idp_service: str
+ application: Application, client: Client, ext_idp_service: str, model: Model
):
"""The application related to Jenkins via auth_proxy v0 relation."""
- oathkeeper = await application.model.deploy("oathkeeper", channel="edge", trust=True)
- # Using a patched local bundle from identity team here as a temporary workaround for
- # https://github.com/canonical/traefik-k8s-operator/issues/322 and
- # https://github.com/juju/python-libjuju/issues/1042
- await ops_test.run(
- "juju", "deploy", "./tests/integration/files/identity-bundle-edge-patched.yaml", "--trust"
- )
- await application.model.applications["kratos-external-idp-integrator"].set_config(
- {
+ oathkeeper = await model.deploy("oathkeeper", channel="edge", trust=True)
+ kratos_app = await model.deploy(
+ "kratos",
+ channel="0.4/edge",
+ # Needed per https://github.com/canonical/oathkeeper-operator/issues/49
+ config={"dev": "True"},
+ trust=True,
+ )
+ kratos_external_idp_integrator_app = await model.deploy(
+ "kratos-external-idp-integrator",
+ channel="latest/edge",
+ config={
"client_id": "client_id",
"client_secret": "client_secret",
"provider": "generic",
"issuer_url": ext_idp_service,
"scope": "profile email",
"provider_id": "Dex",
- }
+ },
)
-
- # See https://github.com/canonical/kratos-operator/issues/182
- await application.model.wait_for_idle(
- status="active",
- apps=[application.name, oathkeeper.name] + IDENTITY_PLATFORM_APPS,
- raise_on_error=False,
- timeout=30 * 60,
- idle_period=5,
+ hydra_app = await model.deploy("hydra", channel="edge", series="jammy", trust=True)
+ postgresql_app = await model.deploy(
+ entity_url="postgresql-k8s",
+ channel="14/stable",
+ series="jammy",
+ trust=True,
+ )
+ traefik_public_app = await model.deploy(
+ "traefik-k8s",
+ application_name="traefik-public",
+ channel="latest/edge",
+ config={
+ "enable_experimental_forward_auth": "True",
+ },
+ trust=True,
+ )
+ traefik_admin_app = await model.deploy(
+ "traefik-k8s",
+ application_name="traefik-admin",
+ channel="latest/edge",
+ trust=True,
+ )
+ ca_app = await model.deploy(
+ "self-signed-certificates",
+ channel="latest/stable",
+ trust=True,
+ )
+ login_ui_app = await model.deploy(
+ "identity-platform-login-ui-operator",
+ channel="0.3/edge",
+ trust=True,
)
- await application.model.add_relation(
- f"{oathkeeper.name}:certificates", "self-signed-certificates"
+ await model.add_relation(f"{hydra_app.name}:pg-database", postgresql_app.name)
+ await model.add_relation(f"{kratos_app.name}:pg-database", postgresql_app.name)
+ await model.add_relation(
+ f"{kratos_app.name}:hydra-endpoint-info", f"{hydra_app.name}:hydra-endpoint-info"
+ )
+ await model.add_relation(f"{application.name}:ingress", traefik_public_app.name)
+ await model.add_relation(
+ f"{hydra_app.name}:admin-ingress", f"{traefik_admin_app.name}:ingress"
+ )
+ await model.add_relation(
+ f"{hydra_app.name}:public-ingress", f"{traefik_public_app.name}:ingress"
+ )
+ await model.add_relation(
+ f"{kratos_app.name}:admin-ingress", f"{traefik_admin_app.name}:ingress"
+ )
+ await model.add_relation(
+ f"{kratos_app.name}:public-ingress", f"{traefik_public_app.name}:ingress"
+ )
+ await model.add_relation(f"{login_ui_app.name}:ingress", traefik_public_app.name)
+ await model.add_relation(
+ f"{login_ui_app.name}:hydra-endpoint-info", f"{hydra_app.name}:hydra-endpoint-info"
)
- await application.model.add_relation(
- "traefik-public:receive-ca-cert", "self-signed-certificates"
+ await model.add_relation(
+ f"{login_ui_app.name}:ui-endpoint-info", f"{hydra_app.name}:ui-endpoint-info"
)
- await application.model.applications["traefik-public"].set_config(
- {"enable_experimental_forward_auth": "True"}
+ await model.add_relation(
+ f"{login_ui_app.name}:ui-endpoint-info", f"{kratos_app.name}:ui-endpoint-info"
)
- await application.model.add_relation(
- f"{oathkeeper.name}", "traefik-public:experimental-forward-auth"
+ await model.add_relation(f"{login_ui_app.name}:kratos-info", f"{kratos_app.name}:kratos-info")
+ await model.add_relation(
+ f"{oathkeeper.name}", f"{traefik_public_app.name}:experimental-forward-auth"
+ )
+ await model.add_relation(f"{oathkeeper.name}:certificates", f"{ca_app.name}:certificates")
+ await model.add_relation(f"{oathkeeper.name}:kratos-info", kratos_app.name)
+ await model.add_relation(f"{oathkeeper.name}:auth-proxy", application.name)
+ await model.add_relation(
+ f"{traefik_admin_app.name}:certificates", f"{ca_app.name}:certificates"
+ )
+ await model.add_relation(
+ f"{traefik_public_app.name}:certificates", f"{ca_app.name}:certificates"
+ )
+ # Wait for idle before running actions to unblock buggy postgresql
+ # "awaiting for primary endpoint to be ready"
+ # Wait for idle before running actions to unblock buggy kratos-external-idp-integrator
+ # "Waiting for Kratos to register provider"
+ await model.wait_for_idle(
+ raise_on_error=False,
+ timeout=30 * 60,
+ idle_period=30,
)
- await application.model.add_relation(f"{oathkeeper.name}:kratos-info", "kratos")
- # Needed per https://github.com/canonical/oathkeeper-operator/issues/49
- await application.model.applications["kratos"].set_config({"dev": "True"})
- await application.model.add_relation(f"{application.name}:ingress", "traefik-public")
- await application.model.add_relation(f"{application.name}:auth-proxy", oathkeeper.name)
- await application.model.wait_for_idle(
+ # try sleeping since the kratos relation does not work well.
+ await asyncio.sleep(10)
+ await model.add_relation(
+ f"{kratos_external_idp_integrator_app.name}:kratos-external-idp",
+ f"{kratos_app.name}:kratos-external-idp",
+ )
+ await model.wait_for_idle(
status="active",
apps=[application.name, oathkeeper.name] + IDENTITY_PLATFORM_APPS,
raise_on_error=False,
timeout=30 * 60,
- idle_period=5,
+ idle_period=30,
)
- get_redirect_uri_action = (
- await application.model.applications["kratos-external-idp-integrator"]
- .units[0]
- .run_action("get-redirect-uri")
+ get_redirect_uri_action = await kratos_external_idp_integrator_app.units[0].run_action(
+ "get-redirect-uri"
)
action_output = await get_redirect_uri_action.wait()
update_redirect_uri(client, action_output.results["redirect-uri"])
diff --git a/tests/integration/constants.py b/tests/integration/constants.py
index 9abe6160..72ae55d0 100644
--- a/tests/integration/constants.py
+++ b/tests/integration/constants.py
@@ -23,3 +23,12 @@
"ssh-agent",
"thinBackup",
]
+DEFAULT_SYSTEM_CONFIGURE_PAYLOAD = {
+ "jenkins-model-MasterBuildConfiguration": {"numExecutors": "0"},
+ "jenkins-model-GlobalComputerRetentionCheckIntervalConfiguration": {
+ "computerRetentionCheckInterval": "60"
+ },
+ "jenkins-model-GlobalQuietPeriodConfiguration": {"quietPeriod": "5"},
+ "jenkins-model-GlobalSCMRetryCountConfiguration": {"scmCheckoutRetryCount": "0"},
+ "com-sonyericsson-rebuild-RebuildDescriptor": {"rememberPasswordEnabled": True},
+}
diff --git a/tests/integration/dex.py b/tests/integration/dex.py
index eaadd414..c7256b76 100644
--- a/tests/integration/dex.py
+++ b/tests/integration/dex.py
@@ -133,6 +133,7 @@ def _apply_dex_manifests(
)
for obj in objs:
+ logger.info("Applying dex manifest: %s", obj)
client.apply(obj, force=True)
diff --git a/tests/integration/files/identity-bundle-edge-patched.yaml b/tests/integration/files/identity-bundle-edge-patched.yaml
deleted file mode 100644
index d1810cc6..00000000
--- a/tests/integration/files/identity-bundle-edge-patched.yaml
+++ /dev/null
@@ -1,76 +0,0 @@
----
-bundle: kubernetes
-name: identity-platform
-website: https://github.com/canonical/iam-bundle
-issues: https://github.com/canonical/iam-bundle/issues
-applications:
- hydra:
- charm: hydra
- revision: 269
- channel: latest/edge
- scale: 1
- series: jammy
- trust: true
- kratos:
- charm: kratos
- revision: 393
- channel: latest/edge
- scale: 1
- series: jammy
- trust: true
- kratos-external-idp-integrator:
- charm: kratos-external-idp-integrator
- channel: latest/edge
- scale: 1
- series: jammy
- identity-platform-login-ui-operator:
- charm: identity-platform-login-ui-operator
- revision: 79
- channel: latest/edge
- scale: 1
- series: jammy
- trust: true
- resources:
- oci-image: 79
- postgresql-k8s:
- charm: postgresql-k8s
- revision: 300
- channel: 14/stable
- series: jammy
- scale: 1
- trust: true
- self-signed-certificates:
- charm: self-signed-certificates
- revision: 52
- channel: latest/edge
- scale: 1
- traefik-admin:
- charm: traefik-k8s
- channel: latest/stable
- series: focal
- scale: 1
- revision: 170
- trust: true
- traefik-public:
- charm: traefik-k8s
- channel: latest/stable
- series: focal
- scale: 1
- revision: 170
- trust: true
-relations:
- - [hydra:pg-database, postgresql-k8s:database]
- - [kratos:pg-database, postgresql-k8s:database]
- - [kratos:hydra-endpoint-info, hydra:hydra-endpoint-info]
- - [kratos-external-idp-integrator:kratos-external-idp, kratos:kratos-external-idp]
- - [hydra:admin-ingress, traefik-admin:ingress]
- - [hydra:public-ingress, traefik-public:ingress]
- - [kratos:admin-ingress, traefik-admin:ingress]
- - [kratos:public-ingress, traefik-public:ingress]
- - [identity-platform-login-ui-operator:ingress, traefik-public:ingress]
- - [identity-platform-login-ui-operator:hydra-endpoint-info, hydra:hydra-endpoint-info]
- - [identity-platform-login-ui-operator:ui-endpoint-info, hydra:ui-endpoint-info]
- - [identity-platform-login-ui-operator:ui-endpoint-info, kratos:ui-endpoint-info]
- - [identity-platform-login-ui-operator:kratos-endpoint-info, kratos:kratos-endpoint-info]
- - [traefik-admin:certificates, self-signed-certificates:certificates]
- - [traefik-public:certificates, self-signed-certificates:certificates]
diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py
index 3a3ffced..c0b2e6b8 100644
--- a/tests/integration/helpers.py
+++ b/tests/integration/helpers.py
@@ -51,14 +51,13 @@ async def install_plugins(
assert res.status_code == 200, "Failed to request plugins install"
# block until the UI does not have "Pending" in download progress column.
- await unit.model.block_until(
+ await wait_for(
lambda: "Pending"
not in str(
client.requester.post_url(f"{web}/manage/pluginManager/updates/body").content,
encoding="utf-8",
),
timeout=60 * 10,
- wait_period=10,
)
# the library will return 503 or other status codes that are not 200, hence restart and
diff --git a/tests/integration/requirements.txt b/tests/integration/requirements.txt
index 01677e96..94903a74 100644
--- a/tests/integration/requirements.txt
+++ b/tests/integration/requirements.txt
@@ -1,5 +1,7 @@
-Jinja2>=3,<4
+Jinja2==3.1.4
lightkube==0.15.1
pytest-playwright==0.4.4
-python-keycloak>=3,<4
+python-keycloak==3.12.0
tenacity==8.4.2
+# Pin httpx, SSL changes breaking auth https://github.com/gtsystem/lightkube/issues/78
+httpx==0.27
diff --git a/tests/integration/test_auth_proxy.py b/tests/integration/test_auth_proxy.py
index 3ab03ee9..8815c323 100644
--- a/tests/integration/test_auth_proxy.py
+++ b/tests/integration/test_auth_proxy.py
@@ -5,12 +5,13 @@
import logging
import re
-from typing import Any, AsyncGenerator, Callable, Coroutine, Match
+from typing import Any, AsyncGenerator, Callable, Coroutine, Match, cast
import pytest
import pytest_asyncio
import requests
from juju.application import Application
+from juju.client._definitions import DetailedStatus, UnitStatus
from juju.model import Model
from playwright.async_api import async_playwright, expect
from playwright.async_api._generated import Browser, BrowserContext, BrowserType, Page
@@ -114,6 +115,21 @@ async def page_fixture(context: BrowserContext) -> AsyncGenerator[Page, None]:
await new_page.close()
+async def get_application_unit_status(model: Model, application: str) -> UnitStatus:
+ """Get the application unit status object.
+
+ Args:
+ model: The Juju model connection object.
+ application: The application unit to get the unit status.
+
+ Returns:
+ The application first unit's status.
+ """
+ status = await model.get_status()
+ unit_status: UnitStatus = status["applications"][application]["units"][f"{application}/0"]
+ return unit_status
+
+
@pytest.mark.abort_on_fail
@pytest.mark.asyncio
@pytest.mark.usefixtures("oathkeeper_related")
@@ -126,16 +142,26 @@ async def test_auth_proxy_integration_returns_not_authorized(
act: send a request Jenkins.
assert: a 401 is returned.
"""
- status = await model.get_status()
- address = status["applications"]["traefik-public"]["public-address"]
- # The certificate is self signed, so verification is disabled.
- response = requests.get( # nosec
- f"https://{address}/{application.model.name}-{application.name}/",
- verify=False,
- timeout=5,
- )
+ unit_status = await get_application_unit_status(model=model, application="traefik-public")
+ workload_message = str(cast(DetailedStatus, unit_status.workload_status).info)
+ # The message is: Serving at
+ address = workload_message.removeprefix("Serving at ")
+
+ def is_auth_401():
+ """Get the status code of application request via ingress.
+
+ Returns:
+ Whether the status code of the request is 401.
+ """
+ response = requests.get( # nosec
+ f"https://{address}/{application.model.name}-{application.name}/",
+ # The certificate is self signed, so verification is disabled.
+ verify=False,
+ timeout=5,
+ )
+ return response.status_code == 401
- assert response.status_code == 401
+ await wait_for(is_auth_401)
@pytest.mark.abort_on_fail
@@ -150,11 +176,15 @@ async def test_auth_proxy_integration_authorized(
) -> None:
"""
arrange: Deploy jenkins, the authentication bundle and DEX.
- act: log into via DEX
+ act: log in via DEX
assert: the browser is redirected to the Jenkins URL with response code 200
"""
- status = await application.model.get_status()
- address = status["applications"]["traefik-public"]["public-address"]
+ unit_status = await get_application_unit_status(
+ model=application.model, application="traefik-public"
+ )
+ workload_message = str(cast(DetailedStatus, unit_status.workload_status).info)
+ # The message is: Serving at
+ address = workload_message.removeprefix("Serving at ")
jenkins_url = f"https://{address}/{application.model.name}-{application.name}/"
expected_url = (
f"https://{address}/{application.model.name}"
diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins_part_1.py
similarity index 58%
rename from tests/integration/test_plugins.py
rename to tests/integration/test_plugins_part_1.py
index 3dc1d2ec..073d390a 100644
--- a/tests/integration/test_plugins.py
+++ b/tests/integration/test_plugins_part_1.py
@@ -3,7 +3,6 @@
"""Integration tests for jenkins-k8s-operator charm."""
-import functools
import json
import logging
import typing
@@ -16,20 +15,20 @@
from juju.application import Application
from pytest_operator.plugin import OpsTest
-from .constants import ALLOWED_PLUGINS, INSTALLED_PLUGINS, REMOVED_PLUGINS
+from .constants import (
+ ALLOWED_PLUGINS,
+ DEFAULT_SYSTEM_CONFIGURE_PAYLOAD,
+ INSTALLED_PLUGINS,
+ REMOVED_PLUGINS,
+)
from .helpers import (
- create_kubernetes_cloud,
- create_secret_file_credentials,
- declarative_pipeline_script,
gen_git_test_job_xml,
gen_test_job_xml,
- gen_test_pipeline_with_custom_script_xml,
get_job_invoked_unit,
install_plugins,
- kubernetes_test_pipeline_script,
wait_for,
)
-from .types_ import KeycloakOIDCMetadata, LDAPSettings, UnitWebClient
+from .types_ import LDAPSettings, UnitWebClient
logger = logging.getLogger(__name__)
@@ -346,11 +345,7 @@ async def test_thinbackup_plugin(ops_test: OpsTest, unit_web_client: UnitWebClie
await install_plugins(unit_web_client, ("thinBackup",))
backup_path = "/srv/jenkins/backup/"
payload = {
- "jenkins-model-MasterBuildConfiguration": {
- "numExecutors": "0",
- },
- "jenkins-model-GlobalQuietPeriodConfiguration": {"quietPeriod": "5"},
- "jenkins-model-GlobalSCMRetryCountConfiguration": {"scmCheckoutRetryCount": "0"},
+ **DEFAULT_SYSTEM_CONFIGURE_PAYLOAD,
"org-jvnet-hudson-plugins-thinbackup-ThinBackupPluginImpl": {
"backupPath": backup_path,
},
@@ -404,243 +399,3 @@ async def test_bzr_plugin(unit_web_client: UnitWebClient):
config_page = str(res.content, "utf-8")
assert "Bazaar" in config_page, f"Bzr configuration option not found. {config_page}"
-
-
-async def test_docker_build_publish_plugin(unit_web_client: UnitWebClient):
- """
- arrange: given a Jenkins charm with docker-build-publish plugin installed.
- act: when a job configuration page is accessed.
- assert: docker-build-publish plugin option exists.
- """
- await install_plugins(unit_web_client, ("docker-build-publish",))
- unit_web_client.client.create_job("docker_plugin_test", gen_test_job_xml("k8s"))
- res = unit_web_client.client.requester.get_url(
- f"{unit_web_client.web}/job/docker_plugin_test/configure"
- )
- config_page = str(res.content, "utf-8")
- assert (
- "Docker Build and Publish" in config_page
- ), f"docker-build-publish configuration option not found. {config_page}"
-
-
-async def test_reverse_proxy_plugin(unit_web_client: UnitWebClient):
- """
- arrange: given a Jenkins charm with reverse-proxy-auth-plugin plugin installed.
- act: when the security configuration is accessed.
- assert: reverse-proxy-auth-plugin plugin option exists.
- """
- await install_plugins(unit_web_client, ("reverse-proxy-auth-plugin",))
-
- res = unit_web_client.client.requester.get_url(
- f"{unit_web_client.web}/manage/configureSecurity"
- )
- config_page = str(res.content, "utf-8")
-
- assert (
- "HTTP Header by reverse proxy" in config_page
- ), f"reverse-proxy-auth-plugin configuration option not found. {config_page}"
-
-
-async def test_dependency_check_plugin(unit_web_client: UnitWebClient):
- """
- arrange: given a Jenkins charm with dependency-check-jenkins-plugin plugin installed.
- act: when a job configuration page is accessed.
- assert: dependency-check-jenkins-plugin plugin option exists.
- """
- await install_plugins(unit_web_client, ("dependency-check-jenkins-plugin",))
- unit_web_client.client.create_job("deps_plugin_test", gen_test_job_xml("k8s"))
- res = unit_web_client.client.requester.get_url(
- f"{unit_web_client.web}/job/deps_plugin_test/configure"
- )
- job_page = str(res.content, "utf-8")
- assert (
- "Invoke Dependency-Check" in job_page
- ), f"Dependency check job configuration option not found. {job_page}"
- res = unit_web_client.client.requester.get_url(f"{unit_web_client.web}/manage/configureTools/")
- tools_page = str(res.content, "utf-8")
- assert (
- "Dependency-Check installations" in tools_page
- ), f"Dependency check tool configuration option not found. {tools_page}"
-
-
-async def test_groovy_libs_plugin(unit_web_client: UnitWebClient):
- """
- arrange: given a Jenkins charm with pipeline-groovy-lib plugin installed.
- act: when a job configuration page is accessed.
- assert: pipeline-groovy-lib plugin option exists.
- """
- await install_plugins(unit_web_client, ("pipeline-groovy-lib",))
- res = unit_web_client.client.requester.get_url(f"{unit_web_client.web}/manage/configure")
-
- config_page = str(res.content, "utf-8")
- # The string is now "Global Trusted Pipeline Libraries" and
- # "Global Untrusted Pipeline Libraries" for v727.ve832a_9244dfa_
- assert (
- "Pipeline Libraries" in config_page
- ), f"Groovy libs configuration option not found. {config_page}"
-
-
-@pytest.mark.usefixtures("k8s_agent_related_app")
-async def test_rebuilder_plugin(unit_web_client: UnitWebClient):
- """
- arrange: given a Jenkins charm with rebuilder plugin installed.
- act: when a job is built and a rebuild is triggered.
- assert: last job is rebuilt.
- """
- await install_plugins(unit_web_client, ("rebuild",))
-
- job_name = "rebuild_test"
- job = unit_web_client.client.create_job(job_name, gen_test_job_xml("k8s"))
- job.invoke().block_until_complete()
-
- unit_web_client.client.requester.post_url(
- f"{unit_web_client.web}/job/{job_name}/lastCompletedBuild/rebuild/"
- )
- job.get_last_build().block_until_complete()
-
- assert job.get_last_buildnumber() == 2, "Rebuild not triggered."
-
-
-async def test_openid_plugin(unit_web_client: UnitWebClient):
- """
- arrange: given a Jenkins charm with openid plugin installed.
- act: when an openid endpoint is validated using the plugin.
- assert: the response returns a 200 status code.
- """
- await install_plugins(unit_web_client, ("openid",))
-
- res = unit_web_client.client.requester.post_url(
- f"{unit_web_client.web}/manage/descriptorByName/hudson.plugins.openid."
- "OpenIdSsoSecurityRealm/validate",
- data={"endpoint": "https://login.ubuntu.com/+openid"},
- )
-
- assert res.status_code == 200, "Failed to validate openid endpoint using the plugin."
-
-
-async def test_openid_connect_plugin(
- unit_web_client: UnitWebClient,
- keycloak_oidc_meta: KeycloakOIDCMetadata,
- keycloak_ip: str,
-):
- """
- arrange: given a Jenkins charm with oic-auth plugin installed and a Keycloak oidc server.
- act:
- 1. when jenkins security realm is configured with oidc server and login page is requested.
- 2. when jenkins security realm is reset and login page is requested.
- assert:
- 1. a redirection to Keycloak SSO is made.
- 2. native Jenkins login ui is loaded.
- """
- await install_plugins(unit_web_client, ("oic-auth",))
-
- # 1. when jenkins security realm is configured with oidc server and login page is requested.
- payload: dict = {
- "securityRealm": {
- "clientId": keycloak_oidc_meta.client_id,
- "clientSecret": keycloak_oidc_meta.client_secret,
- "automanualconfigure": "auto",
- "serverConfiguration": {
- "wellKnownOpenIDConfigurationUrl": keycloak_oidc_meta.well_known_endpoint,
- "scopesOverride": "",
- "stapler-class": "org.jenkinsci.plugins.oic.OicServerWellKnownConfiguration",
- "$class": "org.jenkinsci.plugins.oic.OicServerWellKnownConfiguration",
- },
- "userNameField": "sub",
- "stapler-class": "org.jenkinsci.plugins.oic.OicSecurityRealm",
- "$class": "org.jenkinsci.plugins.oic.OicSecurityRealm",
- },
- "slaveAgentPort": {"type": "fixed", "value": "50000"},
- }
- res = unit_web_client.client.requester.post_url(
- f"{unit_web_client.web}/manage/configureSecurity/configure",
- data=[
- (
- "json",
- json.dumps(payload),
- ),
- ],
- )
- res = requests.get(f"{unit_web_client.web}/securityRealm/commenceLogin?from=%2F", timeout=30)
- assert res.history[0].status_code == 302, "Jenkins login not redirected."
- assert keycloak_ip in res.history[0].headers["location"], "Login not redirected to keycloak."
-
- # 2. when jenkins security realm is reset and login page is requested.
- payload = {
- "securityRealm": {
- "allowsSignup": False,
- "stapler-class": "hudson.security.HudsonPrivateSecurityRealm",
- "$class": "hudson.security.HudsonPrivateSecurityRealm",
- },
- "authorizationStrategy": {
- "allowAnonymousRead": False,
- "stapler-class": "hudson.security.FullControlOnceLoggedInAuthorizationStrategy",
- "$class": "hudson.security.FullControlOnceLoggedInAuthorizationStrategy",
- },
- "slaveAgentPort": {"type": "fixed", "value": "50000"},
- }
- res = unit_web_client.client.requester.post_url(
- f"{unit_web_client.web}/manage/configureSecurity/configure",
- data=[
- (
- "json",
- json.dumps(payload),
- )
- ],
- )
- res = requests.get(f"{unit_web_client.web}/securityRealm/commenceLogin?from=%2F", timeout=30)
- assert res.status_code == 404, "Security realm login not reset."
- res = requests.get(f"{unit_web_client.web}/login?from=%2F", timeout=30)
- assert res.status_code == 200, "Failed to load Jenkins native login UI."
-
-
-async def test_kubernetes_plugin(unit_web_client: UnitWebClient, kube_config: str):
- """
- arrange: given a Jenkins charm with kubernetes plugin installed and credentials from microk8s.
- act: Run a job using an agent provided by the kubernetes plugin.
- assert: Job succeeds.
- """
- # Use plain credentials to be able to create secret-file/secret-text credentials
- await install_plugins(unit_web_client, ("kubernetes", "plain-credentials"))
- credentials_id = await wait_for(
- functools.partial(create_secret_file_credentials, unit_web_client, kube_config)
- )
- assert credentials_id, "Failed to create credentials id"
- kubernetes_cloud_name = await wait_for(
- functools.partial(create_kubernetes_cloud, unit_web_client, credentials_id)
- )
- assert kubernetes_cloud_name, "Failed to create kubernetes cloud"
- job = unit_web_client.client.create_job(
- "kubernetes_plugin_test",
- gen_test_pipeline_with_custom_script_xml(kubernetes_test_pipeline_script()),
- )
-
- queue_item = job.invoke()
- queue_item.block_until_complete()
-
- build: jenkinsapi.build.Build = queue_item.get_build()
- log_stream = build.stream_logs()
- logs = "".join(log_stream)
- logger.debug("build logs: %s", logs)
- assert build.get_status() == "SUCCESS"
-
-
-@pytest.mark.usefixtures("k8s_agent_related_app")
-async def test_pipeline_model_definition_plugin(unit_web_client: UnitWebClient):
- """
- arrange: given a Jenkins charm with declarative pipeline plugin installed.
- act: Run a job using a declarative pipeline script.
- assert: Job succeeds.
- """
- await install_plugins(unit_web_client, ("pipeline-model-definition",))
-
- job = unit_web_client.client.create_job(
- "pipeline_model_definition_plugin_test",
- gen_test_pipeline_with_custom_script_xml(declarative_pipeline_script()),
- )
-
- queue_item = job.invoke()
- queue_item.block_until_complete()
-
- build: jenkinsapi.build.Build = queue_item.get_build()
- assert build.get_status() == "SUCCESS"
diff --git a/tests/integration/test_plugins_part_2.py b/tests/integration/test_plugins_part_2.py
new file mode 100644
index 00000000..91efaa0f
--- /dev/null
+++ b/tests/integration/test_plugins_part_2.py
@@ -0,0 +1,266 @@
+# Copyright 2024 Canonical Ltd.
+# See LICENSE file for licensing details.
+
+"""Integration tests for jenkins-k8s-operator charm."""
+
+import functools
+import json
+import logging
+
+import jenkinsapi.plugin
+import pytest
+import requests
+
+from .helpers import (
+ create_kubernetes_cloud,
+ create_secret_file_credentials,
+ declarative_pipeline_script,
+ gen_test_job_xml,
+ gen_test_pipeline_with_custom_script_xml,
+ install_plugins,
+ kubernetes_test_pipeline_script,
+ wait_for,
+)
+from .types_ import KeycloakOIDCMetadata, UnitWebClient
+
+logger = logging.getLogger(__name__)
+
+
+async def test_docker_build_publish_plugin(unit_web_client: UnitWebClient):
+ """
+ arrange: given a Jenkins charm with docker-build-publish plugin installed.
+ act: when a job configuration page is accessed.
+ assert: docker-build-publish plugin option exists.
+ """
+ await install_plugins(unit_web_client, ("docker-build-publish",))
+ unit_web_client.client.create_job("docker_plugin_test", gen_test_job_xml("k8s"))
+ res = unit_web_client.client.requester.get_url(
+ f"{unit_web_client.web}/job/docker_plugin_test/configure"
+ )
+ config_page = str(res.content, "utf-8")
+ assert (
+ "Docker Build and Publish" in config_page
+ ), f"docker-build-publish configuration option not found. {config_page}"
+
+
+async def test_reverse_proxy_plugin(unit_web_client: UnitWebClient):
+ """
+ arrange: given a Jenkins charm with reverse-proxy-auth-plugin plugin installed.
+ act: when the security configuration is accessed.
+ assert: reverse-proxy-auth-plugin plugin option exists.
+ """
+ await install_plugins(unit_web_client, ("reverse-proxy-auth-plugin",))
+
+ res = unit_web_client.client.requester.get_url(
+ f"{unit_web_client.web}/manage/configureSecurity"
+ )
+ config_page = str(res.content, "utf-8")
+
+ assert (
+ "HTTP Header by reverse proxy" in config_page
+ ), f"reverse-proxy-auth-plugin configuration option not found. {config_page}"
+
+
+async def test_dependency_check_plugin(unit_web_client: UnitWebClient):
+ """
+ arrange: given a Jenkins charm with dependency-check-jenkins-plugin plugin installed.
+ act: when a job configuration page is accessed.
+ assert: dependency-check-jenkins-plugin plugin option exists.
+ """
+ await install_plugins(unit_web_client, ("dependency-check-jenkins-plugin",))
+ unit_web_client.client.create_job("deps_plugin_test", gen_test_job_xml("k8s"))
+ res = unit_web_client.client.requester.get_url(
+ f"{unit_web_client.web}/job/deps_plugin_test/configure"
+ )
+ job_page = str(res.content, "utf-8")
+ assert (
+ "Invoke Dependency-Check" in job_page
+ ), f"Dependency check job configuration option not found. {job_page}"
+ res = unit_web_client.client.requester.get_url(f"{unit_web_client.web}/manage/configureTools/")
+ tools_page = str(res.content, "utf-8")
+ assert (
+ "Dependency-Check installations" in tools_page
+ ), f"Dependency check tool configuration option not found. {tools_page}"
+
+
+async def test_groovy_libs_plugin(unit_web_client: UnitWebClient):
+ """
+ arrange: given a Jenkins charm with pipeline-groovy-lib plugin installed.
+ act: when a job configuration page is accessed.
+ assert: pipeline-groovy-lib plugin option exists.
+ """
+ await install_plugins(unit_web_client, ("pipeline-groovy-lib",))
+ res = unit_web_client.client.requester.get_url(f"{unit_web_client.web}/manage/configure")
+
+ config_page = str(res.content, "utf-8")
+ # The string is now "Global Trusted Pipeline Libraries" and
+ # "Global Untrusted Pipeline Libraries" for v727.ve832a_9244dfa_
+ assert (
+ "Pipeline Libraries" in config_page
+ ), f"Groovy libs configuration option not found. {config_page}"
+
+
+@pytest.mark.usefixtures("k8s_agent_related_app")
+async def test_rebuilder_plugin(unit_web_client: UnitWebClient):
+ """
+ arrange: given a Jenkins charm with rebuilder plugin installed.
+ act: when a job is built and a rebuild is triggered.
+ assert: last job is rebuilt.
+ """
+ await install_plugins(unit_web_client, ("rebuild",))
+
+ job_name = "rebuild_test"
+ job = unit_web_client.client.create_job(job_name, gen_test_job_xml("k8s"))
+ job.invoke().block_until_complete()
+
+ unit_web_client.client.requester.post_url(
+ f"{unit_web_client.web}/job/{job_name}/lastCompletedBuild/rebuild/"
+ )
+ job.get_last_build().block_until_complete()
+
+ assert job.get_last_buildnumber() == 2, "Rebuild not triggered."
+
+
+async def test_openid_plugin(unit_web_client: UnitWebClient):
+ """
+ arrange: given a Jenkins charm with openid plugin installed.
+ act: when an openid endpoint is validated using the plugin.
+ assert: the response returns a 200 status code.
+ """
+ await install_plugins(unit_web_client, ("openid",))
+
+ res = unit_web_client.client.requester.post_url(
+ f"{unit_web_client.web}/manage/descriptorByName/hudson.plugins.openid."
+ "OpenIdSsoSecurityRealm/validate",
+ data={"endpoint": "https://login.ubuntu.com/+openid"},
+ )
+
+ assert res.status_code == 200, "Failed to validate openid endpoint using the plugin."
+
+
+async def test_openid_connect_plugin(
+ unit_web_client: UnitWebClient,
+ keycloak_oidc_meta: KeycloakOIDCMetadata,
+ keycloak_ip: str,
+):
+ """
+ arrange: given a Jenkins charm with oic-auth plugin installed and a Keycloak oidc server.
+ act:
+ 1. when jenkins security realm is configured with oidc server and login page is requested.
+ 2. when jenkins security realm is reset and login page is requested.
+ assert:
+ 1. a redirection to Keycloak SSO is made.
+ 2. native Jenkins login ui is loaded.
+ """
+ await install_plugins(unit_web_client, ("oic-auth",))
+
+ # 1. when jenkins security realm is configured with oidc server and login page is requested.
+ payload: dict = {
+ "securityRealm": {
+ "clientId": keycloak_oidc_meta.client_id,
+ "clientSecret": keycloak_oidc_meta.client_secret,
+ "automanualconfigure": "auto",
+ "serverConfiguration": {
+ "wellKnownOpenIDConfigurationUrl": keycloak_oidc_meta.well_known_endpoint,
+ "scopesOverride": "",
+ "stapler-class": "org.jenkinsci.plugins.oic.OicServerWellKnownConfiguration",
+ "$class": "org.jenkinsci.plugins.oic.OicServerWellKnownConfiguration",
+ },
+ "userNameField": "sub",
+ "stapler-class": "org.jenkinsci.plugins.oic.OicSecurityRealm",
+ "$class": "org.jenkinsci.plugins.oic.OicSecurityRealm",
+ },
+ "slaveAgentPort": {"type": "fixed", "value": "50000"},
+ }
+ res = unit_web_client.client.requester.post_url(
+ f"{unit_web_client.web}/manage/configureSecurity/configure",
+ data=[
+ (
+ "json",
+ json.dumps(payload),
+ ),
+ ],
+ )
+ res = requests.get(f"{unit_web_client.web}/securityRealm/commenceLogin?from=%2F", timeout=30)
+ assert res.history[0].status_code == 302, "Jenkins login not redirected."
+ assert keycloak_ip in res.history[0].headers["location"], "Login not redirected to keycloak."
+
+ # 2. when jenkins security realm is reset and login page is requested.
+ payload = {
+ "securityRealm": {
+ "allowsSignup": False,
+ "stapler-class": "hudson.security.HudsonPrivateSecurityRealm",
+ "$class": "hudson.security.HudsonPrivateSecurityRealm",
+ },
+ "authorizationStrategy": {
+ "allowAnonymousRead": False,
+ "stapler-class": "hudson.security.FullControlOnceLoggedInAuthorizationStrategy",
+ "$class": "hudson.security.FullControlOnceLoggedInAuthorizationStrategy",
+ },
+ "slaveAgentPort": {"type": "fixed", "value": "50000"},
+ }
+ res = unit_web_client.client.requester.post_url(
+ f"{unit_web_client.web}/manage/configureSecurity/configure",
+ data=[
+ (
+ "json",
+ json.dumps(payload),
+ )
+ ],
+ )
+ res = requests.get(f"{unit_web_client.web}/securityRealm/commenceLogin?from=%2F", timeout=30)
+ assert res.status_code == 404, "Security realm login not reset."
+ res = requests.get(f"{unit_web_client.web}/login?from=%2F", timeout=30)
+ assert res.status_code == 200, "Failed to load Jenkins native login UI."
+
+
+async def test_kubernetes_plugin(unit_web_client: UnitWebClient, kube_config: str):
+ """
+ arrange: given a Jenkins charm with kubernetes plugin installed and credentials from microk8s.
+ act: Run a job using an agent provided by the kubernetes plugin.
+ assert: Job succeeds.
+ """
+ # Use plain credentials to be able to create secret-file/secret-text credentials
+ await install_plugins(unit_web_client, ("kubernetes", "plain-credentials"))
+ credentials_id = await wait_for(
+ functools.partial(create_secret_file_credentials, unit_web_client, kube_config)
+ )
+ assert credentials_id, "Failed to create credentials id"
+ kubernetes_cloud_name = await wait_for(
+ functools.partial(create_kubernetes_cloud, unit_web_client, credentials_id)
+ )
+ assert kubernetes_cloud_name, "Failed to create kubernetes cloud"
+ job = unit_web_client.client.create_job(
+ "kubernetes_plugin_test",
+ gen_test_pipeline_with_custom_script_xml(kubernetes_test_pipeline_script()),
+ )
+
+ queue_item = job.invoke()
+ queue_item.block_until_complete()
+
+ build: jenkinsapi.build.Build = queue_item.get_build()
+ log_stream = build.stream_logs()
+ logs = "".join(log_stream)
+ logger.debug("build logs: %s", logs)
+ assert build.get_status() == "SUCCESS"
+
+
+@pytest.mark.usefixtures("k8s_agent_related_app")
+async def test_pipeline_model_definition_plugin(unit_web_client: UnitWebClient):
+ """
+ arrange: given a Jenkins charm with declarative pipeline plugin installed.
+ act: Run a job using a declarative pipeline script.
+ assert: Job succeeds.
+ """
+ await install_plugins(unit_web_client, ("pipeline-model-definition",))
+
+ job = unit_web_client.client.create_job(
+ "pipeline_model_definition_plugin_test",
+ gen_test_pipeline_with_custom_script_xml(declarative_pipeline_script()),
+ )
+
+ queue_item = job.invoke()
+ queue_item.block_until_complete()
+
+ build: jenkinsapi.build.Build = queue_item.get_build()
+ assert build.get_status() == "SUCCESS"
diff --git a/tests/integration/test_upgrade.py b/tests/integration/test_upgrade.py
index 7759202f..7cbae498 100644
--- a/tests/integration/test_upgrade.py
+++ b/tests/integration/test_upgrade.py
@@ -5,8 +5,8 @@
import logging
+import pathlib
-import ops
import pytest
import pytest_asyncio
import requests
@@ -46,7 +46,7 @@ async def jenkins_upgrade_depl(ops_test: OpsTest, model: Model):
@pytest.mark.usefixtures("jenkins_upgrade_depl")
async def test_jenkins_upgrade_check_job(
- ops_test: OpsTest, jenkins_image: str, model: Model, charm: ops.CharmBase
+ ops_test: OpsTest, jenkins_image: str, model: Model, charm: str | pathlib.Path
):
"""
arrange: given charm has been built, deployed and a job has been defined.
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index a5eb4d24..a6d9f2d4 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -215,7 +215,7 @@ def cmd_handler(argv: list[str]) -> tuple[int, str, str]:
case _ if [
"java",
"-jar",
- "jenkins-plugin-manager-2.12.13.jar",
+ f"jenkins-plugin-manager-{jenkins.JENKINS_PLUGIN_MANAGER_VERSION}.jar",
"-w",
"jenkins.war",
"-d",
@@ -237,7 +237,7 @@ def cmd_handler(argv: list[str]) -> tuple[int, str, str]:
f"-Dhttps.proxyPassword={proxy_config.https_proxy.password}",
f'-Dhttp.nonProxyHosts="{no_proxy_hosts}"',
"-jar",
- "jenkins-plugin-manager-2.12.13.jar",
+ f"jenkins-plugin-manager-{jenkins.JENKINS_PLUGIN_MANAGER_VERSION}.jar",
"-w",
"jenkins.war",
"-d",
diff --git a/tox.ini b/tox.ini
index 7e218e39..5b5075e3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -24,10 +24,13 @@ passenv =
description = Build the rock and pack the charm
allowlist_externals=sh
commands =
+ ; build rock and push to microk8s registry
sh -c "cd jenkins_rock && rockcraft pack && \
- sudo skopeo --insecure-policy copy oci-archive:jenkins_1.0_amd64.rock \
- docker-daemon:localhost:32000/jenkins:test"
- sh -c "docker push localhost:32000/jenkins:test"
+ sudo rockcraft.skopeo \
+ --insecure-policy copy \
+ --dest-tls-verify=false \
+ oci-archive:jenkins_1.0_amd64.rock \
+ docker://localhost:32000/jenkins:test"
sh -c "charmcraft pack"
[testenv:fmt]
@@ -127,10 +130,10 @@ commands =
playwright install --with-deps firefox
pytest --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs}
# uncomment the following lines to use with output of tox -e build.
- # --jenkins-image=localhost:32000/jenkins:test \
- # --charm-file=./jenkins-k8s_ubuntu-22.04-amd64.charm \
- # --kube-config=~/.kube/config \
- # --keep-models
+ ; --jenkins-image=localhost:32000/jenkins:test \
+ ; --charm-file=./jenkins-k8s_ubuntu-22.04-amd64.charm \
+ ; --kube-config=~/.kube/config \
+ ; --keep-models
[testenv:static]
description = Run static analysis tests