Skip to content

Commit

Permalink
Merge pull request #96 from alechenninger/91-optional-target
Browse files Browse the repository at this point in the history
Fixes #91: Allow omitting target as if to say, 'apply my change as is'
  • Loading branch information
alechenninger committed Apr 25, 2017
2 parents 86615b8 + f1aa012 commit bbab442
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 22 deletions.
5 changes: 4 additions & 1 deletion bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ https://github.com/alechenninger/monarch
```

```
usage: monarch apply [-?] --changes CHANGES --target TARGET [TARGET ...]
usage: monarch apply [-?] --changes CHANGES [--target TARGET [TARGET ...]]
[--configs CONFIG [CONFIG ...]] [--hierarchy HIERARCHY] [--data-dir DATA_DIR]
[--output-dir OUTPUT_DIR] [--merge-keys MERGE_KEY [MERGE_KEY ...]]
[--yaml-isolate {always,never}]
Expand Down Expand Up @@ -83,6 +83,9 @@ optional arguments:
evaluate to a single source in a dynamic hierarchy. For example:
teams/myteam.yaml
environment=qa team=ops
Without a target, the whole hierarchy is up for change, and so all
changes are applied to whatever their targets are.
--configs CONFIG [CONFIG ...], --config CONFIG [CONFIG ...]
Space delimited paths to files which configures default values for
command line options. By default, monarch will look for '.monarch'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,6 @@ public InputFactory<ApplyChangesInput> addToSubparsers(Subparsers subparsers) {

subparser.addArgument("--target", "-t", "--source", "-s")
.dest("target")
.required(true)
.nargs("+")
.help("A target is the source in the source tree from where you want to change, "
+ "including itself and any sources beneath it in the hierarchy. Redundant keys "
Expand All @@ -295,7 +294,10 @@ public InputFactory<ApplyChangesInput> addToSubparsers(Subparsers subparsers) {
"key=value pairs which evaluate to a single source in a dynamic hierarchy. "
+ "For example:\n" +
"teams/myteam.yaml\n" +
"environment=qa team=ops");
"environment=qa team=ops\n" +
"\n" +
"Without a target, the whole hierarchy is up for change, and so all changes are " +
"applied to whatever their targets are.");

subparser.addArgument("--configs", "--config")
.dest("configs")
Expand Down
58 changes: 49 additions & 9 deletions bin/src/io/github/alechenninger/monarch/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,25 @@ public int run(String... args) {
.orElseThrow(missingOptionException("data directory"));
Hierarchy hierarchy = options.hierarchy()
.orElseThrow(missingOptionException("hierarchy"));
SourceSpec targetSpec = options.target()
.orElseThrow(missingOptionException("target"));
Optional<SourceSpec> targetSpec = options.target();

Map<String, SourceData> currentData =
configuredFormats.parseDataSourcesInHierarchy(dataDir, hierarchy);
Source target = hierarchy.sourceFor(targetSpec).orElseThrow(
() -> new IllegalArgumentException("No source found in hierarchy which satisfies: " +
targetSpec));

Iterable<Change> changes = options.changes();
for (Change change : changes) {
checkChangeIsApplicable(hierarchy, change);
}

applyChanges(outputDir, changes, options.mergeKeys(), currentData, target);
if (targetSpec.isPresent()) {
Source target = hierarchy.sourceFor(targetSpec.get()).orElseThrow(
() -> new IllegalArgumentException("No source found in hierarchy which satisfies: " +
targetSpec));
applyChanges(outputDir, changes, options.mergeKeys(), currentData, Targetable.of(target));
} else {
applyChanges(
outputDir, changes, options.mergeKeys(), currentData, Targetable.of(hierarchy));
}
} catch (Exception e) {
log.error("Error while applying changes.", e);
return 2;
Expand All @@ -167,16 +171,16 @@ public int run(String... args) {
}

private void applyChanges(Path outputDir, Iterable<Change> changes, Set<String> mergeKeys,
Map<String, SourceData> currentSources, Source target) throws IOException {
Map<String, SourceData> currentSources, Targetable target) throws IOException {
List<String> affectedSources = target.descendants().stream()
.map(Source::path)
.collect(Collectors.toList());
// TODO: Consider currentSources of type Sources or something like that with getter for this
Map<String, Map<String, Object>> currentData = currentSources.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().data()));

Map<String, Map<String, Object>> result = monarch.generateSources(
target, changes, currentData, mergeKeys);
Map<String, Map<String, Object>> result =
target.generateSources(monarch, changes, currentData, mergeKeys);

for (Map.Entry<String, Map<String, Object>> pathToData : result.entrySet()) {
String path = pathToData.getKey();
Expand Down Expand Up @@ -315,4 +319,40 @@ public Optional<YamlConfiguration> yamlConfiguration() {

System.exit(exitCode);
}

interface Targetable {
static Targetable of(Source source) {
return new Targetable() {
@Override
public List<Source> descendants() {
return source.descendants();
}

@Override
public Map<String, Map<String, Object>> generateSources(Monarch monarch,
Iterable<Change> changes, Map<String, Map<String, Object>> data, Set<String> mergeKeys) {
return monarch.generateSources(source, changes, data, mergeKeys);
}
};
}

static Targetable of(Hierarchy hierarchy) {
return new Targetable() {
@Override
public List<Source> descendants() {
return hierarchy.allSources();
}

@Override
public Map<String, Map<String, Object>> generateSources(Monarch monarch,
Iterable<Change> changes, Map<String, Map<String, Object>> data, Set<String> mergeKeys) {
return monarch.generateSources(hierarchy, changes, data, mergeKeys);
}
};
}

List<Source> descendants();
Map<String, Map<String, Object>> generateSources(Monarch monarch, Iterable<Change> changes,
Map<String, Map<String, Object>> data, Set<String> mergeKeys);
}
}
58 changes: 58 additions & 0 deletions bin/test/io/github/alechenninger/monarch/MainTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,64 @@ inventory:
] == yaml.load(myteamYaml)
}

@Test
void applyShouldApplyChangeWithoutTarget() {
writeFile('/etc/changes.yaml', '''
---
source: global.yaml
set:
foo: baz
---
source: teams/myteam.yaml
set:
myapp::version: 2
myapp::favorite_website: http://www.redhat.com
---
source: teams/myteam/stage.yaml
set:
myapp::favorite_website: http://stage.redhat.com
''')

writeDataSources([
'global.yaml': 'foo: "bar"',
'teams/myteam.yaml': 'bar: "baz"',
'teams/myteam/stage.yaml': 'fizz: "buzz"'
])

writeFile('/etc/hierarchy.yaml', '''
sources:
- global.yaml
- teams/%{team}.yaml
- teams/%{team}/%{environment}.yaml
inventory:
team:
- myteam
- otherteam
environment:
- dev
- qa
- stage
- prod
''')

main.run("apply -h /etc/hierarchy.yaml -c /etc/changes.yaml -d $dataDir -o /output/")

def globalYaml = new String(Files.readAllBytes(fs.getPath('/output/global.yaml')), 'UTF-8')
def myteamYaml = new String(Files.readAllBytes(fs.getPath('/output/teams/myteam.yaml')), 'UTF-8')
def stageYaml = new String(Files.readAllBytes(fs.getPath('/output/teams/myteam/stage.yaml')), 'UTF-8')

assert ['foo': 'baz'] == yaml.load(globalYaml)
assert [
'fizz': 'buzz',
'myapp::favorite_website': 'http://stage.redhat.com',
] == yaml.load(stageYaml)
assert [
'bar': 'baz',
'myapp::version': 2,
'myapp::favorite_website': 'http://www.redhat.com'
] == yaml.load(myteamYaml)
}

@Test
void applyShouldWriteSourceIfAllKeysRemoved() {
writeDataSource('global.yaml', 'bar: 123')
Expand Down
30 changes: 20 additions & 10 deletions lib/src/io/github/alechenninger/monarch/Monarch.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,31 @@ public class Monarch {
*/
public Map<String, Map<String, Object>> generateSources(Source target, Iterable<Change> changes,
Map<String, Map<String, Object>> data, Set<String> mergeKeys) {
return generateSources(target.descendants(), changes, data, mergeKeys);
}

public Map<String, Map<String, Object>> generateSources(Hierarchy hierarchy,
Iterable<Change> changes, Map<String, Map<String, Object>> data, Set<String> mergeKeys) {
return generateSources(hierarchy.allSources(), changes, data, mergeKeys);
}

private Map<String, Map<String, Object>> generateSources(List<Source> sources,
Iterable<Change> changes, Map<String, Map<String, Object>> data, Set<String> mergeKeys) {
Map<String, Map<String, Object>> result = copyMapAndValues(data);

// From top-most to inner-most, generate results, taking into account the results from ancestors
// as we go along.
if (log.isDebugEnabled()) {
log.debug("Generating sources for descendants: {}", Sources.pathsOf(target.descendants()));
log.debug("Generating sources for descendants: {}", Sources.pathsOf(sources));
}

for (Source descendant : target.descendants()) {
for (Source descendant : sources) {
result.put(descendant.path(), generateSingleSource(descendant, changes, result, mergeKeys));
}

return result;
}

private Optional<Change> findChangeForSource(Source source, Iterable<Change> changes) {
return StreamSupport.stream(changes.spliterator(), false)
.filter(c -> source.isTargetedBy(c.sourceSpec()))
.collect(Collect.maxOneResultOrThrow(() -> new IllegalArgumentException(
"Expected at most one change with matching source in list of changes, but got: " +
changes)));
}

/**
* Generates new data for the given source only, taking into account the desired changes, the
* existing hierarchy, and the existing data in the hierarchy.
Expand Down Expand Up @@ -163,6 +165,14 @@ private Map<String, Object> generateSingleSource(Source target, Iterable<Change>
return resultSourceData;
}

private Optional<Change> findChangeForSource(Source source, Iterable<Change> changes) {
return StreamSupport.stream(changes.spliterator(), false)
.filter(c -> source.isTargetedBy(c.sourceSpec()))
.collect(Collect.maxOneResultOrThrow(() -> new IllegalArgumentException(
"Expected at most one change with matching source in list of changes, but got: " +
changes)));
}

private static Map<String, Map<String, Object>> copyMapAndValues(
Map<String, Map<String, Object>> data) {
Map<String, Map<String, Object>> copy = new HashMap<>();
Expand Down

0 comments on commit bbab442

Please sign in to comment.