CLI wrapper for the freemarker template engine.
It is part of the broader hamlet devops framework.
Hamlet CLI wrapper for the freemarker template engine
License: GNU General Public License v3.0
CLI wrapper for the freemarker template engine.
It is part of the broader hamlet devops framework.
I can create a generated document which is treated like a JSON/freemarker object syntax during template generation but converted to yaml on export
JSON support only
Add a helper function to the freemarker wrapper which will convert from JSON to YAML. This function can be executed by freemarker when creating a file. This could follow on from the work in #23
Some tools ( Jenkins config as code ) only supports YAML based files for its definitions. In order to dynamically create some sections of the configuraton for a Jenkins master we need a way to generate a yaml file
Builds of the freemarker wrapper are built and available from the releases section of this repository
Builds are created by the developer who creates the change and are included in the repo as binary blobs
Add a Java build process as a github action which supports publishing releases to this repository. Builds and tests should run on all builds but releases are created on repo tags
This would allow for us to automate the build and test of the freemarker wrapper inline with CI/CD convetions
When a stop exception is raised within templates the stop reason is logged but the stack trace isn't included
When a stop event is raised the full stack trace is included in the log
When handling the stop exception only log the message provided if possible and don't include the stack trace log to the console
The stop method is used to stop processing at a user level and our convention is mostly to use the stop event as part of a fatal log message. This means the stack trace won't really provide information that is useful in debugging an issue
I've been mulling over how we move our CMDB processing forward. A few things I wanted to achieve
I'm proposing we introduce the idea of a logical CMDB file system, much like the unix file system, where each repo is "mounted" at a particular base path. Further, multiple repos can be mounted at the same path (hence the idea of layers, borrowing from the docker layered image). So I can have separate config and infrastructure repos but mount them at the same point in the tree. Thus whether it is one or more than one repo, the logical tree is the same.
For speed, I'm proposing that the Freemarker wrapper implement the CMDB file system. This will permit directory scanning and file reading/writing as part of normal template processing by exposing CMDB access functions via Freemarker. This will also permit reading AND writing to the CMDB, should we want to use the Freemarker engine to generate new files and save these to the CMDB.
While initially we will support physical file system backed CMDBs, the use of a file system abstraction means adding other sources (e.g. database backed cmdbs) can easily be accommodated in the future.
The .cmdb file will be extended to indicate any CMDBs that could be layered under the existing CMDB. To avoid overlaps, file lookups will be sought in the base CMDB first, then in the layered CMDBs in the order they are specified. (Overlaps can also be avoided by selection of non-overlapping base paths). Each layer entry will indicate the CMDB required by its name, and the base path in the CMDB file system at which it should be mounted. Paths can be absolute or relative. Relative paths are appended to the base path for the CMDB containing the layer. CMDBs will thus be nestable to arbitrary levels.
{
"Layers" : [
{
"Name" : "appointments",
"BasePath" : "/products/appointments"
}
]
}
Freemarker with have new command line switches to support assembling the CMDB file system.
The "-g" switch will support specification of the mapping of CMDB names to physical paths on the file system. Multiple -g switches instances will be supported. It will accept one or more values with two formats will be accepted,
The first form names and locates a single CMDB.
The second form identifies a directory whose subtree is scanned for .cmdb files, with the containing directory being treated as a CMDB whose name is that of the containing directory. It is expected that a top-level set of CMDB directories will be the most common layout used.
Note that where CMDB directories are clones of git repos, the CMDB name need not be the same as the repo name, with the management of repo cloning/updating being out of scope as far as Freemarker is concerned.
The "-c" switch will define the CMDBs to be processed. Multiple -c switch instances will be supported. It will accept one or more values, each of which is treated as a CMDB name. If the -c switch is not provided, all CMDBs found via the -g switch will be considered available for processing.
The "-b" switch will define the base CMDB considered to be at the root of the CMDB structure, so the CMDB with a path of "/". If the switch is not provided, all CMDBs considered for processing will be mounted at "/default".
The three switches allow a variety of arrangements;
for automation processing, the -g switch could specify the tenancy and product/environment level CMDBs, with the -b switch indicating the tenancy CMDB as the starting point.
for local processing, the -g switch could point to a local parent directory under which all repos for a tenant would be cloned. The -c switch would then indicate the product to be processed, and the -b default value used to select the tenant CMDB as the starting point.
Freemarker will offer several functions for manipulating the CMDB file system.
getCMDBTree
[#local candidates =
getCMDBTree(
path,
{
"Regex" : []
"IgnoreDotDirectories" : true,
"IgnoreDotFiles" : true,
"IncludeCMDBInformation" : false
}
) ]
where
Regex will be used for limiting file extensions or looking for specific elements in file paths e.g. .../asFile/...
. The entries of the array are considered to be ORed, mainly to keep the regular expressions simple.
The Java regex library has been used and searches need to be explicitly anchored if required. If no "^" anchoris provided, then the path must match the beginning of any string before it is considered for regex analysis, and "." is inserted before the regex. If a "^" anchor is provided, then the path is ignored. If no "$" anchor is present, the "." is postpended to any provided regex.
It should return a list of objects, one for each file found, modelled along the lines of .get_optional_template...
{
"File":
"Path" :
"Filename" :
"Extension" :
"Contents" :
"ContentsAsJSON" :
"Include" :
"CMDB" : {
"Name" : <name from -g/-c/-b>,
"BasePath" : <absolute path to cmdb in CMDB File System>,
"ContentsAsJSON": <contents of .cmdb file if present>
"File" : <full physical path to file>
}
}
File is the absolute path to the file.
Path, Filename and Extension (no dot) reflect the found file details. Filename includes the Extension but not the Path. Path does not include the filename.
Contents is a function that will return the file contents as a string in the same way as the current -v option works. If the contents can be converted to JSON, the ContentsAsJSON attribute will be present permitting direct access to the parsed result.
If the file is considered to be a Freemarker template (First line = [#ftl]), then there will also be an "Include" attribute present that works the same as its equivalent in .get_optional_template.
This function will form the basis of processing to assemble the in-memory structure to be processed via component templates.
getCMDBs
[#local cmdbs =
getCMDBs(
{
"ActiveOnly" : false
}
) ]
where
This function will return a list of the cmdbs known to the engine;
{
"Name" :
"CMDBPath" :
"FileSystemPath" :
"Base" :
"Active" :
"ParentCMDB" :
"ContentsAsJSON":
}
Name is the CMDB name.
CMDBPath is the mount point for the cmdb in the CMDB File system.
FileSystemPath is the physical location of the CMDB on disk.
Base is a boolean indicating whether the CMDB is considered the base CMDB. If this flag is true, then CMDBPath will always be "/".
Active is a boolean indicating whether the CMDB is to be considered for processing.
ParentCMDB is the name of the parent CMDB by which the CMDB was mounted. This means it was listed in an entry of the Layers array within the .cmdb file of the parent CMDB.
ContentsAsJSON is the contents of the .cmdb file if present.
We were just trying to use the IPAddress function that you added to the freemarker wrapper for codeontap a while ago. When we call it we seem to mostly get Stackoverflows.
Exception in thread "main" java.lang.StackOverflowError
at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:89)
at java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:72)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
I had a quick look on the developers site for the package we use and looks like a new version is out with some specific improvements to prefix handling
https://github.com/seancfoley/IPAddress/releases/tag/v4.3.0
Would you mind having a look at updating our wrapper to use this version or the version 5 ( latest ) if we can?
The address range we were trying get subnets for was pretty big ( "10.0.0.0/8". "20")
But I would think it wouldn’t be too much work for it to overflow
When using toCMDB I can specify if a JSON file should be pretty formatted or compressed
All file outputs are compressed JSON files on a single line
Add an option to specify Formatted on the toCMDB option. When this is specified the JSON output will be formatted with each key on a new line with 2 space indenting. Similar to the JQ standard output
echo '{ "this" : {"that": "other"}}' | jq
{
"this": {
"that": "other"
}
}
This helps with debugging and readability of the output provided by the engine
It should be possible to request directories only or files only when querying the cmdb
Currently this can only be done post search by checking the "IsDirectory" attribute. IT would be more efficient to perform this filtering pre regex checking.
IgnoreDirectories
option to getCMDBTree()
with a default of false which will not include directories when scanning the file systemIgnoreFiles
option to getCMDBTree()
with a default of false which will not include files when scanning the file systemSearching for directories only is used when determining the cmdb structure. Searching for files only is common when analysing the files within the top level directories of the cmdb structure.
A call to initialiseCMDBFileSystem()
following a call to initialisePluginFileSystem()
will cause subsequent calls to getPluginTree()
to fail even though the requested files are present.
The issue only occurs if there are CMDB directories to examine. This was triggered by use of the -g
switch.
Calls to getPluginTree()
should work correctly after a call to initialiseCMDBFileSystem()
.
Calls to getPluginTree()
return no files even though files are present on disk.
-g
setinitialiseCMDBFileSystem()
initialisePluginFileSystem()
getPluginTree()
for files known to be on diskBug is preventing progress on dynamic loading of the CMDB
Where a path parameter is provided to getCMDBTree() and getPluginTree() and a regex being processed is not start anchored (i.e. the path is included in the effective regex), the search can be constrained to cmdbs whose base bath is included in the provided path (or conversely excluded from analysis if the cmdb base path doesn't match the regex).
When combined with #20, this should result in a significant improvement in performance even in the face of a large number of active cmdbs. While searches for marker files may be expensive, the engine logic can maximise the length of the paths provided for specific details such as blueprints.
NOTE: An option to implement this that would actually apply to start anchored regexs would be to parse the regex expression and determine the "static" part at the front representing a fixed path. If this was used instead of the path, then it would potentially be more specific by including any static starting path components of the provided regex, as well as handling provided regex which as start anchored. If this approach is relatively straightforward, then it should be implemented in preference to analysis solely based on the path parameter. However, if this proves complex, base the implementation on the provided path as it is likely to result in 80% of the benefit.
I would like to experiment with the use of JSON schema for validation within the engine. Not sure what will be feasible but I need a method I can call to execute the schema engine.
No mechanism to check JSON against a JSON schema within the hamlet engine.
Add a validateJson(document, schema, options)
method that takes a json document and a json schema document and returns the results from whatever is the easiest schema engine to integrate. Thinking ideally a Java implementation would be best but a shell out to a CLI tool would work as well.
The options is a placeholder object like we use in our other apis to allow future tweaking of the process. For now it would be an empty object.
I know there will be issues with things like relative references but to start I think the schema will be isolated.
In terms of what is returned, I'd suggest an object so we can gather whatever output the engine produces e.g.
{
"Status" : <return code of engine - 0 = ok hopefully>,
"Logs" : [
"...line 1 of the schema output...",
"...line 2 of the schema output..."
],
"Result" : ...
}
The Result
would only be present if validation succeeds and should include whatever defaults have been applied by the engine
This might need a bit of refinement as we go but it will allow me to experiment to see how hard it would be to convert our existing schema validation to a more industry standard approach.
Currently this function returns a string. It should return a freemarker compatible array. If not already implemented, the "ActiveOnly" option flag should also be implemented.
Current [#ftl] must be on the first line of a file to be considered a template.
In order to align with the freemarker syntax, ignore leading whitespace when looking for the [#ftl] marker.
Template generation is failing for lambda components with the following error:
22:08:54 FreeMarker template error:
22:08:54 For "?eval" left-hand operand: Expected a string or something automatically convertible to string (number, date or boolean), but this has evaluated to an extended_hash (wrapper: f.c.AddConcatExpression$ConcatenatedHashEx):
22:08:54 ==> settings [in template "shared/inputseeders/shared/id.ftl" at line 374, column 64]
22:08:54
22:08:54 ----
22:08:54 FTL stack trace ("~" means nesting-related):
22:08:54 - Failed at: #local compositeSettings = (settings!... [in template "shared/inputseeders/shared/id.ftl" in function "shared_configseeder_cmdb" at line 374, column 5]
22:08:54 - Reached through: @internalRefreshInputState [in template "inputdata/inputsource.ftl" in function "getInputState" at line 448, column 9]
22:08:54 - Reached through: @writeStarterMessage writers=getComma... [in template "invokeEntrance.ftl" in function "invokeEntrancePass" at line 101, column 5]
22:08:54 ----
Please make random numbers available to gen3 templates. Suggest the approach described here - https://sourceforge.net/p/freemarker/discussion/2346/thread/f053d79c/
When installing hamlet using pip install hamlet / hamlet engine install-engine no extra underlying requirements need to be setup for the java based freemarker wrapper
You need to ensure that java is installed whenever running hamlet commands
Was Googling around and found that there are some tools to bundle a JRE with your Java application. That way it makes it an executable and you don't need to install a jre/jdk
This plugin for gradle seems to do it
https://badass-runtime-plugin.beryx.org/releases/latest/
And someone's covered the basics of it here
https://simply-how.com/custom-java-runtime
The idea with this is to simplify the hamlet install process and make the hamlet tool independent from the rest of the actions performed within CI/CD tasks. At the moment we use the OS version of java which means that we need to ensure that things like jenkins agents are using the same version of java that we support
To avoid case issues with file paths, add an option to control the case sensitivity of regex matches.
{
"CaseSensitive" : false
}
By default, case sensitivity should be turned off in the regex engine. However, if case sensitive matches are desired, this option can be adjusted.
Note that the default above is not the current default, but seems a more pragmatic default.
The hamlet engine can write to files that aren't part of a CMDB
The engine can only write to files which belong to a CMDB as part of the virtual file system
Add non CMDB methods for the methods defined in
https://github.com/hamlet-io/engine-core/tree/master/freemarker-wrapper/src/main/java/io/hamlet/freemarkerwrapper/files/methods
They would accept similar parameters to the existing CMDB versions of these methods but would be based on an absolute OS file path.
With hamlet-io/engine#1639 we found that writing logs to multiple destinations ( i.e. a console log and a JSON based log file ) was useful and provides an easy way to manage a machine readable log and a user readable log.
however these logs shouldn't be kept in a CMDB and are most likely going to be kept in tmp directories or the hamlet home directory, which aren't CMDBs
At present, when the physical file tree is walked, a glob pattern is applied to the filename. It is mainly used for detecting cmdbs via the presence of .cmdb files. For normal processing, a value of "*" is used.
Introduce a new option to permit the caller to override the default value.
{
"FilenameGlob" : "tenant.*" (default *)
}
This value will be provided to th findr when scanning for files.
At present, wildcards are inserted between the path and the regex if the regex isn't anchored at the start. As a result, where a fixed path is required, the path has to be bypassed but that means the search can't be constrained via the path. The same applies for end anchoring.
To permit more efficient searching using the path parameter, add the following options to getCMDBTree(0 and getPluginTree();
{
"AddStartingWildcard" : (default : true),
"AddEndingWildcard" : (default : true)
}
where
Both default to true to mirror current behaviour. These settings have no effect if the regex is start or end anchored respectively.
Getting an error when trying to create a deploy template using the latest hamlet docker image
master└─ $ hamlet deploy create-deployments -l solution -u mgmt-rds
[!] Error Running Command
script: /home/hamlet/.hamlet/engine/engines/_global/shim/executor-bash/cli/createTemplate.sh -e unitlist -d update -p aws -f cf -i composite -o /home/hamlet/cmdb/cache/query/0126b95d5097ada1bcbd8157b3e87e83
##################################################
stdout
##################################################
(Info) Generating outputs:
[*] entrance: unitlist | output: contract | subset: generationcontract
##################################################
stderr
##################################################
Jun 21, 2021 10:55:20 AM freemarker.log._JULLoggerFactory$JULLogger error
SEVERE: Error executing FreeMarker template
FreeMarker template error:
Function "getDomainObjects" only accepts 1 parameters, but got 2.
----
FTL stack trace ("~" means nesting-related):
- Failed at: #return certificateObject + {"Domains... [in template "common.ftl" in function "getCertificateObject" at line 730, column 5]
- Reached through: @(.vars[macro]) occurrence=occurrence... [in template "component.ftl" in function "invokeComponentMacro" at line 368, column 17]
- Reached through: @(.vars[macro]) level=level [in template "flow.ftl" in macro "processFlows" at line 16, column 13]
- Reached through: @processFlows level=level framework=D... [in template "shared/deploymentframeworks/default/output.ftl" in function "default_output_contract" at line 282, column 9]
- Reached through: @generateOutput deploymentFramework=g... [in template "shared/entrances/unitlist/entrance.ftl" in macro "shared_entrance_unitlist" at line 12, column 3]
- Reached through: @(.vars[macro]) [in template "entrance.ftl" in macro "invokeEntranceMacro" at line 122, column 9]
- Reached through: @invokeEntranceMacro type=entranceType [in template "invokeEntrance.ftl" in function "invokeEntrancePass" at line 107, column 5]
A template created successfully.
Reproduced it in two different CMDBs with more than one Stem defined in the domains.json
It should be possible to include multiple, semi-colon separated plugin dirs in GENERATION_PLUGIN_DIRS
.
The wrapper reports the first plugin as not present and ignores the remainder.
export GENERATION_PLUGIN_DIRS="/c/repos/hamlet/engine-plugin-aws;/c/repos/hamlet/engine-plugin-cmdb"
Exception in thread "main" java.io.FileNotFoundException: \c\repos\hamlet\engine-plugin-aws does not exist.
Developing new plugin under mingw
If appending to a non-existent file, an exception is thrown.
It would be preferrable if this followed shell redirection semantics and created the file is it doesn't exist.
Fix underlying Java calls
toCMDB
with Append : true
as an option for a non-existent fileSome AWS resources, WAF only support specific CIDR masks:
AWS WAF supports /8, /16, /24, and /32 IPv4 address ranges and /16, /24, /32, /48, /56, /64, and /128 IPv6 address ranges. For more information about CIDR notation, see the Wikipedia entry Classless Inter-Domain Routing.
Most other resources support standard CIDR masking (i.e. any mask). Instead of restricting the codeontap configuration to only support the WAF masks we would like to have a function which can convert a given CIDR mask into an array of /32 IP addresses. WAF supports 10,000 IP Addresses in a single rule so it shouldn't be an issue to expand most subnets.
At present, parse exceptions are being thrown if a file is not in JSON format eg. pem files.
Need to
There is a need to be able to scan parts of the provider configuration to determine which templates should then be dynamically loaded. To achieve this, add the getPluginTree() function.
It is modeled on the getCMDBTree, in that there is an "Plugin File System", where
[#local candidates =
getPluginTree(
path,
{
"Regex" : []
"IgnoreDotDirectories" : true,
"IgnoreDotFiles" : true,
"IncludePluginInformation" : true
}
) ]
where
The result uses the same format as getCMDBTree(), but the Plugin Information is included.
A getPlugins() function will be the equivalent of getCMDBs().
[#local plugins =
getPlugins(
{}
) ]
where
It will return an array of objects as follows
{
"Name": "",
"FileSystemPath": ""
}
Name is the plugin name detected based on the directory name.
FileSystemPath is the physical location of the plugin tree on disk.
As part of adding dynamic loading to gen3 and supporting the idea of plugins, we need the ability for the template loader to look in more than one place.
The default loader should still be based on a path of "/".
This will be GSGEN1.7.
At present, the potentially expensive process of establishing what cmdbs/plugins are in a file system and their relationships to each other is being done as part of every call to search the file system.
To make this more efficient, introduce an explicit initialisation routine for the CMDB and Plugin file systems that must be called before any call to search the file system;
initialiseCMDBFileSystem()
initialisePluginFilesystem()
These routines can be called at any time to re-initialise the file systems, though at present there isn't a use case for this.
Change class names, error message etc to use the term "hamlet" rather than "cot".
Existing names reflect the old frameowrk name.
As part of renaming the framework, the wrapper needs to be updated to reflect the new name.
If file contents can be parsed as YAML (triggered by a yml/yaml extension), convert yaml to json and offer via the ContentsAsJSON attribute
This will permit users to choose the format they prefer and even to mix and match.
In order to improve the performance of these functions, this issue introduces ways in which the call can provide assistance in avoiding unnecessary recursions into subdirectories.
Control Recursion Depth
{
"MinDepth" : n, (optional)
"MaxDepth" : m (optional)
}
These options control the range of subdirectories that should be checked for matching regex expressions. They are relative to the provided path, with the values starting at 1 for the directory pointed to by the provided path. Directories with a depth less than MinDepth are not considered in the search. The same goes for those whose depth is greater than MaxDepth.
A simple example where this options are useful is determining the directories at the top of a tree. By setting the MaxDepth to 1, only files/directories in the path directory will be considered for regex matching.
Control Recursion On Match
Where a search is attempting to find marker files, it is possible to ignore any subtrees below the found files. It is also possible to stop altogether on the first match, for instance when attempting to work out the directory structure of a cmdb.
{
"StopAfterFirstMatch" : false,
"IgnoreSubtreeAfterMatch" : false
}
where
Optimise Search Starting Point
Where a path is provided and a CMDB is found to be included under the path, searching of directories in the CMDB should start at the provided path, not the base path of the CMDB.
For example, if the base path for a CMDB is /products/widget
and the provided path is /products/widget/path/in/cmdb
then matching of files should start in the path/in/cmdb
relative path within the cmdb, not at /products/widget
.
A function can be called from within the freemarker engine to write output either the stdout or stderr console pipes.
We currently rely on output files generated by the engine to provide feedback to users and the bash executor reads the output file and then sends sections of it to stdout and stderr
Add functions to the freemarker wrapper similar to the toCMDB function which will right to the console
With the move to using the toCMDB function when a process fails within the engine that generates a fatal message or there is an issue with writing an output we won't be able to see the output or find results
Exceptions generated by the engine are standardised and handled by the engine itself.
Error messages in the engine are currently classified by the executor once the exception is thrown by the engine
Is it possible to create new exception freemarker method which allows for the setting the exit code, status code and along with an exception method. Currently the only exception method available is the freemarker builtin stop method, which only allows for string returns
With hamlet-io/executor-bash#168 and hamlet-io/architectural-decision-log#5 we've identified the need for more complex error handling across hamlet in general. The current implementation relies on the executor to classify messages generated by the engine.
This function technically returns whatever is provided via the -d
switches, which are used to provide the input to the template loader. They are combined into the layers of the plugin filesystem rather than being the plugins themselves. As an example, the plugin case has a single -d
switch but can provide multiple plugins to the engine.
Thus this function should be renamed to getPluginLayers()
.
Currently getPlugins()
actually returns the plugin filesystem layers.
Rename the function.
Ensure consistency of core functions.
Detection of a cmdb should be done on the basis of .cmdb
being a file (legacy) or a directory.
If it is a directory, it should contain a config.json
file with the current contents of the .cmdb
file.
getCMDBs should return the path to the root of the cmdb, and the Contents
should reflect the contents of either the .cmdb
file or the .cmdb/config.json
file.
.cmdb
is expected to be a file.
Code for either possibility.
By changing this to a directory, other information such as cmdb migration history can also be captured.
$ hamlet -i whatif entrance invoke-entrance -e unitlist -o test/
results in the following (when using JDK-11)
(Info) Generating outputs:
WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.UnsupportedOperationException: No class provided, and an appropriate one cannot be found.
at org.apache.logging.log4j.LogManager.callerClass(LogManager.java:576)
at org.apache.logging.log4j.LogManager.getLogger(LogManager.java:601)
at org.apache.logging.log4j.LogManager.getLogger(LogManager.java:588)
at io.hamlet.freemarkerwrapper.RunFreeMarker.<clinit>(RunFreeMarker.java:42)
(Fatal) [!] hamlet unknown issue, code = 1
[!] Error Running Command
$ hamlet -i whatif entrance invoke-entrance -e unitlist -o test/
results in the following (when using JDK-8)
(Info) Generating outputs:
[*] entrance: unitlist | output: contract | subset: generationcontract
[*] entrance: unitlist | output: contract | subset: managementcontract
(Info) - updating unitlist-managementcontract.json
(Info) - updating unitlist-generation-contract.json
There is a note in https://stackoverflow.com/questions/53049346/is-log4j2-compatible-with-java-11 regarding maven-shade-plugin that may be useful
Switching between java-11-openjdk-amd64 and java-8-openjdk-amd64 on Ubuntu 20.0.4 reproduces error at will
Additional methods should be available on the CMDB File System to
mkdir
with -p
as an option)cp
with -rp
as an option)rm
with -rf
as options)>
and >>
)Any cache information will optionally be synchronised with these changes.
The file path will use the CMDB File System file path, and any new file/directory will be created
The getCMDBTree()
function currently only offers read access to CMDB files and directories. This change will round out its file support.
The extra functionality could be covered by four new CMDB related functions as shown below. Where possible, the corresponding unix command behaviour should define the behaviour of these methods. This should permit a straightforward conversion of these requests to underlying operating system rquests.
mkdirCMDB - create directory
[#local result =
mkdirCMDB(
path,
{
"Parents" : false,
"Synch" : true
}
) ]
where
-p
and will create any missing directories in the path (default false).cpCMDB - copy files/directories
[#local result =
cpCMDB(
from,
to,
{
"Recurse" : false,
"Preserve" : false,
"Synch" : true
}
) ]
where
-r
and will recursively copy directories as well as files (default false).-p
and will preserve the file attributes of existing files (default false).Note that this method will need to support copying of binary and text files.
rmCMDB - remove files/directories
[#local result =
rmCMDB(
path,
{
"Recurse" : false,
"Force" : false,
"Synch" : true
}
) ]
where
-r
and will recursively delete directories as well as files (default false).-f
and will not error on non-existent files or directories (default false).toCMDB - write a file
[#local result =
toCMDB(
path,
content,
{
"Append" : false,
"Synch" : true,
"Format" : "json"
}
) ]
where
>>
and will cause the content to be appended to any existing file. (default false = overwrite).fromCMDB - read directories/files
For consistency, getCMDBTree()
could be renamed to fromCMDB
, or an additional method added. Note that the return value would continue to be the results of the read, and NOT the status of the operation.
This issue fleshes out hamlet-io/engine#1265, and is initially intended to support migration of the cmdb upgrade/cleanup process to be in the engine rather than in the executor.
It also opens the possibility of generating multiple outputs in a single invocation of the engine.
Support provision of multiple template locations in a single -d switch argument by supporting path style syntax using a semicolon as a location separator.
This will be GSGEN1.8
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.