Skip to content

feat: Init Agent Intergation #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
*.tar.gz
*.rar

.idea/


# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*

target/
work/
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}
39 changes: 23 additions & 16 deletions example.Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import groovy.json.JsonOutput

pipeline {
agent any

parameters {
string(name: 'JOB_ID', defaultValue: '', description: 'Ctrlplane Job ID')
string(name: 'API_URL', defaultValue: 'https://api.example.com', description: 'API Base URL (optional)')
string(name: 'JOB_ID', defaultValue: '', description: 'Ctrlplane Job ID passed by the plugin')
}

stages {
stage('Deploy') {
stage('Fetch Ctrlplane Job Details') {
steps {
script {
if (!params.JOB_ID) {
error 'JOB_ID parameter is required'
}

def ctrlplane = load 'src/utils/CtrlplaneClient.groovy'
def job = ctrlplane.getJob(
params.JOB_ID,
params.API_URL,
// params.API_KEY
)

if (!job) {
error "Failed to fetch data for job ${params.JOB_ID}"
}

echo "Job status: ${job.id}"
echo "Fetching details for Job ID: ${params.JOB_ID}"

def jobDetails = ctrlplaneGetJob jobId: params.JOB_ID

echo "-----------------------------------------"
echo "Successfully fetched job details:"
echo JsonOutput.prettyPrint(JsonOutput.toJson(jobDetails))
echo "-----------------------------------------"

// Example: Access specific fields from the returned map
// if(jobDetails.variables) {
// echo "Specific Variable: ${jobDetails.variables.your_variable_name}"
// }
// if(jobDetails.metadata) {
// echo "Metadata Value: ${jobDetails.metadata.your_metadata_key}"
// }
// if(jobDetails.job_config) {
// echo "Job Config: ${jobDetails.job_config.jobUrl}"
// }
}
}
}
Expand Down
41 changes: 30 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.85</version>
<version>5.9</version>
<relativePath />
</parent>

Expand All @@ -14,7 +14,7 @@
<version>${revision}${changelist}</version>
<packaging>hpi</packaging>

<name>TODO Plugin</name>
<name>Ctrlplane Plugin</name>
<url>https://github.com/jenkinsci/${project.artifactId}-plugin</url>
<licenses>
<license>
Expand All @@ -32,32 +32,51 @@
<properties>
<revision>1.0</revision>
<changelist>-SNAPSHOT</changelist>

<!-- https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/ -->
<jenkins.version>2.440.3</jenkins.version>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>

<jenkins.baseline>2.492</jenkins.baseline>
<jenkins.version>${jenkins.baseline}.3</jenkins.version>
<!-- <gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo> -->
<gitHubRepo>ctrlplanedev/jenkins-agent-plugin</gitHubRepo>
<spotless.check.skip>false</spotless.check.skip>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<!-- Pick up common dependencies for the selected LTS line: https://github.com/jenkinsci/bom#usage -->
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.440.x</artifactId>
<version>3193.v330d8248d39e</version>
<artifactId>bom-${jenkins.baseline}.x</artifactId>
<version>4051.v78dce3ce8b_d6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>structs</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<!-- <dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<scope>test</scope>
Expand All @@ -76,7 +95,7 @@
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<scope>test</scope>
</dependency>
</dependency> -->
</dependencies>

<repositories>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package io.jenkins.plugins.ctrlplane;

import hudson.Extension;
import hudson.ExtensionList;
import hudson.util.FormValidation;
import jenkins.model.GlobalConfiguration;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

/**
* Global configuration for the Ctrlplane Agent plugin.
*/
@Extension
public class CtrlplaneGlobalConfiguration extends GlobalConfiguration {

/**
* Default API URL
*/
public static final String DEFAULT_API_URL = "https://app.ctrlplane.dev";

public static final String DEFAULT_AGENT_ID = "jenkins-agent";
public static final int DEFAULT_POLLING_INTERVAL_SECONDS = 60;

/** @return the singleton instance */
public static CtrlplaneGlobalConfiguration get() {
return ExtensionList.lookupSingleton(CtrlplaneGlobalConfiguration.class);
}

private String apiUrl;
private String apiKey;
private String agentId;
private String agentWorkspaceId;
private int pollingIntervalSeconds;
Comment on lines +28 to +32
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Store apiKey more securely if possible.
Jenkins offers integration with the Credentials Plugin for sensitive data. Especially for production, consider referencing a credentials ID instead of storing raw secrets in global configuration.

- private String apiKey;
+ private String credentialsId; // Then retrieve the token from Jenkins Credentials Manager
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private String apiUrl;
private String apiKey;
private String agentId;
private String agentWorkspaceId;
private int pollingIntervalSeconds;
private String apiUrl;
private String credentialsId; // Then retrieve the token from Jenkins Credentials Manager
private String agentId;
private String agentWorkspaceId;
private int pollingIntervalSeconds;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid, but we need to integrate with the jenkins credentials plugin.
https://plugins.jenkins.io/credentials/

If you have ideas of how to do that @coderabbitai would love to take them into consideration. Or maybe you can make a followup issue and pr to address that issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No description provided.


public CtrlplaneGlobalConfiguration() {
// When Jenkins is restarted, load any saved configuration from disk.
load();
// Set defaults only if loaded values are null/blank/zero
if (StringUtils.isBlank(apiUrl)) {
apiUrl = DEFAULT_API_URL;
}
if (pollingIntervalSeconds <= 0) { // Set default interval if not loaded or invalid
pollingIntervalSeconds = DEFAULT_POLLING_INTERVAL_SECONDS;
}
}

/** @return the currently configured API URL, or default if not set */
public String getApiUrl() {
return StringUtils.isBlank(apiUrl) ? DEFAULT_API_URL : apiUrl;
}

/**
* Sets the API URL
* @param apiUrl the new API URL
*/
@DataBoundSetter
public void setApiUrl(String apiUrl) {
this.apiUrl = apiUrl;
save();
}

/** @return the currently configured API key */
public String getApiKey() {
return apiKey;
}

/**
* Sets the API key
* @param apiKey the new API key
*/
@DataBoundSetter
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
save();
}

/** @return the currently configured agent ID */
public String getAgentId() {
return StringUtils.isBlank(agentId) ? DEFAULT_AGENT_ID : agentId;
}

/**
* Sets the agent ID
* @param agentId the new agent ID
*/
@DataBoundSetter
public void setAgentId(String agentId) {
this.agentId = agentId;
save();
}

/** @return the currently configured agent workspace ID */
public String getAgentWorkspaceId() {
return agentWorkspaceId;
}

/**
* Sets the agent workspace ID
* @param agentWorkspaceId the new agent workspace ID
*/
@DataBoundSetter
public void setAgentWorkspaceId(String agentWorkspaceId) {
this.agentWorkspaceId = agentWorkspaceId;
save();
}

/** @return the currently configured polling interval in seconds */
public int getPollingIntervalSeconds() {
// Return default if current value is invalid
return pollingIntervalSeconds > 0 ? pollingIntervalSeconds : DEFAULT_POLLING_INTERVAL_SECONDS;
}

/**
* Sets the polling interval.
* @param pollingIntervalSeconds The new interval in seconds.
*/
@DataBoundSetter
public void setPollingIntervalSeconds(int pollingIntervalSeconds) {
// Ensure a minimum value (e.g., 10 seconds) to prevent overly frequent polling
this.pollingIntervalSeconds = Math.max(10, pollingIntervalSeconds);
save();
}

public FormValidation doCheckApiUrl(@QueryParameter String value) {
if (StringUtils.isEmpty(value)) {
return FormValidation.warning("API URL is recommended. Defaults to " + DEFAULT_API_URL);
}
return FormValidation.ok();
}

public FormValidation doCheckApiKey(@QueryParameter String value) {
if (StringUtils.isEmpty(value)) {
return FormValidation.warning("API Key is required for the agent to poll for jobs.");
}
return FormValidation.ok();
}

public FormValidation doCheckAgentId(@QueryParameter String value) {
if (StringUtils.isEmpty(value)) {
return FormValidation.warning("Agent ID is required for the agent to identify itself.");
}
return FormValidation.ok();
}

public FormValidation doCheckAgentWorkspaceId(@QueryParameter String value) {
if (StringUtils.isEmpty(value)) {
return FormValidation.warning("Agent Workspace ID is required for the agent to identify itself.");
}
return FormValidation.ok();
}

public FormValidation doCheckPollingIntervalSeconds(@QueryParameter String value) {
try {
if (StringUtils.isEmpty(value)) {
return FormValidation.ok("Using default interval: " + DEFAULT_POLLING_INTERVAL_SECONDS + " seconds.");
}
int interval = Integer.parseInt(value);
if (interval < 10) {
return FormValidation.error("Polling interval must be at least 10 seconds.");
}
return FormValidation.ok();
} catch (NumberFormatException e) {
return FormValidation.error("Polling interval must be a valid integer.");
}
}
}
Loading