001/* 002 * Copyright 2025 devteam@scivics-lab.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, 011 * software distributed under the License is distributed on an 012 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the 014 * specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package com.scivicslab.actoriac; 019 020import java.io.BufferedReader; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029/** 030 * Parser for Ansible inventory files in INI format. 031 * 032 * <p>This parser supports basic Ansible inventory file format with groups 033 * and variables. Example:</p> 034 * 035 * <pre> 036 * [webservers] 037 * web1.example.com 038 * web2.example.com 039 * 040 * [dbservers] 041 * db1.example.com 042 * 043 * [all:vars] 044 * ansible_user=admin 045 * ansible_port=22 046 * </pre> 047 * 048 * @author devteam@scivics-lab.com 049 */ 050public class InventoryParser { 051 052 /** 053 * Parses an Ansible inventory file. 054 * 055 * @param input the input stream of the inventory file 056 * @return the parsed inventory 057 * @throws IOException if reading the file fails 058 */ 059 public static Inventory parse(InputStream input) throws IOException { 060 Inventory inventory = new Inventory(); 061 062 try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) { 063 String currentGroup = null; 064 boolean inVarsSection = false; 065 Map<String, String> currentVars = new HashMap<>(); 066 067 String line; 068 while ((line = reader.readLine()) != null) { 069 line = line.trim(); 070 071 // Skip empty lines and comments 072 if (line.isEmpty() || line.startsWith("#") || line.startsWith(";")) { 073 continue; 074 } 075 076 // Check for group header 077 if (line.startsWith("[") && line.endsWith("]")) { 078 String groupDeclaration = line.substring(1, line.length() - 1); 079 080 // Check if it's a vars section 081 if (groupDeclaration.endsWith(":vars")) { 082 inVarsSection = true; 083 currentGroup = groupDeclaration.substring(0, groupDeclaration.length() - 5); 084 currentVars = new HashMap<>(); 085 } else { 086 inVarsSection = false; 087 currentGroup = groupDeclaration; 088 inventory.addGroup(currentGroup); 089 } 090 continue; 091 } 092 093 // Process content based on section type 094 if (inVarsSection) { 095 // Parse variable assignment 096 int equalsIndex = line.indexOf('='); 097 if (equalsIndex > 0) { 098 String key = line.substring(0, equalsIndex).trim(); 099 String value = line.substring(equalsIndex + 1).trim(); 100 currentVars.put(key, value); 101 102 // Apply vars to group 103 if ("all".equals(currentGroup)) { 104 inventory.addGlobalVar(key, value); 105 } else if (currentGroup != null) { 106 inventory.addGroupVar(currentGroup, key, value); 107 } 108 } 109 } else if (currentGroup != null) { 110 // Parse host line with optional variables 111 // Format: hostname [key=value key=value ...] 112 String[] tokens = line.split("\\s+"); 113 String hostname = tokens[0]; 114 inventory.addHost(currentGroup, hostname); 115 116 // Parse host-specific variables 117 for (int i = 1; i < tokens.length; i++) { 118 String token = tokens[i]; 119 int equalsIndex = token.indexOf('='); 120 if (equalsIndex > 0) { 121 String key = token.substring(0, equalsIndex).trim(); 122 String value = token.substring(equalsIndex + 1).trim(); 123 inventory.addHostVar(hostname, key, value); 124 } 125 } 126 } 127 } 128 } 129 130 return inventory; 131 } 132 133 /** 134 * Represents a parsed Ansible inventory. 135 */ 136 public static class Inventory { 137 private final Map<String, List<String>> groups = new HashMap<>(); 138 private final Map<String, String> globalVars = new HashMap<>(); 139 private final Map<String, Map<String, String>> groupVars = new HashMap<>(); 140 private final Map<String, Map<String, String>> hostVars = new HashMap<>(); 141 142 public void addGroup(String groupName) { 143 groups.putIfAbsent(groupName, new ArrayList<>()); 144 } 145 146 public void addHost(String groupName, String hostname) { 147 groups.computeIfAbsent(groupName, k -> new ArrayList<>()).add(hostname); 148 } 149 150 public void addGlobalVar(String key, String value) { 151 globalVars.put(key, value); 152 } 153 154 public void addGroupVar(String groupName, String key, String value) { 155 groupVars.computeIfAbsent(groupName, k -> new HashMap<>()).put(key, value); 156 } 157 158 public void addHostVar(String hostname, String key, String value) { 159 hostVars.computeIfAbsent(hostname, k -> new HashMap<>()).put(key, value); 160 } 161 162 public List<String> getHosts(String groupName) { 163 return groups.getOrDefault(groupName, new ArrayList<>()); 164 } 165 166 public Map<String, String> getGlobalVars() { 167 return new HashMap<>(globalVars); 168 } 169 170 public Map<String, String> getGroupVars(String groupName) { 171 return new HashMap<>(groupVars.getOrDefault(groupName, new HashMap<>())); 172 } 173 174 public Map<String, String> getHostVars(String hostname) { 175 return new HashMap<>(hostVars.getOrDefault(hostname, new HashMap<>())); 176 } 177 178 public Map<String, List<String>> getAllGroups() { 179 return new HashMap<>(groups); 180 } 181 } 182}