<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/MacUser.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac

import hudson.util.Secret

class MacUser {
    
    String username
    Secret password
    String workdir
    
    MacUser(String username, Secret password, String workdir) {
        this.username = username
        this.password = password
        this.workdir = workdir
    }
    
    private MacUser() {
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/util/FormUtils.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.util

import static com.cloudbees.plugins.credentials.CredentialsMatchers.anyOf
import static com.cloudbees.plugins.credentials.CredentialsMatchers.instanceOf
import static com.cloudbees.plugins.credentials.domains.URIRequirementBuilder.fromUri

import org.acegisecurity.AccessDeniedException
import org.antlr.v4.runtime.misc.NotNull
import org.jenkinsci.plugins.plaincredentials.FileCredentials
import org.kohsuke.accmod.Restricted
import org.kohsuke.accmod.restrictions.NoExternalUse

import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.common.StandardCredentials
import com.cloudbees.plugins.credentials.common.StandardListBoxModel
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials

import fr.edf.jenkins.plugins.mac.Messages
import fr.edf.jenkins.plugins.mac.ssh.SSHCommand
import fr.edf.jenkins.plugins.mac.ssh.connection.SSHGlobalConnectionConfiguration
import fr.edf.jenkins.plugins.mac.ssh.key.verifiers.MacHostKeyVerifier
import fr.edf.jenkins.plugins.mac.ssh.key.verifiers.MacHostKeyVerifierException
import hudson.model.Item
import hudson.model.ModelObject
import hudson.security.ACL
import hudson.util.FormValidation
import hudson.util.ListBoxModel
import jenkins.model.Jenkins

/**
 * Utilities for Jenkins UI Forms
 * @author <NAME>
 *
 */
class FormUtils {
    /**
     * Transform the host value to an URI
     * @param String host
     * @return URI
     */
    static URI getUri(@NotNull String host) {
        if (!(host.startsWith("http://") || host.startsWith("https://"))) {
            host = "http://" + host
        }
        if (!host.endsWith("/")) {
            host += "/"
        }
        try {
            return new URI(host)
        } catch(Exception e) {
            return null
        }
    }

    /**
     * Validate the given host
     * @see InetAddress.getByName()
     * @param host
     * @return FormValidation
     */
    @Restricted(NoExternalUse)
    static FormValidation validateHost(@NotNull final String host) {
        try {
            Jenkins.get().checkPermission(Jenkins.ADMINISTER)
            if(host) {
                InetAddress.getByName(host)
            }
            return FormValidation.ok()
        } catch(UnknownHostException uhe) {
            return FormValidation.error(Messages._Host_HostInvalid().toString())
        } catch(SecurityException se) {
            return FormValidation.error(Messages._Host_SecurityRestriction().toString())
        } catch(AccessDeniedException ex) {
            return FormValidation.error(ex.getMessage())
        }
    }

    /**
     * Return an error message if the input value is empty
     * @param value
     * @param error
     * @return
     */
    static FormValidation validateNotEmpty(@NotNull final String value, @NotNull final String error) {
        if (!value) {
            return FormValidation.error(error)
        }
        return FormValidation.ok()
    }

    /**
     * Return FormValidation to verify the connection to GitLab with the given url and credentialsId
     * @param host
     * @param port
     * @param credentialsId
     * @param context
     * @return FormValidation
     */
    @Restricted(NoExternalUse)
    static FormValidation verifyConnection(final String host, final Integer port,
            final String credentialsId, final String key, final ModelObject context) {
        try {
            Jenkins.get().checkPermission(Jenkins.ADMINISTER)
            MacHostKeyVerifier verifier = new MacHostKeyVerifier(key)
            String result = SSHCommand.checkConnection(new SSHGlobalConnectionConfiguration(credentialsId: credentialsId, port: port,
            context: context, host: host, connectionTimeout: 30,
            readTimeout: 30, kexTimeout: 0, macHostKeyVerifier: verifier))
            return FormValidation.ok(Messages._Host_ConnectionSucceeded(result).toString())
        } catch(Exception e) {
            return FormValidation.error(Messages._Host_ConnectionFailed(e.getMessage()).toString())
        }
    }

    /**
     * Return a ListBoxModel with credentials accesibles by ancestor
     * @param host
     * @param credentialsId
     * @param item
     * @return ListBoxModel
     */
    @Restricted(NoExternalUse)
    static ListBoxModel newMacHostCredentialsItemsListBoxModel(final String host,
            final String credentialsId,
            final Item ancestor) {
        // Ref: https://github.com/jenkinsci/credentials-plugin/blob/master/docs/consumer.adoc
        boolean noContextNotAdmin = ancestor == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)
        boolean contextNoPerm = ancestor != null && !ancestor.hasPermission(Item.EXTENDED_READ) &&
                !ancestor.hasPermission(CredentialsProvider.USE_ITEM)

        if (noContextNotAdmin || contextNoPerm) {
            return new StandardListBoxModel().includeCurrentValue(credentialsId)
        }
        //noinspection GroovyAssignabilityCheck
        return new StandardListBoxModel()
                .includeEmptyValue()
                .includeMatchingAs(ACL.SYSTEM,
                ancestor ?: Jenkins.get(),
                StandardCredentials,
                fromUri(getUri(host).toString()).build(),
                anyOf(instanceOf(StandardUsernamePasswordCredentials)))
    }

    /**
     * Return a ListBoxModel with credentials accesibles by ancestor
     * @param host
     * @param credentialsId
     * @param item
     * @return ListBoxModel
     */
    @Restricted(NoExternalUse)
    static ListBoxModel newFileCredentialsItemsListBoxModel(final String keychainFileId,
            final Item ancestor) {
        // Ref: https://github.com/jenkinsci/credentials-plugin/blob/master/docs/consumer.adoc
        boolean noContextNotAdmin = ancestor == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)
        boolean contextNoPerm = ancestor != null && !ancestor.hasPermission(Item.EXTENDED_READ) &&
                !ancestor.hasPermission(CredentialsProvider.USE_ITEM)

        if (noContextNotAdmin || contextNoPerm) {
            return new StandardListBoxModel().includeCurrentValue(keychainFileId)
        }
        //noinspection GroovyAssignabilityCheck
        return new StandardListBoxModel()
                .includeEmptyValue()
                .includeMatchingAs(ACL.SYSTEM,
                ancestor ?: Jenkins.get(),
                FileCredentials,
                fromUri(getUri(Jenkins.get().getRootUrl()).toString()).build(),
                anyOf(instanceOf(FileCredentials)))
    }

    /**
     * Check the validity of the given key
     * @param key
     * @return ok if valid, error with exception message if not
     */
    @Restricted(NoExternalUse)
    static FormValidation verifyHostKey(String key) {
        try {
            Jenkins.get().checkPermission(Jenkins.ADMINISTER)
            MacHostKeyVerifier.parseKey(key)
            return FormValidation.ok()
        } catch (MacHostKeyVerifierException|IllegalArgumentException|AccessDeniedException ex) {
            return FormValidation.error(ex.getMessage())
        }
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/planned/PlannedNodeBuilderFactory.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.planned;

import hudson.ExtensionList;
import hudson.ExtensionPoint;

/**
 * A factory of {@link PlannedNodeBuilder} instances.
 */
public abstract class PlannedNodeBuilderFactory implements ExtensionPoint {
    /**
     * Returns all registered implementations of {@link PlannedNodeBuilderFactory}.
     * @return all registered implementations of {@link PlannedNodeBuilderFactory}.
     */
    public static ExtensionList<PlannedNodeBuilderFactory> all() {
        return ExtensionList.lookup(PlannedNodeBuilderFactory.class);
    }

    /**
     * Returns a new instance of {@link PlannedNodeBuilder}.
     * @return a new instance of {@link PlannedNodeBuilder}.
     */
    public static PlannedNodeBuilder createInstance() {
        for (PlannedNodeBuilderFactory factory: all()) {
            PlannedNodeBuilder plannedNodeBuilder = factory.newInstance();
            if (plannedNodeBuilder != null) {
                return plannedNodeBuilder;
            }
        }
        return new StandardPlannedNodeBuilder();
    }

    /**
     * Creates a new instance of {@link PlannedNodeBuilder}.
     * @return a new instance of {@link PlannedNodeBuilder}.
     */
    public abstract PlannedNodeBuilder newInstance();
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/connector/MacComputerJNLPConnectorTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.connector

import spock.lang.Specification

class MacComputerJNLPConnectorTest extends Specification {
    // TODO : MacComputerJNLPConnectorTest
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/util/FormUtilsTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.util

import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import fr.edf.jenkins.plugins.mac.Messages
import hudson.util.FormValidation
import hudson.util.ListBoxModel
import spock.lang.Specification

class FormUtilsTest extends Specification {

    @Rule
    JenkinsRule jenkinsRule

    def "getUri with jenkins URL should not throw exception"() {
        setup:
        String url = jenkinsRule.getURL().toString()
        when:
        URI jenkinsUri = FormUtils.getUri(url)
        then:
        notThrown Exception
        null != jenkinsUri
    }

    def "getUri should return URI"() {
        setup:
        String url = "http://localhost"
        when:
        URI uri = FormUtils.getUri(url)
        then:
        notThrown Exception
        null != uri
        uri.getHost() == "localhost"
    }

    def "validate host with jenkins url should not throw exception"() {
        setup:
        URI uri = jenkinsRule.getURL().toURI()
        when:
        FormValidation result = FormUtils.validateHost(uri.getHost())
        then:
        notThrown Exception
        result == FormValidation.ok()
    }

    def "validate host with given url should not throw Exception but return invalid host"() {
        setup:
        URI uri = FormUtils.getUri("http://test.test")
        when:
        FormValidation result = FormUtils.validateHost(uri.getHost())
        then:
        notThrown Exception
        result.getMessage() == Messages._Host_HostInvalid().toString()
    }

    def "validate not empty should return ok"() {
        setup:
        String value = "test"
        String error = "error"
        when:
        FormValidation result = FormUtils.validateNotEmpty(value, error)
        then:
        notThrown Exception
        result == FormValidation.ok()
    }

    def "validate not empty should return error if input is null or empty"() {
        setup:
        String value = ""
        String value2 = null
        String error = "error"
        when:
        FormValidation result1 = FormUtils.validateNotEmpty(value, error)
        FormValidation result2 = FormUtils.validateNotEmpty(value2, error)
        then:
        notThrown Exception
        result1.getMessage() == error
        result2.getMessage() == error
    }


    def "newMacHostCredentialsItemsListBoxModel should not throw exception and should not return null"() {
        setup:
        String host = "test.host"
        String credentialsId = "credentials"

        when:
        ListBoxModel list = FormUtils.newMacHostCredentialsItemsListBoxModel(host, credentialsId, null)

        then:
        notThrown Exception
        list != null
    }

    def "newKeychainFileItemsListBoxModel should not throw exception and should not return null"() {
        setup:
        String keychainFileId = "credentials"

        when:
        ListBoxModel list = FormUtils.newFileCredentialsItemsListBoxModel(keychainFileId, null)

        then:
        notThrown Exception
        list != null
    }
}


<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/MacHost/help-userManagementTool.html<|end_filename|>
<div>
    <p>Allow to choose between "sysadminctl" and "dscl" tool to create and delete the users.</p>
    <p>Do not forget to update sudoers file before using dscl tool (see plugin documentation)</p>
    <p>This functionality was developed to fix <a href="https://issues.jenkins.io/browse/JENKINS-66374">JENKINS-66374</a></p>
</div>

<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/ssh/key/MacHostKey.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh.key

import com.trilead.ssh2.KnownHosts

class MacHostKey {

    String algorithm
    byte[] key

    MacHostKey(String algorithm, byte[] key) {
        this.algorithm = algorithm
        this.key = key
    }

    public String getFingerprint() {
        return KnownHosts.createHexFingerprint(algorithm, key.clone());
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((algorithm == null) ? 0 : algorithm.hashCode());
        result = prime * result + Arrays.hashCode(key);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this.is(obj))
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MacHostKey other = (MacHostKey) obj;
        if (algorithm == null) {
            if (other.algorithm != null)
                return false;
        } else if (!algorithm.equals(other.algorithm))
            return false;
        if (!Arrays.equals(key, other.key))
            return false;
        return true;
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/slave/MacComputerListener.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.slave

import java.util.logging.Level
import java.util.logging.Logger

import hudson.model.Computer
import hudson.model.TaskListener
import hudson.slaves.ComputerListener
import hudson.slaves.OfflineCause

public class MacComputerListener extends ComputerListener {

    private static final Logger LOGGER = Logger.getLogger(MacComputerListener.name)

    /**
     * {@inheritDoc}
     */
    @Override
    public void onLaunchFailure(Computer c, TaskListener taskListener) throws IOException, InterruptedException {
        if(c instanceof MacComputer) {
            MacComputer macComputer = (MacComputer) c
            LOGGER.log(Level.WARNING, "Mac Agent {0} failed to launch and will be removed", macComputer.getName())
            macComputer.getNode().terminate()
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onOnline(Computer c, TaskListener listener) {
        if(c instanceof MacComputer) {
            MacComputer macComputer = (MacComputer) c
            LOGGER.log(Level.FINE, "Mac Agent {} is now online", macComputer.name)
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onOffline(Computer c, OfflineCause cause) {
        if(c instanceof MacComputer) {
            MacComputer macComputer = (MacComputer) c
            LOGGER.log(Level.FINE, "Mac Agent {} is now offline", macComputer.name)
        }
    }
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/connector/MacComputerConnectorTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.connector

import spock.lang.Specification

class MacComputerConnectorTest extends Specification {
    // TODO : MacComputerConnectorTest
}


<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/MacHost/help-agentConnectionTimeout.html<|end_filename|>
<div>
    Agent connection timeout in seconds for this host. 
    The agent will be deleted if it is not able to be connected before this.
    
    <p>
    Careful, it is preferable that your agent connection timeout be inferior than the Idle minute parameter of your cloud.
    </p>
</div>

<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/MacHost/help-uploadKeychain.html<|end_filename|>
<div>
    Check it to put your keychain file on Jenkins.
    <br>
    It will be upload on the Mac agent in ~/Library/Keychains/.
</div>

<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/MacHostTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac

import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import fr.edf.jenkins.plugins.mac.test.builders.MacPojoBuilder
import spock.lang.Specification

class MacHostTest extends Specification {

    @Rule
    JenkinsRule jenkinsRule

    def "Should build and set preLaunch commands to MacHost"() {
        setup:
        MacHost host = MacPojoBuilder.buildMacHost().get(0)
        final String newLine = "\n"
        String preLaunchCommandsString = "ls"+newLine+"whoami"+newLine+"ifconfig";

        when:
        host.setPreLaunchCommands(preLaunchCommandsString)

        then:
        notThrown Exception
        host.getPreLaunchCommands().contentEquals(preLaunchCommandsString)
        host.preLaunchCommandsList.size() == 3
        host.preLaunchCommandsList.get(0).contentEquals("ls")
        host.preLaunchCommandsList.get(1).contentEquals("whoami")
        host.preLaunchCommandsList.get(2).contentEquals("ifconfig")
    }

    def "Should build and set preLaunch commands to MacHost without empty line"() {
        setup:
        MacHost host = MacPojoBuilder.buildMacHost().get(0)
        final String newLine = "\n"
        String preLaunchCommandsString = "ls"+newLine+"whoami"+newLine+"         "+newLine+"ifconfig";

        when:
        host.setPreLaunchCommands(preLaunchCommandsString)

        then:
        notThrown Exception
        !host.getPreLaunchCommands().contentEquals(preLaunchCommandsString)
        host.preLaunchCommandsList.size() == 3
        host.preLaunchCommandsList.get(0).contentEquals("ls")
        host.preLaunchCommandsList.get(1).contentEquals("whoami")
        host.preLaunchCommandsList.get(2).contentEquals("ifconfig")
    }

    def "preLaunch commands should not be null if string is empty"() {
        setup:
        MacHost host = MacPojoBuilder.buildMacHost().get(0)
        final String newLine = "\n"
        String preLaunchCommandsString = "";

        when:
        host.setPreLaunchCommands(preLaunchCommandsString)

        then:
        notThrown Exception
        host.getPreLaunchCommands().isEmpty()
        host.preLaunchCommandsList != null
        host.preLaunchCommandsList.isEmpty()
    }

    def "preLaunch commands should not be null if string is null"() {
        setup:
        MacHost host = MacPojoBuilder.buildMacHost().get(0)
        final String newLine = "\n"
        String preLaunchCommandsString = "";

        when:
        host.setPreLaunchCommands(preLaunchCommandsString)

        then:
        notThrown Exception
        host.getPreLaunchCommands().isEmpty()
        host.preLaunchCommandsList != null
        host.preLaunchCommandsList.isEmpty()
    }

    def "preLaunch commands should not be null if string is blank"() {
        setup:
        MacHost host = MacPojoBuilder.buildMacHost().get(0)
        final String newLine = "\n"
        String preLaunchCommandsString = "                   \n                 ";

        when:
        host.setPreLaunchCommands(preLaunchCommandsString)

        then:
        notThrown Exception
        host.getPreLaunchCommands().isEmpty()
        host.preLaunchCommandsList != null
        host.preLaunchCommandsList.isEmpty()
    }

    def "get preLaunchCommand should not return null"() {
        setup:
        MacHost host = MacPojoBuilder.buildMacHost().get(0)
        String preLaunchCommandsString = "";

        when:
        host.setPreLaunchCommands(preLaunchCommandsString)

        then:
        notThrown Exception
        host.getPreLaunchCommands().isEmpty()
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/strategy/FastNodeProvisionerStrategy.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.strategy;

import static hudson.slaves.NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES;
import static hudson.slaves.NodeProvisioner.StrategyDecision.PROVISIONING_COMPLETED;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;

import java.util.logging.Logger;

import javax.annotation.Nonnull;

import fr.edf.jenkins.plugins.mac.MacCloud
import hudson.Extension;
import hudson.model.Label;
import hudson.model.LoadStatistics;
import hudson.model.Queue;
import hudson.model.queue.QueueListener;
import hudson.slaves.Cloud;
import hudson.slaves.NodeProvisioner;
import hudson.slaves.NodeProvisioner.Strategy
import hudson.slaves.NodeProvisioner.StrategyDecision
import jenkins.model.Jenkins;

/**
 * Based on https://github.com/jenkinsci/one-shot-executor-plugin/blob/master/src/main/java/org/jenkinsci/plugins/oneshot/OneShotProvisionerStrategy.java
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 */
@Extension
public class FastNodeProvisionerStrategy extends Strategy {

    private static final Logger LOGGER = Logger.getLogger(FastNodeProvisionerStrategy.class.getName())

    @Nonnull
    @Override
    public StrategyDecision apply(@Nonnull NodeProvisioner.StrategyState state) {
        if (Jenkins.get().isQuietingDown()) {
            return CONSULT_REMAINING_STRATEGIES
        }


        for (Cloud cloud : Jenkins.get().clouds) {
            if (cloud instanceof MacCloud) {
                final StrategyDecision decision = applyFoCloud(state, (MacCloud) cloud);
                if (decision == PROVISIONING_COMPLETED) return decision
            }
        }
        return CONSULT_REMAINING_STRATEGIES
    }

    private StrategyDecision applyFoCloud(@Nonnull NodeProvisioner.StrategyState state, MacCloud cloud) {

        final Label label = state.getLabel()

        if (!cloud.canProvision(label)) {
            return CONSULT_REMAINING_STRATEGIES
        }

        LoadStatistics.LoadStatisticsSnapshot snapshot = state.getSnapshot()
        LOGGER.log(FINEST, "Available executors={0}, connecting={1}, planned={2}",
                snapshot.getAvailableExecutors(), snapshot.getConnectingExecutors(), state.getPlannedCapacitySnapshot())
        int availableCapacity = (snapshot.getAvailableExecutors() + snapshot.getConnectingExecutors() + state.getPlannedCapacitySnapshot())

        int currentDemand = snapshot.getQueueLength()
        LOGGER.log(FINE, "Available capacity={0}, currentDemand={1}",
                availableCapacity, currentDemand)

        if (availableCapacity < currentDemand) {
            Collection<NodeProvisioner.PlannedNode> plannedNodes = cloud.provision(label, currentDemand - availableCapacity)
            LOGGER.log(FINE, "Planned {0} new nodes", plannedNodes.size())
            state.recordPendingLaunches(plannedNodes)
            availableCapacity += plannedNodes.size()
            LOGGER.log(FINE, "After provisioning, available capacity={0}, currentDemand={1}",
                    availableCapacity, currentDemand)
        }

        if (availableCapacity >= currentDemand) {
            LOGGER.log(FINE, "Provisioning completed")
            return PROVISIONING_COMPLETED
        } else {
            LOGGER.log(FINE, "Provisioning not complete, consulting remaining strategies")
            return CONSULT_REMAINING_STRATEGIES
        }
    }

    /**
     * Ping the nodeProvisioner as a new task enters the queue, so it can provision a DockerSlave without delay.
     */
    @Extension
    public static class FastProvisionning extends QueueListener {

        @Override
        public void onEnterBuildable(Queue.BuildableItem item) {
            final Jenkins jenkins = Jenkins.get()
            final Label label = item.getAssignedLabel()
            for (Cloud cloud : Jenkins.get().clouds) {
                if (cloud instanceof MacCloud && cloud.canProvision(label)) {
                    final NodeProvisioner provisioner = (label == null
                            ? jenkins.unlabeledNodeProvisioner
                            : label.nodeProvisioner)
                    provisioner.suggestReviewNow()
                }
            }
        }
    }
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/ssh/connection/SSHConnectionFactoryTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh.connection

import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
import com.trilead.ssh2.Connection

import fr.edf.jenkins.plugins.mac.ssh.key.verifiers.MacHostKeyVerifier
import fr.edf.jenkins.plugins.mac.test.builders.MacPojoBuilder
import fr.edf.jenkins.plugins.mac.util.CredentialsUtils
import hudson.util.Secret
import spock.lang.Specification

class SSHConnectionFactoryTest extends Specification {

    @Rule
    JenkinsRule jenkins = new JenkinsRule()

    def "getSshConnection with empty parameter should return null"() {

        when:
        Connection result = SSHConnectionFactory.getSshConnection()

        then:
        notThrown Exception
        result == null
    }

    def "should return connection for user" () {
        setup:
        String host = "host"
        Connection conn = Mock(Connection)
        GroovySpy(SSHConnectionFactory, global:true)
        MacHostKeyVerifier hostKeyVerifier = Mock(MacHostKeyVerifier)
        SSHConnectionFactory.getConnection(_, host, _, _, _, _, hostKeyVerifier) >> conn

        when:
        Connection result = SSHConnectionFactory.getSshConnection(new SSHUserConnectionConfiguration(
            username: "username", password: Secret.fromString("password"),
            host: host, port: null, connectionTimeout: null, readTimeout: null, kexTimeout: null, macHostKeyVerifier: hostKeyVerifier))

        then:
        notThrown Exception
        result == conn
    }
    
    def "should return connection for global" () {
        setup:
        String host = "host"
        StandardUsernamePasswordCredentials c = new UsernamePasswordCredentialsImpl(
            CredentialsScope.SYSTEM,
            null,
            null,
            "username",
            "password"
        )
        Connection conn = Mock(Connection)
        GroovySpy(SSHConnectionFactory, global:true)
        MacHostKeyVerifier hostKeyVerifier = Mock(MacHostKeyVerifier)
        SSHConnectionFactory.getConnection(c, host, _, _, _, _, hostKeyVerifier) >> conn
        GroovyStub(CredentialsUtils, global:true)
        CredentialsUtils.findCredentials(host, _, _) >> c
        
        when:
        Connection result = SSHConnectionFactory.getSshConnection(new SSHGlobalConnectionConfiguration(
            credentialsId: host, host: host, port: null, connectionTimeout: null, readTimeout: null, kexTimeout: null, macHostKeyVerifier: hostKeyVerifier))

        then:
        notThrown Exception
        result == conn
    }
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/strategy/FastNodeProvisionerStrategyTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.strategy

import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import fr.edf.jenkins.plugins.mac.MacCloud
import fr.edf.jenkins.plugins.mac.test.builders.MacPojoBuilder
import hudson.model.Computer
import hudson.model.Label
import hudson.model.LoadStatistics.LoadStatisticsSnapshot
import hudson.slaves.NodeProvisioner.PlannedNode
import hudson.slaves.NodeProvisioner.StrategyDecision
import hudson.slaves.NodeProvisioner.StrategyState
import spock.lang.Specification

public class FastNodeProvisionerStrategyTest extends Specification {

    @Rule
    JenkinsRule jenkins = new JenkinsRule()
    
    def "should not throw exception even if it cannot connect to the mac"() {
        setup:
        final List<PlannedNode> r = new ArrayList<>()
        LoadStatisticsSnapshot snapshot = buildSnapshot()
        StrategyState state = GroovyStub(StrategyState) {
            getLabel() >> Label.parse("label").getAt(0)
            getSnapshot() >> snapshot
        }
        MacCloud cloud = new MacCloud("test", MacPojoBuilder.buildMacHost(), MacPojoBuilder.buildConnector(jenkins), new Integer(1))
//        Cannot add a Mock of MacCloud in JenkinsRule...
//        GroovySpy(constructorArgs: ["test", MacPojoBuilder.buildMacHost(), MacPojoBuilder.buildConnector(jenkins), "testLabel", new Integer(1)]) {
//            canProvision("label") >> true
//            provision("label", _) >> r
//        }
        jenkins.jenkins.clouds.add(cloud)
        
        when:
        FastNodeProvisionerStrategy provisioner = new FastNodeProvisionerStrategy()
        StrategyDecision decision = provisioner.apply(state)
        
        then:
        notThrown Exception
        decision == StrategyDecision.CONSULT_REMAINING_STRATEGIES
    }
    
    def "should not throw exception and don't find any matched label"() {
        setup:
        final List<PlannedNode> r = new ArrayList<>()
        LoadStatisticsSnapshot snapshot = buildSnapshot()
        StrategyState state = GroovyStub(StrategyState) {
            getLabel() >> Label.parse("label").getAt(0)
            getSnapshot() >> snapshot
        }
        MacCloud cloud = new MacCloud("test", MacPojoBuilder.buildMacHost(), MacPojoBuilder.buildConnector(jenkins), new Integer(1))
//        Cannot add a Mock of MacCloud in JenkinsRule...
//        GroovySpy(constructorArgs: ["test", MacPojoBuilder.buildMacHost(), MacPojoBuilder.buildConnector(jenkins), "testLabel", new Integer(1)]) {
//            canProvision("label") >> true
//            provision("label", _) >> r
//        }
        jenkins.jenkins.clouds.add(cloud)
        
        when:
        FastNodeProvisionerStrategy provisioner = new FastNodeProvisionerStrategy()
        StrategyDecision decision = provisioner.apply(state)
        
        then:
        notThrown Exception
        decision == StrategyDecision.CONSULT_REMAINING_STRATEGIES
    }
    
    private LoadStatisticsSnapshot buildSnapshot() {
        LoadStatisticsSnapshot.Builder builder = new LoadStatisticsSnapshot.Builder()
        builder.withQueueLength(1)
        builder.with((Computer) null)
        
        return builder.build()
    }
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/slave/MacSlaveTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.slave

import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import fr.edf.jenkins.plugins.mac.MacCloud
import fr.edf.jenkins.plugins.mac.MacHost
import fr.edf.jenkins.plugins.mac.MacUser
import fr.edf.jenkins.plugins.mac.connector.MacComputerJNLPConnector
import fr.edf.jenkins.plugins.mac.ssh.SSHCommand
import fr.edf.jenkins.plugins.mac.test.builders.MacPojoBuilder
import spock.lang.Specification

class MacSlaveTest extends Specification {

    @Rule
    JenkinsRule jenkinsRule

    def "should create slave"() {
        setup:
        List<MacHost> macHosts = MacPojoBuilder.buildMacHost()
        MacComputerJNLPConnector connector = MacPojoBuilder.buildConnector(jenkinsRule)
        MacUser user = MacPojoBuilder.buildUser()
        MacCloud cloud = MacPojoBuilder.buildMacCloud(macHosts, connector)
        MacSlave slave = MacPojoBuilder.buildMacSlave(cloud.name, user, macHosts.get(0), connector)

        when:
        jenkinsRule.jenkins.get().clouds.add(cloud)
        jenkinsRule.jenkins.get().addNode(slave)

        then:
        notThrown Exception
        jenkinsRule.jenkins.get().getNode(slave.name) == slave
    }

    def "should terminate and remove slave"() {
        setup:
        List<MacHost> macHosts = MacPojoBuilder.buildMacHost()
        MacComputerJNLPConnector connector = MacPojoBuilder.buildConnector(jenkinsRule)
        GroovyStub(SSHCommand, global:true) {
            deleteUserOnMac("toto", macHosts.get(0)) >> "ok"
        }
        MacUser user = MacPojoBuilder.buildUser()
        MacCloud cloud = MacPojoBuilder.buildMacCloud(macHosts, connector)
        MacSlave slave = MacPojoBuilder.buildMacSlave(cloud.name, user, macHosts.get(0), connector)

        when:
        jenkinsRule.jenkins.get().clouds.add(cloud)
        jenkinsRule.jenkins.get().addNode(slave)
        assert jenkinsRule.jenkins.get().getNode(slave.name) == slave
        slave.terminate()

        then:
        notThrown Exception
        jenkinsRule.jenkins.get().getNode(slave.name) == null
    }

    def "should return the mac cloud of the slave"() {
        setup:
        List<MacHost> macHosts = MacPojoBuilder.buildMacHost()
        MacComputerJNLPConnector connector = MacPojoBuilder.buildConnector(jenkinsRule)
        MacUser user = MacPojoBuilder.buildUser()
        MacCloud cloud = MacPojoBuilder.buildMacCloud(macHosts, connector)
        MacSlave slave = MacPojoBuilder.buildMacSlave(cloud.name, user, macHosts.get(0), connector)

        when:
        jenkinsRule.jenkins.get().clouds.add(cloud)
        jenkinsRule.jenkins.get().addNode(slave)
        assert jenkinsRule.jenkins.get().getNode(slave.name) == slave
        MacCloud result = slave.getCloud()

        then:
        notThrown Exception
        result == cloud
    }

    def "should return node name on mac cloud"() {
        setup:
        List<MacHost> macHosts = MacPojoBuilder.buildMacHost()
        MacComputerJNLPConnector connector = MacPojoBuilder.buildConnector(jenkinsRule)
        MacUser user = MacPojoBuilder.buildUser()
        MacCloud cloud = MacPojoBuilder.buildMacCloud(macHosts, connector)
        MacSlave slave = MacPojoBuilder.buildMacSlave(cloud.name, user, macHosts.get(0), connector)

        when:
        jenkinsRule.jenkins.get().clouds.add(cloud)
        jenkinsRule.jenkins.get().addNode(slave)
        assert jenkinsRule.jenkins.get().getNode(slave.name) == slave
        String result = slave.displayName

        then:
        notThrown Exception
        result == user.username + " on " + cloud.name
    }

    def "should return node name equals username"() {
        setup:
        List<MacHost> macHosts = MacPojoBuilder.buildMacHost()
        MacComputerJNLPConnector connector = MacPojoBuilder.buildConnector(jenkinsRule)
        MacUser user = MacPojoBuilder.buildUser()
        MacSlave slave = MacPojoBuilder.buildMacSlave(null, user, macHosts.get(0), connector)

        when:
        jenkinsRule.jenkins.get().addNode(slave)
        assert jenkinsRule.jenkins.get().getNode(slave.name) == slave
        String result = slave.displayName

        then:
        notThrown Exception
        result == user.username
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/slave/MacComputerFactory.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.slave

import hudson.ExtensionList
import hudson.ExtensionPoint

/**
 * A factory of {@link MacComputer} instances.
 */
abstract class MacComputerFactory implements ExtensionPoint {
    /**
     * Returns all registered implementations of {@link MacComputerFactory}.
     * @return all registered implementations of {@link MacComputerFactory}.
     */
    public static ExtensionList<MacComputerFactory> all() {
        return ExtensionList.lookup(MacComputerFactory.class)
    }

    /**
     * Returns a new instance of {@link MacComputer}.
     * @return a new instance of {@link MacComputer}.
     */
    public static MacComputer createInstance(MacSlave slave) {
        for (MacComputerFactory factory: all()) {
            MacComputer macComputer = factory.newInstance(slave)
            if (macComputer != null) {
                return macComputer
            }
        }
        return new MacComputer(slave);
    }

    /**
     * Creates a new instance of {@link MacComputer}.
     * @return a new instance of {@link MacComputer}.
     */
    public abstract MacComputer newInstance(MacSlave slave)
}


<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/MacHost/help-key.html<|end_filename|>
<div>
SSH Key of the host. This can be found with the command "ssh-keyscan hostname" by replacing hostname with your host.
This command list the different host keys with their algorithm. Choose one and put it in this field.
</div>

<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/ssh/key/verifiers/MacHostKeyVerifier.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh.key.verifiers

import java.util.logging.Level
import java.util.logging.Logger

import org.apache.commons.lang.StringUtils
import org.kohsuke.stapler.DataBoundConstructor

import com.trilead.ssh2.ServerHostKeyVerifier
import com.trilead.ssh2.signature.KeyAlgorithm
import com.trilead.ssh2.signature.KeyAlgorithmManager

import fr.edf.jenkins.plugins.mac.Messages
import fr.edf.jenkins.plugins.mac.ssh.key.MacHostKey

class MacHostKeyVerifier implements ServerHostKeyVerifier {

    private static final Logger LOGGER = Logger.getLogger(MacHostKeyVerifier.name)

    private final MacHostKey parsedKey

    @DataBoundConstructor
    MacHostKeyVerifier(final String key) {
        super()
        try {
            this.parsedKey = parseKey(key)
        } catch (MacHostKeyVerifierException e) {
            throw new IllegalArgumentException("Invalid key: " + e.getMessage(), e)
        }
    }

    /**
     * @return the algorithm type and the key as String
     */
    public String getKey() {
        return null != parsedKey ? parsedKey.algorithm + " " + Base64.getEncoder().encodeToString(parsedKey.key) : ""
    }

    String[] getAlgorithms() {
        String[] algorithms = [parsedKey.getAlgorithm()]
        return algorithms
    }

    /**
     * {@inheritDoc}
     */
    @Override
    boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws Exception {
        final MacHostKey serverParsedKey = new MacHostKey(serverHostKeyAlgorithm, serverHostKey)
        if (parsedKey.equals(serverParsedKey)) {
            LOGGER.log(Level.FINE, Messages.MacHostKeyVerifier_KeyTrusted(hostname))
            return true
        } else {
            LOGGER.log(Level.WARNING, Messages.MacHostKeyVerifier_KeyNotTrusted(hostname))
            return false
        }
    }

    /**
     * Check the validity of the host key
     * @param key
     * @return MacHostKey
     * @throws MacHostKeyVerifierException
     */
    private static MacHostKey parseKey(String key) throws MacHostKeyVerifierException {
        if (StringUtils.isEmpty(key) || !key.contains(" ")) {
            throw new MacHostKeyVerifierException(Messages.MacHostKeyVerifier_TwoPartKey())
        }
        StringTokenizer tokenizer = new StringTokenizer(key, " ")
        String algorithm = tokenizer.nextToken()
        byte[] keyValue = Base64.getDecoder().decode(tokenizer.nextToken())
        if (null == keyValue) {
            throw new MacHostKeyVerifierException(Messages.MacHostKeyVerifier_Base64EncodedKeyValueRequired())
        }
        KeyAlgorithm keyAlgorithm = KeyAlgorithmManager.getSupportedAlgorithms().find { it.getKeyFormat().equals(algorithm) }
        if (null == keyAlgorithm) {
            throw new MacHostKeyVerifierException(Messages.MacHostKeyVerifier_UnexpectedKeyAlgorithm(algorithm))
        }
        try {
            keyAlgorithm.decodePublicKey(keyValue)
        } catch (IOException ex) {
            throw new MacHostKeyVerifierException(Messages.MacHostKeyVerifier_KeyValueDoesNotParse(algorithm), ex)
        }
        return new MacHostKey(algorithm, keyValue)
    }
}


<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/MacEnvVar/config.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.MacEnvVar

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

def f = namespace(lib.FormTagLib)

f.entry(title:Messages.EnvVar_Key(), field:'key') {
    f.textbox()
}

f.entry(title: Messages.EnvVar_Value(), field: 'value') {
    f.textbox()
}

<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/connector/MacComputerJNLPConnector/help-webSocket.html<|end_filename|>
<div>
	Use WebSocket to connect agents rather than the TCP port. <b>This need
	Jenkins 2.217 and above</b>. See <a href="https://www.jenkins.io/blog/2020/02/02/web-socket/">WebSocket</a> and </a><a href="https://jenkins.io/jep/222">JEP-222</a>
	for background.
</div>

<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/MacHost/help-connectionTimeout.html<|end_filename|>
<div>
    The connection timeout in seconds for SSH connections to the Mac. Minimum value of 15 recommended.
</div>

<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/MacHost/help-preLaunchCommands.html<|end_filename|>
<div>
    <p>Shell command passed to the user before the agent starts.</p>
    <p>This field is a multi-line string. If you require more than one
        command, then you will need to expand the field. Each line match to a
        command execution.</p>
    <p>It is possible to run a script on the Mac with this field.</p>
    <p>If this field is left blank then nothing will be set before
        launch the agent.</p>
</div>

<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/provisioning/InProvisionningTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.provisioning

class InProvisionningTest {
    //TODO : InProvisionningTest
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/planned/PlannedNodeBuilder.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.planned

import fr.edf.jenkins.plugins.mac.MacCloud
import fr.edf.jenkins.plugins.mac.MacHost
import fr.edf.jenkins.plugins.mac.MacUser
import hudson.slaves.EnvironmentVariablesNodeProperty
import hudson.slaves.NodeProperty
import hudson.slaves.NodeProvisioner;

/**
 * A builder of {@link hudson.slaves.NodeProvisioner.PlannedNode} implementations for Mac.
 * Can be subclassed to provide alternative implementations of {@link hudson.slaves.NodeProvisioner.PlannedNode}.
 */
public abstract class PlannedNodeBuilder {
    protected MacCloud cloud
    protected MacUser user
    protected MacHost macHost
    protected int numExecutors = 1
    protected List<? extends NodeProperty<?>> nodeProperties

    /**
     * @param cloud the {@link MacCloud} instance to use.
     * @return the current builder.
     */
    PlannedNodeBuilder cloud(MacCloud cloud) {
        this.cloud = cloud
        return this
    }

    /**
     * @param host the {@link MacHost} instance to use.
     * @return the current builder.
     */
    PlannedNodeBuilder host(MacHost host) {
        this.macHost = host
        if(host.envVars) {
            this.nodeProperties = new ArrayList()
            nodeProperties.add(new EnvironmentVariablesNodeProperty(host.envVars.collect {
                new EnvironmentVariablesNodeProperty.Entry(it.key, it.value)
            }))
        }
        else {
            this.nodeProperties = Collections.EMPTY_LIST
        }
        return this
    }

    /**
     * @param numExecutors the number of executors.
     * @return the current builder.
     */
    PlannedNodeBuilder numExecutors(int numExecutors) {
        this.numExecutors = numExecutors
        return this
    }

    /**
     * Builds the {@link hudson.slaves.NodeProvisioner.PlannedNode} instance based
     * on the given inputs.
     * 
     * @return a {@link hudson.slaves.NodeProvisioner.PlannedNode} configured from
     *         this builder.
     */
    abstract NodeProvisioner.PlannedNode build()
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/util/CredentialsUtilsTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.util

import org.jenkinsci.plugins.plaincredentials.FileCredentials
import org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl
import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.SecretBytes
import com.cloudbees.plugins.credentials.common.StandardCredentials
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials
import com.cloudbees.plugins.credentials.domains.Domain
import com.cloudbees.plugins.credentials.domains.HostnameSpecification
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl

import hudson.model.FileParameterValue.FileItemImpl
import jenkins.model.Jenkins
import spock.lang.Specification

class CredentialsUtilsTest extends Specification {

    @Rule
    public JenkinsRule r = new JenkinsRule()

    def "findCredentials should return credentials with id"() {
        setup:
        Domain domain = new Domain("example", "test domain", Arrays.asList(new HostnameSpecification("example.org", null)))
        StandardUsernamePasswordCredentials c1 = new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "test1",
                null,
                "username",
                "password"
                )
        StandardUsernamePasswordCredentials c2 = new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "test2",
                null,
                "username",
                "password"
                )
        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c1)
        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c2)

        when:
        StandardCredentials result = CredentialsUtils.findCredentials("example.org", "test1", Jenkins.get())

        then:
        assert result != null
        assert result instanceof StandardUsernamePasswordCredentials
        assert result.id == "test1"
    }

    def "findCredentials should throw IllegalArgumentException because id does not exist"() {
        setup:
        Domain domain = new Domain("example", "test domain", Arrays.asList(new HostnameSpecification("example.org", null)))
        StandardUsernamePasswordCredentials c1 = new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "test1",
                null,
                "username",
                "password"
                )
        StandardUsernamePasswordCredentials c2 = new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "test2",
                null,
                "username",
                "password"
                )
        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c1)
        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c2)

        when:
        StandardCredentials result = CredentialsUtils.findCredentials("example.org", "inexistentId", Jenkins.get())

        then:
        IllegalArgumentException iae = thrown()
    }

    def "findFileCredentials should return credentials with id"() {
        setup:
        Domain domain = new Domain("example", "test domain", Arrays.asList(new HostnameSpecification(Jenkins.get().getRootUrl(), null)))
        FileCredentials c1 = new FileCredentialsImpl(
            CredentialsScope.SYSTEM,
            "keychain1",
            "keychain1",
            new FileItemImpl(new File("src/test/resources/test.keychain-db")),
            null,
            new SecretBytes(true, new String("test").bytes)
            )
        StandardUsernamePasswordCredentials c2 = new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "test2",
                null,
                "username",
                "password"
                )
        boolean test = CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c1)
        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c2)

        when:
        FileCredentials result = CredentialsUtils.findFileCredentials("keychain1", Jenkins.get())

        then:
        assert result != null
        assert result instanceof FileCredentials
        assert result.id == "keychain1"
    }

    def "findKeychain should throw IllegalArgumentException because there is no KeychainFileCredentials"() {
        setup:
        Domain domain = new Domain("example", "test domain", Arrays.asList(new HostnameSpecification(Jenkins.get().getRootUrl(), null)))
        StandardUsernamePasswordCredentials c1 = new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "keychain1",
                null,
                "username",
                "password"
                )
        StandardUsernamePasswordCredentials c2 = new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "test2",
                null,
                "username",
                "password"
                )
        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c1)
        CredentialsProvider.lookupStores(r.jenkins).iterator().next().addDomain(domain, c2)

        when:
        FileCredentials result = CredentialsUtils.findFileCredentials("keychain1", Jenkins.get())

        then:
        IllegalArgumentException iae = thrown()
    }
}


<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/connector/MacComputerJNLPConnector/config.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.MacCloud

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

def f = namespace(lib.FormTagLib)

f.entry(title: _(Messages.Connector_WebSocket()), field: 'webSocket') {
    f.checkbox()
}

f.entry(title: _(Messages.Connector_JenkinsUrl()), field: 'jenkinsUrl') {
    f.textbox()
}


<|start_filename|>src/main/resources/fr/edf/jenkins/plugins/mac/slave/MacSlave/configure-entries.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.slave.MacSlave

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

def f = namespace(lib.FormTagLib)

f.entry(title: Messages.MacSlave_Description(), help: '/help/system-config/master-slave/description.html') {
    f.textbox(field:'nodeDescription')
}

f.entry(title:Messages.MacSlave_Executors(), field:'numExecutors') {
    f.textbox()
}

f.entry(title: Messages.MacSlave_RemoteFSRoot(), field: 'remoteFS') {
    f.textbox()
}

f.slave_mode(name: 'mode', node: it)

f.descriptorList(title: Messages.MacSlave_NodeProperties(), descriptors: h.getNodePropertyDescriptors(descriptor.clazz), field: 'nodeProperties')

<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/ssh/key/verifiers/MacHostKeyVerifierTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh.key.verifiers

import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import fr.edf.jenkins.plugins.mac.MacHost
import fr.edf.jenkins.plugins.mac.MacHost.DescriptorImpl
import fr.edf.jenkins.plugins.mac.ssh.key.MacHostKey
import hudson.util.FormValidation
import spock.lang.Specification

class MacHostKeyVerifierTest extends Specification {

    @Rule
    JenkinsRule jenkinsRule

    def "doCheckKey with empty parameter should return Key should be 2 parts: algorithm and Base 64 encoded key value."() {
        given:
        String key = ""
        DescriptorImpl desc = new MacHost.DescriptorImpl()

        when:
        FormValidation result = desc.doCheckKey(key)

        then:
        result.getMessage().equals("Key should be 2 parts: algorithm and Base 64 encoded key value.")
    }

    def "doCheckKey should work"() {
        given:
        String key = "ssh-rsa <KEY>
        DescriptorImpl desc = new MacHost.DescriptorImpl()

        when:
        FormValidation result = desc.doCheckKey(key)

        then:
        result == FormValidation.OK
    }

    def "doCheckKey should return Input byte array has wrong 4-byte ending unit"() {
        given:
        String key = "ssh-rsa <KEY>
        DescriptorImpl desc = new MacHost.DescriptorImpl()

        when:
        FormValidation result = desc.doCheckKey(key)

        then:
        result.getMessage().equals("Input byte array has wrong 4-byte ending unit")
    }

    def "doCheckKey should return Unexpected key algorithm: ss-rsa"() {
        given:
        String key = "ss-rsa <KEY>
        DescriptorImpl desc = new MacHost.DescriptorImpl()

        when:
        FormValidation result = desc.doCheckKey(key)

        then:
        result.getMessage().equals("Unexpected key algorithm: ss-rsa")
    }

    def "doCheckKey should return Illegal base64 character 3a"() {
        given:
        String key = "ssh-rsa ::::"
        DescriptorImpl desc = new MacHost.DescriptorImpl()

        when:
        FormValidation result = desc.doCheckKey(key)

        then:
        result.getMessage().equals("Illegal base64 character 3a")
    }

    def "doCheckKey should return Key should be 2 parts: algorithm and Base 64 encoded key value. because of null value"() {
        given:
        String key = null
        DescriptorImpl desc = new MacHost.DescriptorImpl()

        when:
        FormValidation result = desc.doCheckKey(key)

        then:
        result.getMessage().equals("Key should be 2 parts: algorithm and Base 64 encoded key value.")
    }

    def "verifyServerHostKey should retrurn true"() {
        given:
        String key = "<KEY>
        String hostname = "test"
        int port = 22
        MacHostKey macHostKey = MacHostKeyVerifier.parseKey(key)
        MacHostKeyVerifier verifier = new MacHostKeyVerifier(key)

        when:
        boolean result = verifier.verifyServerHostKey(hostname, port, macHostKey.algorithm, macHostKey.key)

        then:
        result == true
    }

    def "verifyServerHostKey should return false"() {
        given:
        String key = "ssh-rsa <KEY>
        String serverKey = "<KEY>
        String hostname = "test"
        int port = 22
        MacHostKey macHostKey = MacHostKeyVerifier.parseKey(serverKey)
        MacHostKeyVerifier verifier = new MacHostKeyVerifier(key)

        when:
        boolean result = verifier.verifyServerHostKey(hostname, port, macHostKey.algorithm, macHostKey.key)

        then:
        result == false
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/ssh/connection/SSHConnectionConfiguration.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh.connection

import fr.edf.jenkins.plugins.mac.ssh.key.verifiers.MacHostKeyVerifier
import hudson.model.ModelObject
import hudson.util.Secret

/**
 * All properties needed for SshClientFactory
 * @author <NAME>
 *
 */
abstract class SSHConnectionConfiguration {
    String host
    Integer port
    Integer connectionTimeout
    Integer readTimeout
    Integer kexTimeout
    MacHostKeyVerifier macHostKeyVerifier
}

/**
 * Specific for global connection
 * @author <NAME>
 *
 */
class SSHGlobalConnectionConfiguration extends SSHConnectionConfiguration {
    String credentialsId
    ModelObject context
}

/**
 * Specific for user connection
 * @author <NAME>
 *
 */
class SSHUserConnectionConfiguration extends SSHConnectionConfiguration {
    String username
    Secret password
}


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/ssh/SSHCommandLauncherTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh

import org.junit.Rule
import org.jvnet.hudson.test.JenkinsRule

import com.trilead.ssh2.ChannelCondition
import com.trilead.ssh2.Connection
import com.trilead.ssh2.Session

import fr.edf.jenkins.plugins.mac.ssh.connection.SSHConnectionFactory
import fr.edf.jenkins.plugins.mac.ssh.connection.SSHGlobalConnectionConfiguration
import fr.edf.jenkins.plugins.mac.ssh.connection.SSHUserConnectionConfiguration
import spock.lang.Specification

class SSHCommandLauncherTest extends Specification {

    @Rule
    JenkinsRule jenkins

    def "executeCommand should not throw exception"() {
        setup:
        String command = "test command"
        Session session = Stub(Session) {
            execCommand(command) >> null
            waitForCondition(ChannelCondition.EXIT_STATUS | ChannelCondition.EXIT_SIGNAL, 5000) >> 1
            close() >> null
            getExitSignal() >> "0"
            getExitStatus() >> 0
            getStdout() >> new ByteArrayInputStream(new String("out").getBytes())
            getStderr() >> new ByteArrayInputStream(new String("err").getBytes())
        }
        Connection conn = Stub(Connection) {
            openSession() >> session
        }
        GroovySpy(SSHConnectionFactory, global:true)
        1 * SSHConnectionFactory.getSshConnection(*_) >> conn
        SSHGlobalConnectionConfiguration connectionConfig = Mock(SSHGlobalConnectionConfiguration)
        when:
        String result = SSHCommandLauncher.executeCommand(connectionConfig, false, command)

        then:
        notThrown Exception
        result != null
        result == "out"
    }

    def "executeCommand should throw exception because exit status is 1"() {
        setup:
        String command = "test command"
        Session session = Stub(Session) {
            execCommand(command) >> null
            waitForCondition(ChannelCondition.EXIT_STATUS | ChannelCondition.EXIT_SIGNAL, 5000) >> 1
            close() >> null
            getExitSignal() >> "Error"
            getExitStatus() >> 1
            getStdout() >> new ByteArrayInputStream(new String("out").getBytes())
            getStderr() >> new ByteArrayInputStream(new String("err").getBytes())
        }

        Connection conn = Stub(Connection) {
            openSession() >> session
        }
        GroovySpy(SSHConnectionFactory, global:true)
        1 * SSHConnectionFactory.getSshConnection(*_) >> conn
        SSHGlobalConnectionConfiguration connectionConfig = Mock(SSHGlobalConnectionConfiguration)
        when:
        String result = SSHCommandLauncher.executeCommand(connectionConfig, false, command)

        then:
        Exception e = thrown()
        e.getMessage() == "Failed to execute command " + command
    }

    def "executeCommand should not throw exception with exit status is 1 because ignore error is true"() {
        setup:
        String command = "test command"
        Session session = Stub(Session) {
            execCommand(command) >> null
            waitForCondition(ChannelCondition.EXIT_STATUS | ChannelCondition.EXIT_SIGNAL, 5000) >> 1
            close() >> null
            getExitSignal() >> "Error"
            getExitStatus() >> 1
            getStdout() >> new ByteArrayInputStream(new String("").getBytes())
            getStderr() >> new ByteArrayInputStream(new String("err").getBytes())
        }
        Connection conn = Stub(Connection) {
            openSession() >> session
        }
        GroovySpy(SSHConnectionFactory, global:true)
        1 * SSHConnectionFactory.getSshConnection(*_) >> conn
        SSHGlobalConnectionConfiguration connectionConfig = Mock(SSHGlobalConnectionConfiguration)
        when:
        String result = SSHCommandLauncher.executeCommand(connectionConfig, true, command)

        then:
        notThrown Exception
        result == "err"
    }

    def "sendFile should throw Exception because SCPClient cannot find host" () {
        setup:
        SSHUserConnectionConfiguration connectionConfig = Mock(SSHUserConnectionConfiguration)
        Connection conn = Stub(Connection) {
            close() >> {}
        }
        GroovySpy(SSHConnectionFactory, global:true)
        1 * SSHConnectionFactory.getSshConnection(*_) >> conn
        InputStream content = new ByteArrayInputStream(new String().getBytes())
        String fileName = "fileName"
        String outputDir = "outputDir"

        when:
        SSHCommandLauncher.sendFile(connectionConfig, content, fileName, outputDir)

        then:
        Exception e = thrown()
    }

    def "executeMultipleCommands should not throw exception"() {
        setup:
        String command = "test command"
        Session session = Stub(Session) {
            execCommand(command) >> null
            waitForCondition(ChannelCondition.EXIT_STATUS | ChannelCondition.EXIT_SIGNAL, 5000) >> 1
            close() >> null
            getExitSignal() >> "0"
            getExitStatus() >> 0
            getStdout() >> new ByteArrayInputStream(new String("out").getBytes())
            getStderr() >> new ByteArrayInputStream(new String("err").getBytes())
        }
        Connection conn = Stub(Connection) {
            openSession() >> session
            close() >> null
        }
        GroovySpy(SSHConnectionFactory, global:true)
        1 * SSHConnectionFactory.getSshConnection(*_) >> conn
        SSHGlobalConnectionConfiguration connectionConfig = Mock(SSHGlobalConnectionConfiguration)
        String ls = "ls"
        String whoami = "whoami"
        List<String> commands = Arrays.asList(ls, whoami)
        GroovySpy(SSHCommandLauncher, global:true)

        when:
        SSHCommandLauncher.executeMultipleCommands(connectionConfig, true, commands)

        then:
        1 * SSHCommandLauncher.executeCommandWithConnection(conn, false, ls)
        1 * SSHCommandLauncher.executeCommandWithConnection(conn, false, whoami)
        notThrown Exception
    }

    def "executeMultipleCommands should throw exception and stop at the first command ignoreError is false and exit status is 1"() {
        setup:
        String command = "test command"
        Session session = Stub(Session) {
            execCommand(command) >> null
            waitForCondition(ChannelCondition.EXIT_STATUS | ChannelCondition.EXIT_SIGNAL, 5000) >> 1
            close() >> null
            getExitSignal() >> "0"
            getExitStatus() >> 1
            getStdout() >> new ByteArrayInputStream(new String("out").getBytes())
            getStderr() >> new ByteArrayInputStream(new String("err").getBytes())
        }
        Connection conn = Stub(Connection) {
            openSession() >> session
            close() >> null
        }
        GroovySpy(SSHConnectionFactory, global:true)
        1 * SSHConnectionFactory.getSshConnection(*_) >> conn
        SSHGlobalConnectionConfiguration connectionConfig = Mock(SSHGlobalConnectionConfiguration)
        String ls = "ls"
        String whoami = "whoami"
        List<String> commands = Arrays.asList(ls, whoami)
        GroovySpy(SSHCommandLauncher, global:true)

        when:
        SSHCommandLauncher.executeMultipleCommands(connectionConfig, false, commands)

        then:
        1 * SSHCommandLauncher.executeCommandWithConnection(conn, false, ls)
        0 * SSHCommandLauncher.executeCommandWithConnection(conn, false, whoami)
        Exception e = thrown()
    }
    
    def "executeMultipleCommands should not throw exception and launch both commands with exit status 1 and ignore error true"() {
        setup:
        String command = "test command"
        Session session = Stub(Session) {
            execCommand(command) >> null
            waitForCondition(ChannelCondition.EXIT_STATUS | ChannelCondition.EXIT_SIGNAL, 5000) >> 1
            close() >> null
            getExitSignal() >> "0"
            getExitStatus() >> 1
            getStdout() >> new ByteArrayInputStream(new String("out").getBytes())
            getStderr() >> new ByteArrayInputStream(new String("err").getBytes())
        }
        Connection conn = Stub(Connection) {
            openSession() >> session
            close() >> null
        }
        GroovySpy(SSHConnectionFactory, global:true)
        1 * SSHConnectionFactory.getSshConnection(*_) >> conn
        SSHGlobalConnectionConfiguration connectionConfig = Mock(SSHGlobalConnectionConfiguration)
        String ls = "ls"
        String whoami = "whoami"
        List<String> commands = Arrays.asList(ls, whoami)
        GroovySpy(SSHCommandLauncher, global:true)

        when:
        SSHCommandLauncher.executeMultipleCommands(connectionConfig, true, commands)

        then:
        1 * SSHCommandLauncher.executeCommandWithConnection(conn, false, ls)
        1 * SSHCommandLauncher.executeCommandWithConnection(conn, false, whoami)
        notThrown Exception
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/ssh/key/verifiers/MacHostKeyVerifierException.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh.key.verifiers

class MacHostKeyVerifierException extends Exception {

    /**
     * Constructor with message
     * @param message
     */
    MacHostKeyVerifierException(String message) {
        super(message)
    }

    /**
     * Constructor with message and cause
     * @param message
     * @param cause
     */
    MacHostKeyVerifierException(String message, Exception cause) {
        super(message, cause)
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/cause/MacOfflineCause.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.cause

import hudson.slaves.OfflineCause

class MacOfflineCause extends OfflineCause {
    
    @Override
    public String toString() {
        return "Remove Mac user"
    }
}


<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/ssh/connection/SSHConnectionFactory.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.ssh.connection

import org.kohsuke.accmod.Restricted
import org.kohsuke.accmod.restrictions.NoExternalUse

import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.common.StandardCredentials
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
import com.trilead.ssh2.Connection

import fr.edf.jenkins.plugins.mac.ssh.key.verifiers.MacHostKeyVerifier
import fr.edf.jenkins.plugins.mac.util.CredentialsUtils
import jenkins.model.Jenkins

/**
 * Factory of SSH Connection
 * @author <NAME>
 *
 */
class SSHConnectionFactory {

    /**
     * Dispatch request on the good method of connection
     * @param conf
     * @return com.trilead.ssh2.Connection
     */
    static Connection getSshConnection(SSHConnectionConfiguration conf) {
        if(conf instanceof SSHGlobalConnectionConfiguration) {
            return getGlobalSshConnection(conf)
        }
        if(conf instanceof SSHUserConnectionConfiguration) {
            return getUserSshConnection(conf)
        }
        return null
    }

    /**
     * Get the SSH client to the Mac for global user
     * @param conf
     * @return com.trilead.ssh2.Connection
     */
    @Restricted(NoExternalUse)
    private static Connection getGlobalSshConnection(SSHGlobalConnectionConfiguration conf = new SSHGlobalConnectionConfiguration()) {
        String host = conf.host
        Integer port = conf.port ?: new Integer(22)
        Integer connectionTimeout = conf.connectionTimeout ?: new Integer(5)
        Integer readTimeout = conf.readTimeout ?: new Integer(60)
        Integer kexTimeout = conf.kexTimeout ?: new Integer(0)
        def context = conf.context ?: Jenkins.get()
        def credentialsId = conf.credentialsId ?: null
        if(!credentialsId) {throw new Exception("No credentials found for the host " + host)}
        MacHostKeyVerifier macHostKeyVerifier = conf.macHostKeyVerifier ?: new MacHostKeyVerifier()
        def credentials = CredentialsUtils.findCredentials(host, credentialsId, context)
        return getConnection(credentials, host, port, connectionTimeout, readTimeout, kexTimeout, macHostKeyVerifier)
    }

    /**
     * Generate a transient credential with the given user and password and return a connection
     * @param conf
     * @return com.trilead.ssh2.Connection
     */
    @Restricted(NoExternalUse)
    private static Connection getUserSshConnection(SSHUserConnectionConfiguration conf) {
        String host = conf.host
        Integer port = conf.port ?: new Integer(22)
        Integer connectionTimeout = conf.connectionTimeout ?: new Integer(0)
        Integer readTimeout = conf.readTimeout ?: new Integer(0)
        Integer kexTimeout = conf.kexTimeout ?: new Integer(0)
        MacHostKeyVerifier macHostKeyVerifier = conf.macHostKeyVerifier ?: new MacHostKeyVerifier()
        UsernamePasswordCredentials credentials =  new UsernamePasswordCredentialsImpl(
                CredentialsScope.SYSTEM,
                "global",
                null,
                conf.username,
                conf.password.getPlainText()
                );
        return getConnection(credentials, host, port, connectionTimeout, readTimeout, kexTimeout, macHostKeyVerifier)
    }

    /**
     * Return the ssh client to the Mac
     * @param credentials
     * @param host
     * @param port
     * @param connectionTimeout
     * @param readTimeout
     * @param kexTimeout
     * @return com.trilead.ssh2.Connection
     */
    @Restricted(NoExternalUse)
    private static Connection getConnection(final StandardCredentials credentials, final String host, final Integer port,
            final Integer connectionTimeout, final Integer readTimeout, final Integer kexTimeout, MacHostKeyVerifier macHostKeyVerifier) {
        String adr = InetAddress.getByName(host).toString().split("/")[1]
        Connection conn = new Connection(adr, port.intValue())
        if(credentials instanceof StandardUsernamePasswordCredentials) {
            StandardUsernamePasswordCredentials usernamePasswordCredentials = credentials
            if(null != macHostKeyVerifier.getAlgorithms()) { conn.setServerHostKeyAlgorithms(macHostKeyVerifier.getAlgorithms()) }
            conn.connect(macHostKeyVerifier, connectionTimeout.multiply(1000).intValue(), readTimeout.multiply(1000).intValue(), kexTimeout.multiply(1000).intValue())
            conn.authenticateWithPassword(usernamePasswordCredentials.getUsername(), usernamePasswordCredentials.getPassword().getPlainText())
        }
        return conn
    }
}

<|start_filename|>src/main/java/fr/edf/jenkins/plugins/mac/MacEnvVar.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac


import org.kohsuke.stapler.DataBoundConstructor
import org.kohsuke.stapler.DataBoundSetter

import hudson.Extension
import hudson.model.Describable
import hudson.model.Descriptor
import jenkins.model.Jenkins

class MacEnvVar implements Describable<MacEnvVar> {

    String key
    String value

    @DataBoundConstructor
    MacEnvVar(String key, String value) {
        this.key = key
        this.value = value
    }

    @DataBoundSetter
    setKey(String key) {
        this.key = key
    }

    @DataBoundSetter
    setValue(String value) {
        this.value = value
    }

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

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


<|start_filename|>src/test/java/fr/edf/jenkins/plugins/mac/provisioning/MacInProvisioningTest.groovy<|end_filename|>
package fr.edf.jenkins.plugins.mac.provisioning

class MacInProvisioningTest {
    //TODO : MacInProvisioningTest
}
