Skip to content
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

Support for copying file from Jenkins host to agents #27

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 25 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,28 @@ A good utility to build yours IOS apps, this plugin create MacOs agents for your
It can stock your Keychains file on Jenkins and send it to the MacOs Nodes.

## Table of Contents
- [Features](#features)
- [Requirements](#requirements)
- [Jenkins](#jenkins)
- [MacOs](#macos)
- [Enable SSH for all users](#enable-ssh-for-all-users)
- [SSH configuration](#ssh-configuration)
- [Configure a Jenkins User](#configure-a-jenkins-user)
- [Plugin configuration](#plugin-configuration)
- [Global Configuration](#global-configuration)
- [Keychain Managment](#keychain-managment)
- [Environment variables](#environment-variables)
- [Pre-launch commands](#pre-launch-commands)
- [Web Socket](#web-socket)
- [User Management Tool](#user-management-tool)
- [Logs configuration](#logs-configuration)
- [Execution](#execution)
- [Troubleshooting](#troubleshooting)
- [Team](#team)
- [Contact](#contact)
- [Mac Plugin](#mac-plugin)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Requirements](#requirements)
- [Jenkins](#jenkins)
- [MacOS](#macos)
- [Enable SSH for all users](#enable-ssh-for-all-users)
- [SSH configuration](#ssh-configuration)
- [Configure a Jenkins User](#configure-a-jenkins-user)
- [Plugin configuration](#plugin-configuration)
- [Global Configuration](#global-configuration)
- [Keychain Managment](#keychain-managment)
- [Environment variables](#environment-variables)
- [Host files](#host-files)
- [Pre-launch commands](#pre-launch-commands)
- [Web Socket](#web-socket)
- [User Management Tool](#user-management-tool)
- [Logs configuration](#logs-configuration)
- [Execution](#execution)
- [Troubleshooting](#troubleshooting)
- [Team](#team)
- [Contact](#contact)

## Features

Expand Down Expand Up @@ -137,6 +140,9 @@ Since v1.1.0, you can set environment variables on Mac host. Theses variables wi

<img src="https://zupimages.net/up/19/50/i14g.png" width="650"/>

### Host files
You can upload files (defined as Jenkins' credentials file type) to a path relative to the user's home folder

### Pre-launch commands
Since v1.3.0, you can set commands passed to the user before the agent starts.
The field is a multi-line string, and each line match to a command execution.
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/fr/edf/jenkins/plugins/mac/MacHost.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ class MacHost implements Describable<MacHost> {
MacHostKeyVerifier macHostKeyVerifier
transient Set<LabelAtom> labelSet
String userManagementTool
List<MacHostFile> hostFiles = new ArrayList()

@DataBoundConstructor
MacHost(String host, String credentialsId, Integer port, Integer maxUsers, Integer connectionTimeout, Integer readTimeout, Integer agentConnectionTimeout,
Boolean disabled, Integer maxTries, String labelString, Boolean uploadKeychain, String fileCredentialsId, List<MacEnvVar> envVars, String key,
String preLaunchCommands, String userManagementTool, String agentJvmParameters) {
String preLaunchCommands, String userManagementTool, String agentJvmParameters, List<MacHostFile> hostFiles) {
this.host = host
this.credentialsId = credentialsId
this.port = port
Expand All @@ -70,6 +71,7 @@ class MacHost implements Describable<MacHost> {
this.preLaunchCommandsList = buildPreLaunchCommands(preLaunchCommands)
this.userManagementTool = userManagementTool
this.agentJvmParameters = agentJvmParameters
this.hostFiles = hostFiles
labelSet = Label.parse(StringUtils.defaultIfEmpty(labelString, ""))
}

Expand Down Expand Up @@ -272,7 +274,7 @@ class MacHost implements Describable<MacHost> {
}

/**
* Verify the connection to the Mac machine
* Verify the connection to the Mac machine
* @param host
* @param port
* @param credentialsId
Expand Down
65 changes: 65 additions & 0 deletions src/main/java/fr/edf/jenkins/plugins/mac/MacHostFile.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package fr.edf.jenkins.plugins.mac

import org.kohsuke.stapler.DataBoundConstructor
import org.kohsuke.stapler.DataBoundSetter
import org.kohsuke.stapler.AncestorInPath
import org.kohsuke.stapler.QueryParameter
import org.kohsuke.stapler.verb.POST

import fr.edf.jenkins.plugins.mac.util.FormUtils

import hudson.Extension
import hudson.model.Describable
import hudson.model.Descriptor
import hudson.model.Item
import jenkins.model.Jenkins
import hudson.util.ListBoxModel

class MacHostFile implements Describable<MacHostFile> {
String hostFileCredentialsId
String hostPath

@DataBoundConstructor
MacHostFile(String hostFileCredentialsId, String hostPath) {
this.hostFileCredentialsId = hostFileCredentialsId
this.hostPath = hostPath
}

@DataBoundSetter
setHostFileCredentialsId(String hostFileCredentialsId) {
this.hostFileCredentialsId = hostFileCredentialsId
}

@DataBoundSetter
setHostPath(String hostPath) {
this.hostPath = hostPath
}

@Override
public Descriptor<MacHostFile> getDescriptor() {
return Jenkins.get().getDescriptorOrDie(this.getClass())
}

@Extension
static class DescriptorImpl extends Descriptor<MacHostFile> {
/**
* {@inheritDoc}
*/
@Override
String getDisplayName() {
return Messages.HostFile_DisplayName()
}


/**
* Return ListBoxModel of existing secret files
* @param credentialsId
* @param context
* @return ListBoxModel
*/
@POST
ListBoxModel doFillHostFileCredentialsIdItems(@QueryParameter String fileCredentialsId, @AncestorInPath Item ancestor) {
return FormUtils.newFileCredentialsItemsListBoxModel(fileCredentialsId, ancestor)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ class MacComputerJNLPConnector extends MacComputerConnector {
listener.logger.print("Launching entry point cmd")
SSHCommand.launchPreLaunchCommand(host, user)
}
if (host.hostFiles) {
host.hostFiles.each { hostFile ->
FileCredentials fileCredentials = CredentialsUtils.findFileCredentials(hostFile.hostFileCredentialsId, Jenkins.get())
SSHCommand.uploadHostFile(host, user, fileCredentials, hostFile.hostPath)
}
}
SSHCommand.jnlpConnect(host, user, jenkinsUrl, computer.getJnlpMac())
}catch(Exception e) {
launched = false
Expand Down
33 changes: 30 additions & 3 deletions src/main/java/fr/edf/jenkins/plugins/mac/ssh/SSHCommand.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import fr.edf.jenkins.plugins.mac.ssh.connection.SSHUserConnectionConfiguration
import fr.edf.jenkins.plugins.mac.util.Constants
import hudson.util.Secret
import jenkins.model.Jenkins
import java.nio.file.Paths;

/**
* Contains all availables methods to execute ssh command for the Mac plugin
Expand Down Expand Up @@ -173,6 +174,32 @@ class SSHCommand {
}
}

/**
* Upload file to the host
* @param macHost
* @param user
* @param hostFile
* @param hostPath
* @return true if file uploaded
* @throws SSHCommandException, Exception
*/
@Restricted(NoExternalUse)
static boolean uploadHostFile(MacHost host, MacUser user, FileCredentials hostFile, String hostPath) throws SSHCommandException, Exception {
try {
SSHUserConnectionConfiguration connectionConfig = new SSHUserConnectionConfiguration(username: user.username, password: user.password, host: host.host,
port: host.port, connectionTimeout: host.connectionTimeout, readTimeout: host.readTimeout, kexTimeout: host.kexTimeout, macHostKeyVerifier: host.macHostKeyVerifier)
String outputDir = Paths.get(String.format(Constants.HOST_FILE_DESTINATION_BASE_FOLDER, user.username), hostPath).toAbsolutePath()
LOGGER.log(Level.FINE, "Uploading host file {0} to {1}", hostFile.fileName, outputDir)
LOGGER.log(Level.FINE, SSHCommandLauncher.executeCommand(connectionConfig, true, String.format(Constants.CREATE_DIR, outputDir)))
SSHCommandLauncher.sendFile(connectionConfig, hostFile.content, hostFile.fileName, outputDir)
return true
} catch(Exception e) {
final String message = String.format(SSHCommandException.TRANSFER_HOST_FILE_ERROR_MESSAGE, host.host, e.getMessage())
LOGGER.log(Level.SEVERE, message, e)
throw new SSHCommandException(message, e)
}
}

/**
* Generate a Mac user with the pattern in Constants
* @return a MacUser
Expand Down Expand Up @@ -224,7 +251,7 @@ class SSHCommand {

/**
* Build a single command to create the user with the password using dscl
*
*
* @param username
* @param password
* @return command as String
Expand All @@ -249,7 +276,7 @@ class SSHCommand {

/**
* Build a single command to delete the given user and his workdir using dscl
*
*
* @param username
* @return command as String
*/
Expand All @@ -260,4 +287,4 @@ class SSHCommand {
.append(Constants.COMMAND_JOINER)
.append(String.format(Constants.DELETE_USER_HOMEDIR, username)).toString()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ class Constants {
/** /Users/%s/Library/Keychains/ */
public static final String KEYCHAIN_DESTINATION_FOLDER = "/Users/%s/Library/Keychains/"

/** /Users/%s/ */
public static final String HOST_FILE_DESTINATION_BASE_FOLDER = "/Users/%s/"

// Command for grouping users on a mac (not used but keep for potential evol)
// /** sudo dseditgroup -o create %s */
// public static final String CREATE_GROUP = "sudo dseditgroup -o create %s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,14 @@ f.advanced(title:Messages.Host_Details()) {
repeatableDeleteButton:'true'
)
}

f.entry(title: _(Messages.Host_File())) {
f.repeatableHeteroProperty( field:'hostFiles',
hasHeader: 'true',
addCaption: Messages.HostFile_Add(),
deleteCaption:Messages.HostFile_Delete(),
oneEach:'false',
repeatableDeleteButton:'true'
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package fr.edf.jenkins.plugins.mac.MacHostFile

import fr.edf.jenkins.plugins.mac.Messages

def f = namespace(lib.FormTagLib)
def c = namespace(lib.CredentialsTagLib)

f.entry(title:_(Messages.Keychain_DisplayName()), field:"hostFileCredentialsId") {
c.select(context: app, includeUser: false, expressionAllowed: false)
}

f.entry(title: Messages.HostFile_HostPath(), field: 'hostPath') {
f.textbox()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The secret file credentials item
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The path on the host to upload the file to relative to the user's home folder (/Users/<username>/)
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Host.Details=Host details
Host.PreLaunchCommand=Pre-launch Command
Host.UserManagementTool= User management tool
Host.AgentJVMParameters= Agent JVM Parameters
Host.File=Host files

# MacComputerJNLPConnector
Connector.JenkinsUrl=Jenkins URL
Expand All @@ -59,6 +60,13 @@ MacSlave.RemoteFSRoot=Remote FS root
MacSlave.Labels=Labels
MacSlave.NodeProperties=Node Properties

# HostFile
HostFile.DisplayName=Host file
HostFile.HostFileCredentialsId=Credentials Id
HostFile.HostPath=Host path
HostFile.Add=Add host file
HostFile.Delete=Delete host file

# MacHostKeyVerifier
MacHostKeyVerifier.HostKey=Host Key
MacHostKeyVerifier.KeyNotTrusted=The SSH key for the host {0} does not match the key required in the connection configuration. Connections will be denied until the host key matches the configuration key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class MacPojoBuilder {
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZw==", //macHostKeyVerifier
"ls \n pwd \n whoami", //preLaunchCommands
Constants.SYSADMINCTL, //userManagementTool
"-Xms64m -Xmx128m" //agentJvmParameters
"-Xms64m -Xmx128m", //agentJvmParameters,
[] // host files
)
List<MacHost> hostList = new ArrayList()
hostList.add(host)
Expand Down