Code Monkey home page Code Monkey logo

monarch's Introduction

monarch

Rule over hierarchical data!

example

$ monarch set --changes petstore.yaml --source global.yaml --put 'petstore::version: "2"'
$ monarch apply --changes petstore.yaml --target env=dev
$ cat hieradata/env/dev.yaml 
petstore::version: '2'
$ cat hieradata/global.yaml 
petstore::version: '1'
$ monarch apply --changes petstore.yaml --target global.yaml
$ cat hieradata/env/dev.yaml 
$ cat hieradata/global.yaml 
petstore::version: '2'

screencast

install from tarball

  1. Download tar or zip @ https://github.com/alechenninger/monarch/releases/latest
  2. Extract somewhere you like to put things
  3. Have JRE8 installed
  4. Have java in your PATH or JAVA_HOME environment variable defined (for ex: "/etc/alternatives/java_sdk")
  5. Add a symlink 'monarch' to your PATH which points to ${where_you_extracted_zip}/bin/monarch-bin

install from source

  1. Fork or git clone [email protected]:alechenninger/monarch.git
  2. cd monarch
  3. Have gradle installed. I recommend using sdkman.
  4. Have JRE8 installed
  5. Have java in your PATH or JAVA_HOME defined (for ex: "/etc/alternatives/java_sdk")
  6. Run gradle installDist
  7. Add symlink 'monarch' to your path which points to ${where_you_put_git_things}/monarch/bin/build/install/monarch-bin/bin/monarch-bin
  8. Hack or git pull and gradle installDist whenever you want to update your executable

install from docker

  1. docker pull alechenninger/monarch
  2. docker tag alechenninger/monarch monarch
  3. docker run [-v ...:... -w ...] monarch

Few things to note when running with docker:

  • You will need to mount your data and output directories at a minimum.
  • You may also want to mount the locations of config files, and set your working directory appropriately, or use --config option.
  • The containers user is "root" so on your system monarch may look for default config files (in user's home directory) in a different location then in the container.

usage

See bin for command line usage.

See the tests for example library usage and edge cases.

motivation

The idea is something that can take a desired end state, a hierarchy of data sources, a "target" data source to change among them, the current state of all data sources in the hierarchy, and split out a new state of all data sources in the hierarchy with the given changes applied to the target data source and its children. In other words, it automates promoting values throughout the hierarchy, only affecting the data sources in the hierarchy you want, but allowing you to specify changes only as changes to root nodes that you eventually want applied but can't jump to right away.

A practical usage example is Hiera, used by Puppet. You organize configuration in a hierarchy of data sources. During releases, you might promote values from one environment's configuration to another. It can be tedious to copy these values among horizontal promotions, which do not inherit each other (say, a QA environment to a staging environment), as well as, finally, vertical promotions (say from QA and stage environments to a base configuration file which all environments inherit defaults from). This tool allows you to start with your end state, but be able to generate intermediate stages as you promote your release from one environment to another. Finally, when you are ready to generate your final state, redundant keys are removed.

monarch's People

Contributors

alechenninger avatar kahowell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

kahowell

monarch's Issues

Allow merging hashes

Test case:

  @Test
  public void shouldMergeHashesIfNeeded() {
    def changes = '''

---
  source: global.yaml
  set:
    some::hash:
      global_value: true

---
  source: myteam.yaml
  merge:
    - some::hash
  set:
    some::hash:
      myteam_value: false
'''

    def result = generateFromYaml(hierarchy, changes, 'myteam.yaml', [
        'global.yaml': '',
        'myteam.yaml': '',
        'myteam/stage.yaml': ''
    ])

    def expected = [
        'global.yaml': [:],
        'myteam.yaml': [
            'some::hash': [
                'global_value': true,
                'myteam_value': false
            ]
        ],
        'myteam/stage.yaml': ''
    ]

    assert result == expected
  }

Add ability to restrict modifications to an isolated portion of a yaml file

This will help interaction with YAML files that are partially human edited, and partially managed by monarch.

For example, the YAML might look like:

# human comments
human::managed::key: value

# --- Begin generated by monarch
monarch::managed::key: value
# --- End generated by monarch

# more human comments
another::human::managed::key: value

Support hierarchy defined with variables instead of static path tree

Essentially, like hiera proper:

---
:hierarchy:
  - "nodes/%{trusted.certname}"
  - "environment/%{server_facts.environment}"
  - "virtual/%{::is_virtual}"
  - common

So in a change:

# when no source: ... try facts instead
facts:
  trusted.certname: fake.com
  server_facts.environment: production
  ::is_virtual: true
set:
  etc: values here

Add command(s) to modify an existing changeset

Use case and some prior discussion can be found in #36

Basically, it would be useful to be able to programmatically modify a changeset.

That being said, without a YAML parser and dumper that maintain comments and formatting, doing so will canonicalize the changeset document, sorting it, reformatting it, and stripping it of comments.

I looked internally and we have no meaningful comments in our changesets, so maybe this is not such a big deal. More discussion on this kind of thing here: #15

Reverse engineer changeset from exiting hiera configs?

Already have a ton of stuff in hiera for a project, would be great to generate the changeset from that as a starting point. And maybe an example for how to cleanup duplicate keys by generate a changeset then generate the hiera data from the changeset.

Variables in a change set

Would maybe be convenient to do something like

---
  source: foo
  variables:
    version: 1.2.3
  set:
    myapp1::version: ${version}
    myapp2::version: ${version}
    myapp3::version: ${version}

But maybe not

Maybe support a change which is comprehensive, removes keys it is not setting

This would be useful when you are creating a new data source and you maybe experimenting with moving keys around. You'd have to add the moved keys to remove blocks to clean up from the old data source. If this feature was added, you can say flag the change as "comprehensive" or something like that, and it was ignore existing data (maybe "ignore_existing" or "overwrite" are better flag names).

Include more information when YAML parse exception occurs

Had a random tab creep into a changeset and got the following error:
Exception in thread "main" while scanning for the next token
found character '\t(TAB)' that cannot start any token. (Do not use \t(TAB) for indentation)
in 'reader', line 19, column 1:

^

at org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens(ScannerImpl.java:420)
at org.yaml.snakeyaml.scanner.ScannerImpl.checkToken(ScannerImpl.java:226)
at org.yaml.snakeyaml.parser.ParserImpl$ParseBlockMappingKey.produce(ParserImpl.java:558)
at org.yaml.snakeyaml.parser.ParserImpl.peekEvent(ParserImpl.java:158)
at org.yaml.snakeyaml.parser.ParserImpl.checkEvent(ParserImpl.java:143)
at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:224)
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:155)
at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:229)
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:155)
at org.yaml.snakeyaml.composer.Composer.composeDocument(Composer.java:122)
at org.yaml.snakeyaml.composer.Composer.getNode(Composer.java:84)
at org.yaml.snakeyaml.constructor.BaseConstructor.getData(BaseConstructor.java:104)
at org.yaml.snakeyaml.Yaml$1.next(Yaml.java:471)
at io.github.alechenninger.monarch.YamlMonarchParser.parseChanges(YamlMonarchParser.java:53)
at io.github.alechenninger.monarch.MonarchOptionsFromInputs.lambda$changes$5(MonarchOptionsFromInputs.java:73)
at java.util.Optional.map(Optional.java:215)
at io.github.alechenninger.monarch.MonarchOptionsFromInputs.changes(MonarchOptionsFromInputs.java:69)
at io.github.alechenninger.monarch.OverridableOptions$1.<init>(OverridableOptions.java:54)
at io.github.alechenninger.monarch.OverridableOptions.lambda$changes$11(OverridableOptions.java:53)
at io.github.alechenninger.monarch.Main.run(Main.java:89)
at io.github.alechenninger.monarch.Main.main(Main.java:179)

The tab was at the beginning of a blank line. I could see it being expected behavior but was definitely annoying. Could at least throw a better exception message (even just including the offending filename would be a big help)

Having a changeset that ends with '---' causes a null pointer exception

I'm guessing that yaml.loadAll (from org.yaml.snakeyaml.Yaml) is parsing it as 'null' for some reason, meaning this could affect any yaml file that is parsed throughout the app.

Exception in thread "main" java.lang.NullPointerException
at io.github.alechenninger.monarch.Change.fromMap(Change.java:41)
at io.github.alechenninger.monarch.YamlMonarchParser.parseChanges(YamlMonarchParser.java:55)
at io.github.alechenninger.monarch.MonarchOptionsFromInputs.lambda$changes$5(MonarchOptionsFromInputs.java:73)
at java.util.Optional.map(Optional.java:215)
at io.github.alechenninger.monarch.MonarchOptionsFromInputs.changes(MonarchOptionsFromInputs.java:69)
at io.github.alechenninger.monarch.OverridableOptions$1.(OverridableOptions.java:54)
at io.github.alechenninger.monarch.OverridableOptions.lambda$changes$11(OverridableOptions.java:53)
at io.github.alechenninger.monarch.Main.run(Main.java:89)
at io.github.alechenninger.monarch.Main.main(Main.java:179)

Allow declaration of multiple 'root' hierarchies in config file

It would also be nice to have multiple root hierarchies in a single config, e.g.:
config.yaml
hierarchy:

  • group/A.yaml
    • group/A/dev.yaml
    • group/A/prod.yaml
  • group/B.yaml
    • group/B/dev.yaml
    • group/B/prod.yaml

Then you could have multiple change sets that could work off of group/A.yaml or group/B.yaml without having to specify multiple config files. Trying to have multiple roots now gives me a NullPointerException:
Exception in thread "main" java.lang.NullPointerException
at io.github.alechenninger.monarch.MonarchParsers.getExtensionForFileName(MonarchParsers.java:44)
at io.github.alechenninger.monarch.Main.readDataForHierarchy(Main.java:131)
at io.github.alechenninger.monarch.Main.run(Main.java:100)
at io.github.alechenninger.monarch.Main.main(Main.java:179)

Should validate changeset 'source' is in config hierarchy

You can specify a source in your changeset that is not in the hierarchy. I dont think it is trying to apply the changeset, but at the very least it is still rewriting your hieradata output in alphabetical order.

Should probably verify that the source is declared in your hierarchy and fail loudly.

Maintain comments, formatting when dumping YAML

Not everybody using the hiera data will be looking at monarch files. While it's good to have comments in the changeset we loose the ability to comment the hiera yaml. This can help folks understand why something was set etc.

Allow one variable definition to define others, implicitly

For example, imagine a dynamic hierarchy like this:

hierarchy:
  sources:
    - common.yaml
    - environment/%{environment}.yaml
    - team/%{team}.yaml
    - team/%{team}/%{environment}.yaml
    - node/%{hostname}.yaml
  potentials:
    # snip...
    hostname:
      - myhost.prod.mycompany.com:
        environment: prod
        team: myteam

Then, if you defined a target by hostname=myhost.prod.mycompany.com, you would have a complete hierarchy, without having to manually define environment and team for that node too, which you already know statically in advance.

Additionally, the reverse is true. If you targeted environment=prod or team=myteam, you would know that host was a descendant.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.