Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public static String[] getValues() {
public static final String ANSIBLE_INVENTORY_INLINE = "ansible-inventory-inline";
public static final String ANSIBLE_INVENTORY = "ansible-inventory";
public static final String ANSIBLE_GENERATE_INVENTORY = "ansible-generate-inventory";
public static final String ANSIBLE_GENERATE_INVENTORY_NODES_AUTH = "ansible-generate-inventory-nodes-auth";
public static final String ANSIBLE_MODULE = "ansible-module";
public static final String ANSIBLE_MODULE_ARGS = "ansible-module-args";
public static final String ANSIBLE_DEBUG = "ansible-debug";
Expand Down Expand Up @@ -233,6 +234,13 @@ public static String[] getValues() {
.description("Generate Ansible inventory from Rundeck nodes.")
.build();

static final Property GENERATE_INVENTORY_NODES_AUTH = PropertyBuilder.builder()
.booleanType(ANSIBLE_GENERATE_INVENTORY_NODES_AUTH)
.required(false)
.title("Generate inventory, pass node authentication from rundeck nodes")
.description("Pass authentication from rundeck nodes.")
.build();

public static Property EXECUTABLE_PROP = PropertyUtil.freeSelect(
ANSIBLE_EXECUTABLE,
"Executable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ public static AnsibleRunner buildAnsibleRunner(AnsibleRunnerContextBuilder conte
ansibleRunnerBuilder.inventory(inventory);
}

Boolean generateInventoryNodeAuth = contextBuilder.generateInventoryNodesAuth();
if(generateInventoryNodeAuth != null && generateInventoryNodeAuth){
Map<String, Map<String, String>> nodesAuth = contextBuilder.getNodesAuthenticationMap();
if (nodesAuth != null && !nodesAuth.isEmpty()) {
ansibleRunnerBuilder.addNodeAuthToInventory(true);
ansibleRunnerBuilder.nodesAuthentication(nodesAuth);
}
}

String limit = contextBuilder.getLimit();
if (limit != null) {
ansibleRunnerBuilder.limits(limit);
Expand Down Expand Up @@ -291,9 +300,15 @@ public static AnsibleRunner buildAnsibleRunner(AnsibleRunnerContextBuilder conte
File tempSshVarsFile ;
File tempBecameVarsFile ;
File vaultPromptFile;
File tempNodeAuthFile;
File groupVarsDir;

String customTmpDirPath;

@Builder.Default
Boolean addNodeAuthToInventory = false;
Map<String, Map<String, String>> nodesAuthentication;

public void deleteTempDirectory(Path tempDirectory) throws IOException {
Files.walkFileTree(tempDirectory, new SimpleFileVisitor<Path>() {
@Override
Expand Down Expand Up @@ -396,6 +411,63 @@ public int run() throws Exception {
if (inventory != null && !inventory.isEmpty()) {
procArgs.add("-i");
procArgs.add(inventory);

if(addNodeAuthToInventory != null && addNodeAuthToInventory && nodesAuthentication != null && !nodesAuthentication.isEmpty()) {
Map<String, String> hostUsers = new LinkedHashMap<>();
Map<String, String> hostPasswords = new LinkedHashMap<>();
nodesAuthentication.forEach((nodeName, authValues) -> {
String user = authValues.get("ansible_user");
String password = authValues.get("ansible_password");
if (user != null) {
hostUsers.put(nodeName, user);
}
if (password != null) {
String encryptedPassword = password;
if (useAnsibleVault) {
try {
encryptedPassword = encryptExtraVarsKey(password);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
hostPasswords.put(nodeName, encryptedPassword);
}
});
// Build YAML structure
Map<String, Object> yamlData = new LinkedHashMap<>();
yamlData.put("host_passwords", hostPasswords);
yamlData.put("host_users", hostUsers);
try {
String yamlContent = mapperYaml.writeValueAsString(yamlData);

// Create group_vars directory structure
File inventoryFile = new File(inventory);
File inventoryParentDir = inventoryFile.getParentFile();

if (inventoryParentDir != null) {
groupVarsDir = new File(inventoryParentDir, "group_vars");

if (!groupVarsDir.exists()) {
if (!groupVarsDir.mkdirs()) {
throw new RuntimeException("Failed to create group_vars directory at: " + groupVarsDir.getAbsolutePath());
}
}

// Create all.yaml in group_vars directory
tempNodeAuthFile = new File(groupVarsDir, "all.yaml");
java.nio.file.Files.writeString(tempNodeAuthFile.toPath(), yamlContent);
tempNodeAuthFile.deleteOnExit();
groupVarsDir.deleteOnExit();
} else {
// Fallback to temp file if inventory has no parent directory
tempNodeAuthFile = AnsibleUtil.createTemporaryFile("group_vars", "all.yaml", yamlContent, customTmpDirPath);
}

} catch (IOException e) {
throw new RuntimeException("Failed to write all.yaml for node auth", e);
}
}

}

if (limits != null && limits.size() == 1) {
Expand Down Expand Up @@ -513,8 +585,6 @@ public int run() throws Exception {
//SET env variables
Map<String, String> processEnvironment = new HashMap<>();



if (configFile != null && !configFile.isEmpty()) {
if (debug) {
System.out.println(" ANSIBLE_CONFIG: " + configFile);
Expand Down Expand Up @@ -663,6 +733,16 @@ public int run() throws Exception {
vaultPromptFile.deleteOnExit();
}

if (tempNodeAuthFile != null && !tempNodeAuthFile.delete()) {
tempNodeAuthFile.deleteOnExit();
}

if (groupVarsDir != null && groupVarsDir.exists()) {
if (!groupVarsDir.delete()) {
groupVarsDir.deleteOnExit();
}
}

if (usingTempDirectory && !retainTempDirectory) {
deleteTempDirectory(baseDirectory);
}
Expand Down Expand Up @@ -824,4 +904,5 @@ public String encryptExtraVarsKey(String extraVars) throws Exception {
}


}
}

Original file line number Diff line number Diff line change
Expand Up @@ -193,24 +193,29 @@ public String getSshPassword() throws ConfigurationException {

if (null != storagePath) {
//look up storage value
Path path = PathUtil.asPath(storagePath);
try {
ResourceMeta contents = context.getStorageTree().getResource(path)
.getContents();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
contents.writeContent(byteArrayOutputStream);
return byteArrayOutputStream.toString();
} catch (StorageException | IOException e) {
throw new ConfigurationException("Failed to read the ssh password for " +
"storage path: " + storagePath + ": " + e.getMessage());
}
return getPasswordFromPath(storagePath);

} else {
return null;
}
}
}

public String getPasswordFromPath(String storagePath) throws ConfigurationException {
//look up storage value
Path path = PathUtil.asPath(storagePath);
try {
ResourceMeta contents = context.getStorageTree().getResource(path)
.getContents();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
contents.writeContent(byteArrayOutputStream);
return byteArrayOutputStream.toString();
} catch (StorageException | IOException e) {
throw new ConfigurationException("Failed to read the ssh password for " +
"storage path: " + storagePath + ": " + e.getMessage());
}
}

public Integer getSSHTimeout() throws ConfigurationException {
Integer timeout = null;
final String stimeout = PropertyResolver.resolveProperty(
Expand Down Expand Up @@ -249,6 +254,23 @@ public String getSshUser() {
return user;
}

public String getSshNodeUser(INodeEntry node) {
final String user;
user = PropertyResolver.resolveProperty(
AnsibleDescribable.ANSIBLE_SSH_USER,
null,
getFrameworkProject(),
getFramework(),
node,
getJobConf()
);

if (null != user && user.contains("${")) {
return DataContextUtils.replaceDataReferencesInString(user, getContext().getDataContext());
}
return user;
}


public AuthenticationType getSshAuthenticationType() {
String authType = PropertyResolver.resolveProperty(
Expand Down Expand Up @@ -880,4 +902,83 @@ public Map<String,String> getListOptions(){
}
return options;
}

public Map<String, Map<String, String>> getNodesAuthenticationMap(){

Map<String, Map<String, String>> authenticationNodesMap = new HashMap<>();

this.context.getNodes().forEach((node) -> {
String keyPath = PropertyResolver.resolveProperty(
AnsibleDescribable.ANSIBLE_SSH_PASSWORD_STORAGE_PATH,
null,
getFrameworkProject(),
getFramework(),
node,
getJobConf()
);

Map<String, String> auth = new HashMap<>();

if(null!=keyPath){
try {
auth.put("ansible_password",getPasswordFromPath(keyPath) );
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}

}
String userName = getSshNodeUser(node);

if(null!=userName){
auth.put("ansible_user",userName );
}

authenticationNodesMap.put(node.getNodename(), auth);
});

return authenticationNodesMap;
}


public List<String> getListNodesKeyPath(){

List<String> secretPaths = new ArrayList<>();

this.context.getNodes().forEach((node) -> {
String keyPath = PropertyResolver.resolveProperty(
AnsibleDescribable.ANSIBLE_SSH_PASSWORD_STORAGE_PATH,
null,
getFrameworkProject(),
getFramework(),
node,
getJobConf()
);

if(null!=keyPath){
if(!secretPaths.contains(keyPath)){
secretPaths.add(keyPath);
}
}
});

return secretPaths;
}


public Boolean generateInventoryNodesAuth() {
Boolean generateInventoryNodesAuth = null;
String sgenerateInventoryNodesAuth = PropertyResolver.resolveProperty(
AnsibleDescribable.ANSIBLE_GENERATE_INVENTORY_NODES_AUTH,
null,
getFrameworkProject(),
getFramework(),
getNode(),
getJobConf()
);

if (null != sgenerateInventoryNodesAuth) {
generateInventoryNodesAuth = Boolean.parseBoolean(sgenerateInventoryNodesAuth);
}
return generateInventoryNodesAuth;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class AnsibleNodeExecutor implements NodeExecutor, AnsibleDescribable, Pr
builder.property(WINDOWS_EXECUTABLE_PROP);
builder.property(CONFIG_FILE_PATH);
builder.property(GENERATE_INVENTORY_PROP);
builder.property(GENERATE_INVENTORY_NODES_AUTH);
builder.property(SSH_AUTH_TYPE_PROP);
builder.property(SSH_USER_PROP);
builder.property(SSH_PASSWORD_STORAGE_PROP);
Expand Down Expand Up @@ -63,6 +64,8 @@ public class AnsibleNodeExecutor implements NodeExecutor, AnsibleDescribable, Pr
builder.frameworkMapping(ANSIBLE_CONFIG_FILE_PATH,FWK_PROP_PREFIX + ANSIBLE_CONFIG_FILE_PATH);
builder.mapping(ANSIBLE_GENERATE_INVENTORY,PROJ_PROP_PREFIX + ANSIBLE_GENERATE_INVENTORY);
builder.frameworkMapping(ANSIBLE_GENERATE_INVENTORY,FWK_PROP_PREFIX + ANSIBLE_GENERATE_INVENTORY);
builder.mapping(ANSIBLE_GENERATE_INVENTORY_NODES_AUTH,PROJ_PROP_PREFIX + ANSIBLE_GENERATE_INVENTORY_NODES_AUTH);
builder.frameworkMapping(ANSIBLE_GENERATE_INVENTORY_NODES_AUTH,FWK_PROP_PREFIX + ANSIBLE_GENERATE_INVENTORY_NODES_AUTH);
builder.mapping(ANSIBLE_SSH_AUTH_TYPE,PROJ_PROP_PREFIX + ANSIBLE_SSH_AUTH_TYPE);
builder.frameworkMapping(ANSIBLE_SSH_AUTH_TYPE,FWK_PROP_PREFIX + ANSIBLE_SSH_AUTH_TYPE);
builder.mapping(ANSIBLE_SSH_USER,PROJ_PROP_PREFIX + ANSIBLE_SSH_USER);
Expand Down Expand Up @@ -197,7 +200,13 @@ public List<String> listSecretsPath(ExecutionContext context, INodeEntry node) {
jobConf.put(AnsibleDescribable.ANSIBLE_LIMIT,node.getNodename());
AnsibleRunnerContextBuilder builder = new AnsibleRunnerContextBuilder(node, context, context.getFramework(), jobConf);

return AnsibleUtil.getSecretsPath(builder);
List<String> secretPaths = AnsibleUtil.getSecretsPath(builder);
List<String> secretPathsNodes = builder.getListNodesKeyPath();

if(secretPathsNodes != null && !secretPathsNodes.isEmpty()){
secretPaths.addAll(secretPathsNodes);
}
return secretPaths;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public class AnsiblePlaybookInlineWorkflowNodeStep implements NodeStepPlugin, An
builder.property(PLAYBOOK_INLINE_PROP);
builder.property(EXTRA_VARS_PROP);
builder.property(CONFIG_ENCRYPT_EXTRA_VARS);
builder.property(GENERATE_INVENTORY_PROP);
builder.property(GENERATE_INVENTORY_NODES_AUTH);
builder.property(VAULT_KEY_FILE_PROP);
builder.property(VAULT_KEY_STORAGE_PROP);
builder.property(EXTRA_ATTRS_PROP);
Expand Down Expand Up @@ -121,7 +123,13 @@ public void executeNodeStep(
@Override
public List<String> listSecretsPathWorkflowNodeStep(ExecutionContext context, INodeEntry node, Map<String, Object> configuration) {
AnsibleRunnerContextBuilder builder = new AnsibleRunnerContextBuilder(node, context, context.getFramework(), configuration);
return AnsibleUtil.getSecretsPath(builder);
List<String> secretPaths = AnsibleUtil.getSecretsPath(builder);
List<String> secretPathsNodes = builder.getListNodesKeyPath();

if(secretPathsNodes != null && !secretPathsNodes.isEmpty()){
secretPaths.addAll(secretPathsNodes);
}
return secretPaths;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public Description getDescription() {
@Override
public List<String> listSecretsPathWorkflowStep(ExecutionContext context, Map<String, Object> configuration) {
AnsibleRunnerContextBuilder builder = new AnsibleRunnerContextBuilder(context, context.getFramework(), context.getNodes(), configuration);
return AnsibleUtil.getSecretsPath(builder);
return AnsibleUtil.getSecretsPathWorkflowSteps(builder);
}
@Override
public Map<String, String> getRuntimeProperties(ExecutionContext context) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/groovy/com/rundeck/plugins/ansible/util/AnsibleUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ public static List<String> getSecretsPath(AnsibleRunnerContextBuilder builder){

}

public static List<String> getSecretsPathWorkflowSteps(AnsibleRunnerContextBuilder builder){
List<String> secretPaths = getSecretsPath(builder);
List<String> secretPathsNodes =builder.getListNodesKeyPath();

if(secretPathsNodes!=null && !secretPathsNodes.isEmpty()){
secretPaths.addAll(secretPathsNodes);
}
return secretPaths;
}

public static Map<String, String> getRuntimeProperties(ExecutionContext context, String propertyPrefix) {
Map<String, String> properties = null;

Expand Down
Loading