/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.entity.chef; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.internal.ssh.process.ProcessTool; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory; import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; import org.apache.brooklyn.util.core.task.system.internal.SystemProcessTaskFactory; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.text.StringEscapes.BashStringEscapes; import com.google.common.base.Function; /** A factory which acts like {@link ProcessTaskFactory} with special options for knife. * Typical usage is to {@link #addKnifeParameters(String)}s for the knife command to be run. * You can also {@link #add(String...)} commands as needed; these will run *before* knife, * unless you addKnifeCommandHere(). * <p> * This impl will use sensible defaults, including {@link ConfigKey}s on the context entity, * for general knife config but not specific commands etc. It supports: * <li> {@link ChefConfig#KNIFE_EXECUTABLE} * <li> {@link ChefConfig#KNIFE_CONFIG_FILE} * <p> * (Other fields will typically be used by methods calling to this factory.) * */ // see e.g. http://docs.opscode.com/knife_bootstrap.html public class KnifeTaskFactory<RET> extends SystemProcessTaskFactory<KnifeTaskFactory<RET>, RET>{ private static String KNIFE_PLACEHOLDER = "<knife command goes here 1234>"; public final String taskName; protected String knifeExecutable; protected List<String> knifeParameters = new ArrayList<String>(); protected String knifeConfigFile; protected String knifeSetupCommands; protected Boolean throwOnCommonKnifeErrors; public KnifeTaskFactory(String taskName) { this.taskName = taskName; summary(taskName); // knife setup usually requires a login shell config.put(ProcessTool.PROP_LOGIN_SHELL, true); } @Override public List<Function<ProcessTaskWrapper<?>, Void>> getCompletionListeners() { MutableList<Function<ProcessTaskWrapper<?>, Void>> result = MutableList.copyOf(super.getCompletionListeners()); if (throwOnCommonKnifeErrors != Boolean.FALSE) insertKnifeCompletionListenerIntoCompletionListenersList(result); return result.asUnmodifiable(); } public KnifeTaskFactory<RET> notThrowingOnCommonKnifeErrors() { throwOnCommonKnifeErrors = false; return self(); } protected void insertKnifeCompletionListenerIntoCompletionListenersList(List<Function<ProcessTaskWrapper<?>, Void>> listeners) { // give a nice warning if chef/knife not set up correctly Function<ProcessTaskWrapper<?>, Void> propagateIfKnifeConfigFileMissing = new Function<ProcessTaskWrapper<?>, Void>() { public Void apply(@Nullable ProcessTaskWrapper<?> input) { if (input.getExitCode()!=0 && input.getStderr().indexOf("WARNING: No knife configuration file found")>=0) { String myConfig = knifeConfigFileOption(); if (Strings.isEmpty(myConfig)) throw new IllegalStateException("Config file for Chef knife must be specified in "+ChefConfig.KNIFE_CONFIG_FILE+" (or valid knife default set up)"); else throw new IllegalStateException("Error reading config file for Chef knife ("+myConfig+") -- does it exist?"); } return null; } }; listeners.add(propagateIfKnifeConfigFileMissing); } @Override public ProcessTaskWrapper<RET> newTask() { return new SystemProcessTaskWrapper("Knife"); } /** Inserts the knife command at the current place in the list. * Can be run multiple times. The knife command added at the end of the list * if this is not invoked (and it is the only command if nothing is {@link #add(String...)}ed. */ public KnifeTaskFactory<RET> addKnifeCommandToScript() { add(KNIFE_PLACEHOLDER); return self(); } @Override public List<String> getCommands() { MutableList<String> result = new MutableList<String>(); String setupCommands = knifeSetupCommands(); if (setupCommands != null && Strings.isNonBlank(setupCommands)) result.add(setupCommands); int numKnifes = 0; for (String c: super.getCommands()) { if (c==KNIFE_PLACEHOLDER) result.add(buildKnifeCommand(numKnifes++)); else result.add(c); } if (numKnifes==0) result.add(buildKnifeCommand(numKnifes++)); return result.asUnmodifiable(); } /** creates the command for running knife. * in some cases knife may be added multiple times, * and in that case the parameter here tells which time it is being added, * on a single run. */ protected String buildKnifeCommand(int knifeCommandIndex) { MutableList<String> words = new MutableList<String>(); words.add(knifeExecutable()); words.addAll(initialKnifeParameters()); words.addAll(knifeParameters()); String x = knifeConfigFileOption(); if (Strings.isNonBlank(x)) words.add(knifeConfigFileOption()); return Strings.join(words, " "); } /** allows a way for subclasses to build up parameters at the start */ protected List<String> initialKnifeParameters() { return new MutableList<String>(); } @Nullable /** callers should allow this to be null so task can be used outside of an entity */ protected Entity entity() { return BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); } protected <T> T entityConfig(ConfigKey<T> key) { Entity entity = entity(); if (entity!=null) return entity.getConfig(key); return null; } public KnifeTaskFactory<RET> knifeExecutable(String knifeExecutable) { this.knifeExecutable = knifeExecutable; return this; } protected String knifeExecutable() { if (knifeExecutable!=null) return knifeExecutable; String knifeExecFromConfig = entityConfig(ChefConfig.KNIFE_EXECUTABLE); if (knifeExecFromConfig!=null) return BashStringEscapes.wrapBash(knifeExecFromConfig); // assume on the path, if executable not set return "knife"; } protected List<String> knifeParameters() { return knifeParameters; } public KnifeTaskFactory<RET> knifeAddParameters(String word1, String ...words) { knifeParameters.add(word1); for (String w: words) knifeParameters.add(w); return self(); } public KnifeTaskFactory<RET> knifeConfigFile(String knifeConfigFile) { this.knifeConfigFile = knifeConfigFile; return self(); } @Nullable protected String knifeConfigFileOption() { if (knifeConfigFile!=null) return "-c "+knifeConfigFile; String knifeConfigFileFromConfig = entityConfig(ChefConfig.KNIFE_CONFIG_FILE); if (knifeConfigFileFromConfig!=null) return "-c "+BashStringEscapes.wrapBash(knifeConfigFileFromConfig); // if not supplied will use global config return null; } public KnifeTaskFactory<RET> knifeSetupCommands(String knifeSetupCommands) { this.knifeSetupCommands = knifeSetupCommands; return self(); } @Nullable protected String knifeSetupCommands() { if (knifeSetupCommands!=null) return knifeSetupCommands; String knifeSetupCommandsFromConfig = entityConfig(ChefConfig.KNIFE_SETUP_COMMANDS); if (knifeSetupCommandsFromConfig!=null) return knifeSetupCommandsFromConfig; // if not supplied will use global config return null; } @Override public <T2> KnifeTaskFactory<T2> returning(ScriptReturnType type) { return (KnifeTaskFactory<T2>) super.<T2>returning(type); } @Override public <RET2> KnifeTaskFactory<RET2> returning(Function<ProcessTaskWrapper<?>, RET2> resultTransformation) { return (KnifeTaskFactory<RET2>) super.returning(resultTransformation); } @Override public KnifeTaskFactory<Boolean> returningIsExitCodeZero() { return (KnifeTaskFactory<Boolean>) super.returningIsExitCodeZero(); } @Override public KnifeTaskFactory<String> requiringZeroAndReturningStdout() { return (KnifeTaskFactory<String>) super.requiringZeroAndReturningStdout(); } }