-
Notifications
You must be signed in to change notification settings - Fork 277
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial support for parsing a provision file.
- Loading branch information
Showing
4 changed files
with
330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package qz.installer.provision; | ||
|
||
import org.apache.commons.io.FileUtils; | ||
import org.apache.commons.io.IOUtils; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.codehaus.jettison.json.JSONArray; | ||
import org.codehaus.jettison.json.JSONException; | ||
import org.codehaus.jettison.json.JSONObject; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.ArrayList; | ||
|
||
import static qz.installer.provision.Step.*; | ||
|
||
public class Provisioner { | ||
protected static final Logger log = LogManager.getLogger(Provisioner.class); | ||
ArrayList<Step> steps; | ||
|
||
public Provisioner(InputStream in) throws IOException, JSONException { | ||
this(IOUtils.toString(in, StandardCharsets.UTF_8)); | ||
} | ||
|
||
public Provisioner(File jsonFile) throws IOException, JSONException { | ||
this(FileUtils.readFileToString(jsonFile, StandardCharsets.UTF_8)); | ||
} | ||
|
||
public Provisioner(String jsonData) throws JSONException { | ||
steps = parse(jsonData); | ||
} | ||
|
||
public void invoke() { | ||
for(Step step : steps) { | ||
try { | ||
step.invoke(); | ||
} catch(Exception e) { | ||
log.error("Provisioning step failed: {}", step, e); | ||
} | ||
} | ||
} | ||
|
||
private ArrayList<Step> parse(String jsonData) throws JSONException { | ||
JSONArray jsonArray = new JSONArray(jsonData); | ||
|
||
ArrayList<Step> steps = new ArrayList<>(); | ||
for(int i = 0; i < jsonArray.length(); i++) { | ||
JSONObject step = jsonArray.getJSONObject(i); | ||
|
||
Type type = Type.parse(step.optString("type", null)); | ||
String data = step.optString("data", null); | ||
|
||
Os os = null; | ||
if(step.has("os")) { | ||
String osString = step.optString("os", "all"); | ||
os = Os.parse(osString); | ||
if(os == null) { | ||
log.warn("Os provided \"{}\" could not be parsed", osString); | ||
} | ||
} | ||
|
||
Phase phase = null; | ||
if(step.has("phase")) { | ||
String phaseString = step.optString("phase", null); | ||
phase = Phase.parse(phaseString); | ||
if(phase == null) { | ||
log.warn("Phase provided \"{}\" could not be parsed", phaseString); | ||
} | ||
} | ||
|
||
try { | ||
steps.add(new Step(type, os, phase, data)); | ||
} catch(Exception e) { | ||
log.warn("Unable to add Step{}", step, e); | ||
} | ||
} | ||
return steps; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
package qz.installer.provision; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import qz.common.Constants; | ||
import qz.utils.FileUtilities; | ||
import qz.utils.ShellUtilities; | ||
import qz.utils.SystemUtilities; | ||
|
||
import java.io.File; | ||
import java.util.ArrayList; | ||
import java.util.EnumSet; | ||
|
||
import static qz.utils.ArgParser.ExitStatus.*; | ||
|
||
public class Step { | ||
public enum Type { | ||
SCRIPT, | ||
CERT, | ||
PROPERTY, | ||
PREFERENCE; | ||
|
||
public static Type parse(String input) { | ||
return Step.parse(Type.class, input); | ||
} | ||
} | ||
|
||
public enum Os { | ||
WINDOWS, | ||
MAC, | ||
LINUX, | ||
ALL; | ||
|
||
public static Os parse(String input) { | ||
// Handle ALL="*" | ||
if(input != null && input.equals("*")) { | ||
return ALL; | ||
} | ||
return Step.parse(Os.class, input); | ||
} | ||
} | ||
|
||
public enum Phase { | ||
INSTALL, | ||
STARTUP; | ||
|
||
public static Phase parse(String input) { | ||
return Step.parse(Phase.class, input); | ||
} | ||
} | ||
|
||
public enum Script { | ||
PS1, | ||
BAT, | ||
SH, | ||
PY, | ||
RB; | ||
|
||
public static Script parse(String input) { | ||
if(input != null) { | ||
if(input.contains(".")) { | ||
String extension = input.substring(input.lastIndexOf(".") + 1); | ||
return Step.parse(Script.class, input); | ||
} else { | ||
// If no file extension, assume a shell script | ||
return SH; | ||
} | ||
|
||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Returns the interpreter command (and if needed, arguments) to invoke the script file | ||
* | ||
* An empty array will fall back to Unix "shebang" notation, e.g. #!/usr/bin/python3 | ||
* which will allow the OS to select the correct interpreter for the given file | ||
* | ||
* No special attention is given to "shebang", behavior may differ between OSs | ||
*/ | ||
public ArrayList<String> getInterpreter(Os os) { | ||
ArrayList<String> interpreter = new ArrayList<>(); | ||
switch(this) { | ||
case PS1: | ||
interpreter.add(os == Os.WINDOWS ? "powershell.exe" : "pwsh"); | ||
interpreter.add("-File"); | ||
break; | ||
case PY: | ||
interpreter.add(os == Os.WINDOWS ? "python3.exe" : "python3"); | ||
break; | ||
case BAT: | ||
interpreter.add(os == Os.WINDOWS ? "cmd.exe" : "wineconsole"); | ||
break; | ||
case RB: | ||
interpreter.add(os == Os.WINDOWS ? "ruby.exe" : "ruby"); | ||
break; | ||
case SH: | ||
default: | ||
} | ||
return interpreter; | ||
} | ||
} | ||
|
||
|
||
protected static final Logger log = LogManager.getLogger(Step.class); | ||
|
||
private Type type; | ||
private Os os; | ||
private Phase phase; | ||
private String data; | ||
|
||
public Step(Type type, Os os, Phase phase, String data) { | ||
this.type = type; | ||
this.os = os; | ||
this.phase = phase; | ||
this.data = data; | ||
|
||
// Curate inputs | ||
if(type == null || data == null) { | ||
throw new UnsupportedOperationException("Type and Data cannot be null"); | ||
} | ||
|
||
// Handle nulls | ||
if(phase == null) { | ||
this.phase = type == Type.PREFERENCE ? Phase.STARTUP : Phase.INSTALL; | ||
log.info("Phase is null, defaulting to {} based on Type {}", this.phase, this.type); | ||
} | ||
if(os == null) { | ||
this.os = Os.ALL; | ||
log.info("Os {} is null, defaulting to ", this.os); | ||
} | ||
|
||
// Override conflicts | ||
if (type == Type.PROPERTY || type == Type.CERT) { | ||
if (phase == Phase.STARTUP) { | ||
this.phase = Phase.INSTALL; | ||
log.warn("Phase {} is unsupported for {}, defaulting to ", phase, type, this.phase); | ||
} | ||
} | ||
if (type == Type.PREFERENCE) { | ||
if (phase == Phase.INSTALL) { | ||
this.phase = Phase.STARTUP; | ||
log.warn("Phase {} is unsupported for {}, defaulting to ", phase, type, this.phase); | ||
} | ||
} | ||
} | ||
|
||
public boolean invoke() throws Exception { | ||
switch(type) { | ||
case CERT: | ||
File cert = normalizePath(this.data); | ||
return FileUtilities.addToCertList(Constants.ALLOW_FILE, cert) == SUCCESS; | ||
case SCRIPT: | ||
File file = normalizePath(this.data); | ||
Script engine = Script.parse(this.data); | ||
ArrayList<String> command = engine.getInterpreter(this.os); | ||
command.add(file.toString()); | ||
return ShellUtilities.execute(command.toArray(new String[command.size()])); | ||
case PROPERTY: | ||
// TODO: How should we pass this parameter in? | ||
case PREFERENCE: | ||
// TODO: Calculate this | ||
default: | ||
throw new UnsupportedOperationException("Type " + type + " is not yet supported."); | ||
} | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "Step{" + | ||
"type=" + type + | ||
", os=" + os + | ||
", phase=" + phase + | ||
", data='" + data + '\'' + | ||
'}'; | ||
} | ||
|
||
/** | ||
* Basic enum parser | ||
*/ | ||
private static <T extends Enum<T>> T parse(Class<T> clazz, String s) { | ||
for(T en : EnumSet.allOf(clazz)){ | ||
if(en.name().equalsIgnoreCase(s)){ | ||
return en; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private File normalizePath(String relative) { | ||
if(this.type == Type.CERT || this.type == Type.SCRIPT) { | ||
File normalized = SystemUtilities.getJarParentPath(".").resolve(relative).toFile(); | ||
log.info("Normalizing path {} to {}", relative, normalized); | ||
return normalized; | ||
} | ||
throw new UnsupportedOperationException("Path conversion for Type " + this.type + " not yet supported"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package qz.installer.provision; | ||
|
||
import org.codehaus.jettison.json.JSONException; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
public class ProvisionerTests { | ||
|
||
public static void main(String ... args) throws JSONException, IOException { | ||
InputStream in = ProvisionerTests.class.getResourceAsStream("resources/provision.json"); | ||
|
||
// Parse the JSON | ||
Provisioner provisioner = new Provisioner(in); | ||
|
||
// Invoke all parsed steps | ||
provisioner.invoke(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
|
||
[ | ||
{ | ||
"type": "script", | ||
"os": "windows", | ||
"phase": "install", | ||
"data": "provision/script1.ps1" | ||
}, | ||
{ | ||
"type": "cert", | ||
"os": "*", | ||
// "phase": "install" is assumed | ||
"data": "provision/cert1.crt" | ||
}, | ||
{ | ||
"type": "property", | ||
"os": "*", | ||
// "phase": "install" is assumed | ||
"data": "wss.host=0.0.0.0" | ||
}, | ||
{ | ||
"type": "preference", | ||
"os": "*", | ||
// "phase": "startup" is assumed | ||
"data": "tray.notifications=true" | ||
}, | ||
{ // Deliberately throws error | ||
"os": "*", | ||
// "phase": "startup" is assumed | ||
"data": "tray.notifications=true" | ||
} | ||
] |