Search in sources :

Example 1 with EXIT

use of org.csanchez.jenkins.plugins.kubernetes.pipeline.Constants.EXIT in project kubernetes-plugin by jenkinsci.

the class ContainerExecDecorator method decorate.

@Override
public Launcher decorate(final Launcher launcher, final Node node) {
    // If the node is not a KubernetesSlave return the original launcher
    if (node != null && !(node instanceof KubernetesSlave)) {
        return launcher;
    }
    return new Launcher.DecoratedLauncher(launcher) {

        @Override
        public Proc launch(ProcStarter starter) throws IOException {
            LOGGER.log(Level.FINEST, "Launch proc with environment: {0}", Arrays.toString(starter.envs()));
            // find container working dir
            KubernetesSlave slave = (KubernetesSlave) node;
            FilePath containerWorkingDirFilePath = starter.pwd();
            String containerWorkingDirFilePathStr = containerWorkingDirFilePath != null ? containerWorkingDirFilePath.getRemote() : ContainerTemplate.DEFAULT_WORKING_DIR;
            String containerWorkingDirStr = ContainerTemplate.DEFAULT_WORKING_DIR;
            if (slave != null && slave.getPod().isPresent() && containerName != null) {
                Optional<Container> container = slave.getPod().get().getSpec().getContainers().stream().filter(container1 -> container1.getName().equals(containerName)).findAny();
                Optional<String> containerWorkingDir = Optional.empty();
                if (container.isPresent() && container.get().getWorkingDir() != null) {
                    containerWorkingDir = Optional.of(container.get().getWorkingDir());
                }
                if (containerWorkingDir.isPresent()) {
                    containerWorkingDirStr = containerWorkingDir.get();
                }
                if (containerWorkingDir.isPresent() && containerWorkingDirFilePath != null && !containerWorkingDirFilePath.getRemote().startsWith(containerWorkingDirStr)) {
                    // Container has a custom workingDir, updated the pwd to match container working dir
                    containerWorkingDirFilePathStr = containerWorkingDirFilePath.getRemote().replaceFirst(ContainerTemplate.DEFAULT_WORKING_DIR, containerWorkingDirStr);
                    containerWorkingDirFilePath = new FilePath(containerWorkingDirFilePath.getChannel(), containerWorkingDirFilePathStr);
                    LOGGER.log(Level.FINEST, "Modified the pwd to match {0} containers workspace directory : {1}", new String[] { containerName, containerWorkingDirFilePathStr });
                }
            }
            String[] envVars = starter.envs();
            // modify the working dir on envvars part of starter env vars
            if (!containerWorkingDirStr.equals(ContainerTemplate.DEFAULT_WORKING_DIR)) {
                for (int i = 0; i < envVars.length; i++) {
                    String keyValue = envVars[i];
                    String[] split = keyValue.split("=", 2);
                    if (split[1].startsWith(ContainerTemplate.DEFAULT_WORKING_DIR)) {
                        // Container has a custom workingDir, update env vars with right workspace folder
                        split[1] = split[1].replaceFirst(ContainerTemplate.DEFAULT_WORKING_DIR, containerWorkingDirStr);
                        envVars[i] = split[0] + "=" + split[1];
                        LOGGER.log(Level.FINEST, "Updated the starter environment variable, key: {0}, Value: {1}", new String[] { split[0], split[1] });
                    }
                }
            }
            if (node != null) {
                // It seems this is possible despite the method javadoc saying it is non-null
                final Computer computer = node.toComputer();
                if (computer != null) {
                    List<String> resultEnvVar = new ArrayList<>();
                    try {
                        EnvVars environment = computer.getEnvironment();
                        if (environment != null) {
                            Set<String> overriddenKeys = new HashSet<>();
                            for (String keyValue : envVars) {
                                String[] split = keyValue.split("=", 2);
                                if (!split[1].equals(environment.get(split[0]))) {
                                    // Only keep environment variables that differ from Computer's environment
                                    resultEnvVar.add(keyValue);
                                    overriddenKeys.add(split[0]);
                                }
                            }
                            // modify the working dir on envvars part of Computer
                            if (!containerWorkingDirStr.equals(ContainerTemplate.DEFAULT_WORKING_DIR)) {
                                for (Map.Entry<String, String> entry : environment.entrySet()) {
                                    if (entry.getValue().startsWith(ContainerTemplate.DEFAULT_WORKING_DIR) && !overriddenKeys.contains(entry.getKey())) {
                                        // Value should be overridden and is not overridden earlier
                                        String newValue = entry.getValue().replaceFirst(ContainerTemplate.DEFAULT_WORKING_DIR, containerWorkingDirStr);
                                        String keyValue = entry.getKey() + "=" + newValue;
                                        LOGGER.log(Level.FINEST, "Updated the value for envVar, key: {0}, Value: {1}", new String[] { entry.getKey(), newValue });
                                        resultEnvVar.add(keyValue);
                                    }
                                }
                            }
                            envVars = resultEnvVar.toArray(new String[resultEnvVar.size()]);
                        }
                    } catch (InterruptedException e) {
                        throw new IOException("Unable to retrieve environment variables", e);
                    }
                }
            }
            return doLaunch(starter.quiet(), fixDoubleDollar(envVars), starter.stdout(), containerWorkingDirFilePath, starter.masks(), getCommands(starter, containerWorkingDirFilePathStr, launcher.isUnix()));
        }

        private Proc doLaunch(boolean quiet, String[] cmdEnvs, OutputStream outputForCaller, FilePath pwd, boolean[] masks, String... commands) throws IOException {
            final CountDownLatch started = new CountDownLatch(1);
            final CountDownLatch finished = new CountDownLatch(1);
            final AtomicBoolean alive = new AtomicBoolean(false);
            final AtomicLong startAlive = new AtomicLong();
            long startMethod = System.nanoTime();
            PrintStream printStream;
            OutputStream stream;
            // Only output to stdout at the beginning for diagnostics.
            ByteArrayOutputStream stdout = new ByteArrayOutputStream();
            // Wrap stdout so that we can toggle it off.
            ToggleOutputStream toggleStdout = new ToggleOutputStream(stdout);
            // Do not send this command to the output when in quiet mode
            if (quiet) {
                stream = toggleStdout;
                printStream = new PrintStream(stream, true, StandardCharsets.UTF_8.toString());
            } else {
                printStream = launcher.getListener().getLogger();
                stream = new TeeOutputStream(toggleStdout, printStream);
            }
            ByteArrayOutputStream dryRunCaller = null;
            ;
            ToggleOutputStream toggleDryRunCaller = null;
            ToggleOutputStream toggleOutputForCaller = null;
            // Send to proc caller as well if they sent one
            if (outputForCaller != null && !outputForCaller.equals(printStream)) {
                if (launcher.isUnix()) {
                    stream = new TeeOutputStream(outputForCaller, stream);
                } else {
                    // Prepare to capture output for later.
                    dryRunCaller = new ByteArrayOutputStream();
                    toggleDryRunCaller = new ToggleOutputStream(dryRunCaller);
                    // Initially disable the output for the caller, to prevent it from getting unwanted output such as prompt
                    toggleOutputForCaller = new ToggleOutputStream(outputForCaller, true);
                    stream = new TeeOutputStream(toggleOutputForCaller, stream);
                    stream = new TeeOutputStream(toggleDryRunCaller, stream);
                }
            }
            ByteArrayOutputStream error = new ByteArrayOutputStream();
            String[] sh = shell != null ? new String[] { shell } : launcher.isUnix() ? new String[] { "sh" } : new String[] { "cmd", "/Q" };
            String msg = "Executing " + String.join(" ", sh) + " script inside container " + containerName + " of pod " + getPodName();
            LOGGER.log(Level.FINEST, msg);
            printStream.println(msg);
            if (closables == null) {
                closables = new ArrayList<>();
            }
            Execable<String, ExecWatch> execable = // 
            getClient().pods().inNamespace(getNamespace()).withName(getPodName()).inContainer(containerName).redirectingInput(// JENKINS-50429
            STDIN_BUFFER_SIZE).writingOutput(stream).writingError(stream).writingErrorChannel(error).usingListener(new ExecListener() {

                @Override
                public void onOpen() {
                    alive.set(true);
                    started.countDown();
                    startAlive.set(System.nanoTime());
                    LOGGER.log(Level.FINEST, "onOpen : {0}", finished);
                }

                @Override
                public void onFailure(Throwable t, Response response) {
                    alive.set(false);
                    t.printStackTrace(launcher.getListener().getLogger());
                    started.countDown();
                    LOGGER.log(Level.FINEST, "onFailure : {0}", finished);
                    if (finished.getCount() == 0) {
                        LOGGER.log(Level.WARNING, "onFailure called but latch already finished. This may be a bug in the kubernetes-plugin");
                    }
                    finished.countDown();
                }

                @Override
                public void onClose(int i, String s) {
                    alive.set(false);
                    started.countDown();
                    LOGGER.log(Level.FINEST, "onClose : {0} [{1} ms]", new Object[] { finished, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startAlive.get()) });
                    if (finished.getCount() == 0) {
                        LOGGER.log(Level.WARNING, "onClose called but latch already finished. This indicates a bug in the kubernetes-plugin");
                    }
                    finished.countDown();
                }
            });
            ExecWatch watch;
            try {
                watch = execable.exec(sh);
            } catch (KubernetesClientException e) {
                if (e.getCause() instanceof InterruptedException) {
                    throw new IOException("Interrupted while starting websocket connection, you should increase the Max connections to Kubernetes API", e);
                } else {
                    throw e;
                }
            } catch (RejectedExecutionException e) {
                throw new IOException("Connection was rejected, you should increase the Max connections to Kubernetes API", e);
            }
            boolean hasStarted = false;
            try {
                // prevent a wait forever if the connection is closed as the listener would never be called
                hasStarted = started.await(WEBSOCKET_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                closeWatch(watch);
                throw new IOException("Interrupted while waiting for websocket connection, you should increase the Max connections to Kubernetes API", e);
            }
            if (!hasStarted) {
                closeWatch(watch);
                throw new IOException("Timed out waiting for websocket connection. " + "You should increase the value of system property " + WEBSOCKET_CONNECTION_TIMEOUT_SYSTEM_PROPERTY + " currently set at " + WEBSOCKET_CONNECTION_TIMEOUT + " seconds");
            }
            try {
                // Not fully satisfied with this solution because it can delay the execution
                if (finished.await(COMMAND_FINISHED_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                    launcher.getListener().error("Process exited immediately after creation. See output below%n%s", stdout.toString(StandardCharsets.UTF_8.name()));
                    throw new AbortException("Process exited immediately after creation. Check logs above for more details.");
                }
                toggleStdout.disable();
                OutputStream stdin = watch.getInput();
                PrintStream in = new PrintStream(stdin, true, StandardCharsets.UTF_8.name());
                if (!launcher.isUnix()) {
                    in.print("@echo off");
                    in.print(newLine(true));
                }
                if (pwd != null) {
                    // We need to get into the project workspace.
                    // The workspace is not known in advance, so we have to execute a cd command.
                    in.print(String.format("cd \"%s\"", pwd));
                    in.print(newLine(!launcher.isUnix()));
                }
                EnvVars envVars = new EnvVars();
                // get global vars here, run the export first as they'll get overwritten.
                if (globalVars != null) {
                    envVars.overrideAll(globalVars);
                }
                if (rcEnvVars != null) {
                    envVars.overrideAll(rcEnvVars);
                }
                if (environmentExpander != null) {
                    environmentExpander.expand(envVars);
                }
                // setup specific command envs passed into cmd
                if (cmdEnvs != null) {
                    for (String cmdEnv : cmdEnvs) {
                        envVars.addLine(cmdEnv);
                    }
                }
                LOGGER.log(Level.FINEST, "Launching with env vars: {0}", envVars.toString());
                setupEnvironmentVariable(envVars, in, !launcher.isUnix());
                if (!launcher.isUnix() && toggleOutputForCaller != null) {
                    // Windows welcome message should not be sent to the caller as it is a side-effect of calling the wrapping cmd.exe
                    // Microsoft Windows [Version 10.0.17763.2686]
                    // (c) 2018 Microsoft Corporation. All rights reserved.
                    // 
                    // C:\>
                    stream.flush();
                    long beginning = System.currentTimeMillis();
                    // watch for the prompt character
                    while (!dryRunCaller.toString(StandardCharsets.UTF_8.name()).contains(">")) {
                        Thread.sleep(100);
                    }
                    LOGGER.log(Level.FINEST, "Windows prompt printed after " + (System.currentTimeMillis() - beginning) + " ms");
                }
                // We don't need to capture output anymore
                if (toggleDryRunCaller != null) {
                    toggleDryRunCaller.disable();
                }
                // Clear any captured bytes
                if (dryRunCaller != null) {
                    dryRunCaller.reset();
                }
                if (toggleOutputForCaller != null) {
                    toggleOutputForCaller.enable();
                }
                doExec(in, !launcher.isUnix(), printStream, masks, commands);
                LOGGER.log(Level.INFO, "Created process inside pod: [" + getPodName() + "], container: [" + containerName + "]" + "[" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startMethod) + " ms]");
                ContainerExecProc proc = new ContainerExecProc(watch, alive, finished, stdin, error);
                closables.add(proc);
                return proc;
            } catch (InterruptedException ie) {
                throw new InterruptedIOException(ie.getMessage());
            } catch (Exception e) {
                closeWatch(watch);
                throw e;
            }
        }

        @Override
        public void kill(Map<String, String> modelEnvVars) throws IOException, InterruptedException {
            getListener().getLogger().println("Killing processes");
            String cookie = modelEnvVars.get(COOKIE_VAR);
            int exitCode = doLaunch(true, null, null, null, null, // TODO Windows
            "sh", "-c", "kill \\`grep -l '" + COOKIE_VAR + "=" + cookie + "' /proc/*/environ | cut -d / -f 3 \\`").join();
            getListener().getLogger().println("kill finished with exit code " + exitCode);
        }

        private void setupEnvironmentVariable(EnvVars vars, PrintStream out, boolean windows) throws IOException {
            for (Map.Entry<String, String> entry : vars.entrySet()) {
                // Check that key is bash compliant.
                if (entry.getKey().matches("[a-zA-Z_][a-zA-Z0-9_]*")) {
                    out.print(String.format(windows ? "set %s=%s" : "export %s='%s'", entry.getKey(), windows ? entry.getValue() : entry.getValue().replace("'", "'\\''")));
                    out.print(newLine(windows));
                }
            }
        }
    };
}
Also used : Arrays(java.util.Arrays) ByteArrayOutputStream(java.io.ByteArrayOutputStream) Container(io.fabric8.kubernetes.api.model.Container) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) InterruptedIOException(java.io.InterruptedIOException) EnvironmentExpander(org.jenkinsci.plugins.workflow.steps.EnvironmentExpander) Level(java.util.logging.Level) ArrayList(java.util.ArrayList) Computer(hudson.model.Computer) HashSet(java.util.HashSet) RejectedExecutionException(java.util.concurrent.RejectedExecutionException) Matcher(java.util.regex.Matcher) Map(java.util.Map) Response(okhttp3.Response) Execable(io.fabric8.kubernetes.client.dsl.Execable) EnvVars(hudson.EnvVars) Proc(hudson.Proc) ExecListener(io.fabric8.kubernetes.client.dsl.ExecListener) TeeOutputStream(org.apache.commons.io.output.TeeOutputStream) OutputStream(java.io.OutputStream) PrintStream(java.io.PrintStream) KubernetesClientException(io.fabric8.kubernetes.client.KubernetesClientException) AbortException(hudson.AbortException) Set(java.util.Set) FilterOutputStream(java.io.FilterOutputStream) IOException(java.io.IOException) Launcher(hudson.Launcher) ExecWatch(io.fabric8.kubernetes.client.dsl.ExecWatch) EXIT(org.csanchez.jenkins.plugins.kubernetes.pipeline.Constants.EXIT) Logger(java.util.logging.Logger) StandardCharsets(java.nio.charset.StandardCharsets) Serializable(java.io.Serializable) Node(hudson.model.Node) TimeUnit(java.util.concurrent.TimeUnit) KubernetesSlave(org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave) CountDownLatch(java.util.concurrent.CountDownLatch) AtomicLong(java.util.concurrent.atomic.AtomicLong) List(java.util.List) ContainerTemplate(org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate) FilePath(hudson.FilePath) Closeable(java.io.Closeable) NullOutputStream(org.apache.commons.io.output.NullOutputStream) LauncherDecorator(hudson.LauncherDecorator) KubernetesClient(io.fabric8.kubernetes.client.KubernetesClient) Optional(java.util.Optional) UnsupportedEncodingException(java.io.UnsupportedEncodingException) SuppressFBWarnings(edu.umd.cs.findbugs.annotations.SuppressFBWarnings) InterruptedIOException(java.io.InterruptedIOException) TeeOutputStream(org.apache.commons.io.output.TeeOutputStream) ByteArrayOutputStream(java.io.ByteArrayOutputStream) TeeOutputStream(org.apache.commons.io.output.TeeOutputStream) OutputStream(java.io.OutputStream) FilterOutputStream(java.io.FilterOutputStream) NullOutputStream(org.apache.commons.io.output.NullOutputStream) ArrayList(java.util.ArrayList) Container(io.fabric8.kubernetes.api.model.Container) Computer(hudson.model.Computer) ExecListener(io.fabric8.kubernetes.client.dsl.ExecListener) HashSet(java.util.HashSet) FilePath(hudson.FilePath) PrintStream(java.io.PrintStream) ExecWatch(io.fabric8.kubernetes.client.dsl.ExecWatch) InterruptedIOException(java.io.InterruptedIOException) IOException(java.io.IOException) ByteArrayOutputStream(java.io.ByteArrayOutputStream) CountDownLatch(java.util.concurrent.CountDownLatch) RejectedExecutionException(java.util.concurrent.RejectedExecutionException) InterruptedIOException(java.io.InterruptedIOException) RejectedExecutionException(java.util.concurrent.RejectedExecutionException) KubernetesClientException(io.fabric8.kubernetes.client.KubernetesClientException) AbortException(hudson.AbortException) IOException(java.io.IOException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) Response(okhttp3.Response) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) AtomicLong(java.util.concurrent.atomic.AtomicLong) EnvVars(hudson.EnvVars) KubernetesSlave(org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave) Map(java.util.Map) KubernetesClientException(io.fabric8.kubernetes.client.KubernetesClientException) AbortException(hudson.AbortException)

Aggregations

SuppressFBWarnings (edu.umd.cs.findbugs.annotations.SuppressFBWarnings)1 AbortException (hudson.AbortException)1 EnvVars (hudson.EnvVars)1 FilePath (hudson.FilePath)1 Launcher (hudson.Launcher)1 LauncherDecorator (hudson.LauncherDecorator)1 Proc (hudson.Proc)1 Computer (hudson.model.Computer)1 Node (hudson.model.Node)1 Container (io.fabric8.kubernetes.api.model.Container)1 KubernetesClient (io.fabric8.kubernetes.client.KubernetesClient)1 KubernetesClientException (io.fabric8.kubernetes.client.KubernetesClientException)1 ExecListener (io.fabric8.kubernetes.client.dsl.ExecListener)1 ExecWatch (io.fabric8.kubernetes.client.dsl.ExecWatch)1 Execable (io.fabric8.kubernetes.client.dsl.Execable)1 ByteArrayOutputStream (java.io.ByteArrayOutputStream)1 Closeable (java.io.Closeable)1 FilterOutputStream (java.io.FilterOutputStream)1 IOException (java.io.IOException)1 InterruptedIOException (java.io.InterruptedIOException)1