Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gitman ignores groups on nested gitman install runs #222

Open
jrisch opened this issue Aug 20, 2020 · 15 comments
Open

Gitman ignores groups on nested gitman install runs #222

jrisch opened this issue Aug 20, 2020 · 15 comments

Comments

@jrisch
Copy link

jrisch commented Aug 20, 2020

We have just decided to switch from an inhouse developed, almost identical tool, to gitman. But I've stumbled across what seems to be a bug, during my initial testing:

Given a gitman.yml file that looks like this:

location: .gitman
sources:
  # Production sources
  - name: tf-module-production
    link: tf-module
    repo: git@githost:tf-module.git
    rev: 1.2.0
  # Dev sources
  - name: tf-module-dev
    link: tf-module
    repo: git@githost:tf-module.git
    rev: master
groups:
  - name: production
    members:
      - tf-module-production
  - name: dev
    members:
      - tf-module-dev

I expect that I end up with a directory looking like this, if I do a gitman install dev:

├── .gitman
├── .gitman.yml
└── tf-module -> .gitman/tf-module-dev

And if I do a gitman install production:

├── .gitman
├── .gitman.yml
└── tf-module -> .gitman/tf-module-production

This works - so far so good.

But if my tf-module contains a gitman.yml file with a similar structure and I run gitman install production, gitman actually installs all the sources, linking for each source, and it seems like I end up with the dev sources (so I end up with the master branches). I guess that gitman doesn't honour the groupname in the subsequent gitman install calls...?

@jacebrowning
Copy link
Owner

Thanks for the report!

if my tf-module contains a gitman.yml

By this do you mean that git@githost:tf-module.git has its own set of dependencies to be recursively managed by gitman? Could you share the output of that second gitman install production -vv run?

@jrisch
Copy link
Author

jrisch commented Aug 20, 2020

Yes - sure! Here it is (some output is obfuscated):

[DEBUG   ] Running install command...
[INFO    ] Installing dependencies: production
[DEBUG   ] Searching for config...
[DEBUG   ] Looking for config in: /home/username/develop/gitman-test
[DEBUG   ] Found config: /home/username/develop/gitman-test/.gitman.yml
[INFO    ] No locked sources, using latest...
[INFO    ] $ mkdir -p /home/username/develop/gitman-test/.gitman
[INFO    ] $ cd /home/username/develop/gitman-test/.gitman
[INFO    ] Updating source files...
[DEBUG   ] Creating a new repository...
[INFO    ] $ git clone --reference /home/username/.gitcache/gitman-subsub.reference [email protected]:username/gitman-subsub.git module-production
[DEBUG   ] > Cloning into 'module-production'...
[INFO    ] $ cd module-production
[DEBUG   ] Checking for a valid working tree...
[DEBUG   ] $ git rev-parse --is-inside-work-tree
[DEBUG   ] > true
[DEBUG   ] Checking for a valid git top level...
[DEBUG   ] $ git rev-parse --show-toplevel
[DEBUG   ] > /home/username/develop/gitman-test/.gitman/module-production
[DEBUG   ] $ cwd /home/username/develop/gitman-test/.gitman/module-production
[DEBUG   ] Confirming there are no uncommitted changes...
[DEBUG   ] $ git update-index -q --refresh
[DEBUG   ] $ git diff-index --quiet HEAD
[DEBUG   ] $ git ls-files --others --exclude-standard
[DEBUG   ] $ git rev-parse --abbrev-ref HEAD
[DEBUG   ] > master
[DEBUG   ] $ git rev-parse HEAD
[DEBUG   ] > fc6f3ed3c7f8ab297d22af0eda76e4bfd53d5676
[DEBUG   ] $ git describe --tags --exact-match
[DEBUG   ] > 0.0.1
[INFO    ] $ git remote set-url origin [email protected]:username/gitman-subsub.git
[INFO    ] $ git fetch --tags --force --prune origin 2.0.0
[DEBUG   ] > From githost.example:username/gitman-subsub
[DEBUG   ] > * tag               2.0.0      -> FETCH_HEAD
[DEBUG   ] $ git stash
[DEBUG   ] > No local changes to save
[INFO    ] $ git checkout --force 2.0.0
[DEBUG   ] > HEAD is now at fc6f3ed Update gitman.yml
[DEBUG   ] $ git branch --set-upstream-to origin/2.0.0
[DEBUG   ] > fatal: could not set upstream of HEAD to origin/2.0.0 when it does not point to any branch.
[DEBUG   ] Ignored error from call to 'git'
[INFO    ] Creating a symbolic link...
[DEBUG   ] Looking for config in: /home/username/develop/gitman-test/.gitman/module-production
[DEBUG   ] Found config: /home/username/develop/gitman-test/.gitman/module-production/gitman.yml
[INFO    ] No locked sources, using latest...
[INFO    ] $ mkdir -p /home/username/develop/gitman-test/.gitman/module-production/.gitman
[INFO    ] $ cd /home/username/develop/gitman-test/.gitman/module-production/.gitman
[INFO    ] Updating source files...
[DEBUG   ] Creating a new repository...
[INFO    ] $ git clone --reference /home/username/.gitcache/gitman-submodule.reference [email protected]:username/gitman-submodule.git module-production
[DEBUG   ] > Cloning into 'module-production'...
[INFO    ] $ cd module-production
[DEBUG   ] Checking for a valid working tree...
[DEBUG   ] $ git rev-parse --is-inside-work-tree
[DEBUG   ] > true
[DEBUG   ] Checking for a valid git top level...
[DEBUG   ] $ git rev-parse --show-toplevel
[DEBUG   ] > /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[DEBUG   ] $ cwd /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[DEBUG   ] Confirming there are no uncommitted changes...
[DEBUG   ] $ git update-index -q --refresh
[DEBUG   ] $ git diff-index --quiet HEAD
[DEBUG   ] $ git ls-files --others --exclude-standard
[DEBUG   ] $ git rev-parse --abbrev-ref HEAD
[DEBUG   ] > master
[DEBUG   ] $ git rev-parse HEAD
[DEBUG   ] > c2efb7cfb2766c8b67d0a027fa97b281ff4cea49
[DEBUG   ] $ git describe --tags --exact-match
[DEBUG   ] > fatal: no tag exactly matches 'c2efb7cfb2766c8b67d0a027fa97b281ff4cea49'
[DEBUG   ] Ignored error from call to 'git'
[INFO    ] $ git remote set-url origin [email protected]:username/gitman-submodule.git
[INFO    ] $ git fetch --tags --force --prune origin 1.0.0
[DEBUG   ] > From githost.example:username/gitman-submodule
[DEBUG   ] > * tag               1.0.0      -> FETCH_HEAD
[DEBUG   ] $ git stash
[DEBUG   ] > No local changes to save
[INFO    ] $ git checkout --force 1.0.0
[DEBUG   ] > HEAD is now at 8d84107 Release 1.0.0
[DEBUG   ] $ git branch --set-upstream-to origin/1.0.0
[DEBUG   ] > fatal: could not set upstream of HEAD to origin/1.0.0 when it does not point to any branch.
[DEBUG   ] Ignored error from call to 'git'
[INFO    ] Creating a symbolic link...
[INFO    ] $ mkdir -p /home/username/develop/gitman-test/.gitman/module-production/modules
[DEBUG   ] Looking for config in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[DEBUG   ] No config found in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[DEBUG   ] $ cd /home/username/develop/gitman-test/.gitman/module-production/.gitman
[INFO    ] Updating source files...
[DEBUG   ] Creating a new repository...
[INFO    ] $ git clone --reference /home/username/.gitcache/gitman-submodule.reference [email protected]:username/gitman-submodule.git module-sandbox
[DEBUG   ] > Cloning into 'module-sandbox'...
[INFO    ] $ cd module-sandbox
[DEBUG   ] Checking for a valid working tree...
[DEBUG   ] $ git rev-parse --is-inside-work-tree
[DEBUG   ] > true
[DEBUG   ] Checking for a valid git top level...
[DEBUG   ] $ git rev-parse --show-toplevel
[DEBUG   ] > /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[DEBUG   ] $ cwd /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[DEBUG   ] Confirming there are no uncommitted changes...
[DEBUG   ] $ git update-index -q --refresh
[DEBUG   ] $ git diff-index --quiet HEAD
[DEBUG   ] $ git ls-files --others --exclude-standard
[DEBUG   ] $ git rev-parse --abbrev-ref HEAD
[DEBUG   ] > master
[DEBUG   ] $ git rev-parse HEAD
[DEBUG   ] > c2efb7cfb2766c8b67d0a027fa97b281ff4cea49
[DEBUG   ] $ git describe --tags --exact-match
[DEBUG   ] > fatal: no tag exactly matches 'c2efb7cfb2766c8b67d0a027fa97b281ff4cea49'
[DEBUG   ] Ignored error from call to 'git'
[DEBUG   ] $ git stash
[DEBUG   ] > No local changes to save
[INFO    ] $ git checkout --force master
[DEBUG   ] > Already on 'master'
[DEBUG   ] > Your branch is up to date with 'origin/master'.
[DEBUG   ] $ git branch --set-upstream-to origin/master
[DEBUG   ] > Branch 'master' set up to track remote branch 'master' from 'origin'.
[INFO    ] Creating a symbolic link...
[DEBUG   ] Looking for config in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[DEBUG   ] No config found in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[DEBUG   ] $ cd /home/username/develop/gitman-test/.gitman/module-production/.gitman
[DEBUG   ] $ cd /home/username/develop/gitman-test/.gitman
[INFO    ] Skipped dependency: module-sandbox
[INFO    ] No locked sources, using latest...
[INFO    ] $ cd /home/username/develop/gitman-test/.gitman
[INFO    ] Running install scripts...
[INFO    ] $ cd module-production
[DEBUG   ] Checking for a valid working tree...
[DEBUG   ] $ git rev-parse --is-inside-work-tree
[DEBUG   ] > true
[DEBUG   ] Checking for a valid git top level...
[DEBUG   ] $ git rev-parse --show-toplevel
[DEBUG   ] > /home/username/develop/gitman-test/.gitman/module-production
[DEBUG   ] $ cwd /home/username/develop/gitman-test/.gitman/module-production
[INFO    ] (no scripts to run)
[DEBUG   ] Looking for config in: /home/username/develop/gitman-test/.gitman/module-production
[DEBUG   ] Found config: /home/username/develop/gitman-test/.gitman/module-production/gitman.yml
[INFO    ] No locked sources, using latest...
[INFO    ] $ cd /home/username/develop/gitman-test/.gitman/module-production/.gitman
[INFO    ] Running install scripts...
[INFO    ] $ cd module-production
[DEBUG   ] Checking for a valid working tree...
[DEBUG   ] $ git rev-parse --is-inside-work-tree
[DEBUG   ] > true
[DEBUG   ] Checking for a valid git top level...
[DEBUG   ] $ git rev-parse --show-toplevel
[DEBUG   ] > /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[DEBUG   ] $ cwd /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[INFO    ] (no scripts to run)
[DEBUG   ] Looking for config in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[DEBUG   ] No config found in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-production
[DEBUG   ] $ cd /home/username/develop/gitman-test/.gitman/module-production/.gitman
[INFO    ] Running install scripts...
[INFO    ] $ cd module-sandbox
[DEBUG   ] Checking for a valid working tree...
[DEBUG   ] $ git rev-parse --is-inside-work-tree
[DEBUG   ] > true
[DEBUG   ] Checking for a valid git top level...
[DEBUG   ] $ git rev-parse --show-toplevel
[DEBUG   ] > /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[DEBUG   ] $ cwd /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[INFO    ] (no scripts to run)
[DEBUG   ] Looking for config in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[DEBUG   ] No config found in: /home/username/develop/gitman-test/.gitman/module-production/.gitman/module-sandbox
[DEBUG   ] $ cd /home/username/develop/gitman-test/.gitman/module-production/.gitman
[DEBUG   ] $ cd /home/username/develop/gitman-test/.gitman
[INFO    ] Installed 3 dependencies
[DEBUG   ] Command succeeded

The config looks like this:

location: .gitman
sources:
  - name: module-production
    repo: [email protected]:username/gitman-subsub.git
    rev: 2.0.0
    link: modules/module0
  - name: module-sandbox
    repo: [email protected]:username/gitman-subsub.git
    rev: master
    link: modules/module0
groups:
    - name: production
      members:
          - module-production
    - name: sandbox
      members:
          - module-sandbox

@jacebrowning
Copy link
Owner

Does gitman-subsub.git have it's own gitman config file? Can you share that as well?

@jrisch
Copy link
Author

jrisch commented Aug 20, 2020

Yes, sure:

location: .gitman
sources:
  # Production sources
  - name: module-production
    link: modules/module1
    repo: [email protected]:username/gitman-submodule.git
    rev: 1.0.0
  # Dev sources
  - name: module-sandbox
    link: modules/module1
    repo: [email protected]:username/gitman-submodule.git
    rev: master
groups:
  - name: production
    members:
      - module-production
  - name: sandbox
    members:
      - module-sandbox

@jacebrowning
Copy link
Owner

Thanks, that's helpful! You're correct that groups are only processed once at the top level, so I believe this issue is a request to pass that information to each nested level.


FWIW, I'm not sure if installing different versions of the same dependency is the intended use case for groups.

You should be able to accomplish what you're trying to do with locked sources: https://gitman.readthedocs.io/en/latest/use-cases/branch-tracking/

location: .gitman
sources:
  - name: module-production
    repo: [email protected]:username/gitman-subsub.git
    rev: master
    link: modules/module0
sources_locked:
  - name: module-production
    repo: [email protected]:username/gitman-subsub.git
    rev: 2.0.0
    link: modules/module0

where your "dev" setup is gitman update --skip-lock and your "production" setup is gitman install.

@jrisch
Copy link
Author

jrisch commented Aug 21, 2020 via email

@jacebrowning
Copy link
Owner

This is the spot where "production" is translated to ["tf-module-production"]:

sources_filter = self._get_sources_filter(*names, sources=sources)

This is the spot where we recurse into nested repositories:

count += config.install_dependencies(

You'll notice that names are not forwarded to recursive installation calls. To achieve the behavior you're looking for, we would need to determine that "production" is the name of a group (and not just the name of a single source), then forward that as *names to all recursive calls. In hindsight, having a separate --groups argument may have been a better design to keep these lists of names separate.

Another thing to consider is how to handle nested gitman configs that don't have groups defined, but are passed a group name.

@jrisch
Copy link
Author

jrisch commented Aug 24, 2020 via email

@daniel-brosche
Copy link
Collaborator

We have a similar requirement solved by using seperate branches in the top level repository (like dev and prod) to maintain seperate gitman configs because of our many repositories and parallel dev enviroments. Switching between environments is just accomblished by switching between branches.

Furthermore we realized that the recursive dependency resolution increases the complexity of the config management at a certain amount of nested dependency levels. We have a very complex dependency graph with more than 80 repositories.
To simplify the config management we introduced a distinct top level "product" repository which contains one huge flat gitman.yml and some product related global stuff (configs/build scripts/jenkins groovy scripts).
We don't use recursive dependency resolution which can also produce hard to find problems when referencing different versions of the same repository.

We resolve all product related dependencies over a global gitman config. Each developer and also our build system needs to use this product repository to build all or parts of the product. This helps to avoid workspace clashes.
Because of the many different software developer roles resp. software modules we defined groups which represents sub-workspaces (like app_a, app_b, hal, drivers and so on). This works quite well.

In a release phase we create a new release branch in this toplevel repository and pining each of the dependencies one by one.
At the end in each rev-field is a tag referenced. To see what changed over several releases or between dev activities is quite easy by comparing the one and only gitman.yml between the different branches,

Maybe this approach is also helpful for your application.

@jrisch
Copy link
Author

jrisch commented Aug 24, 2020

@daniel-brosche Thank you for your input - although I have a hard time figuring out what you are doing. Can you maybe paint the picture a little bit clearer with a simple example..?

@daniel-brosche
Copy link
Collaborator

daniel-brosche commented Aug 24, 2020

Maybe this helps, I have tried to illustrate it with an abstract example.

The product repository here is product_xy which contains the global product related gitman.yml besides some other stuff.

Mostly we continously build the mainline (master) of all repositories during the common development phase.
Here the product repository master branch references the needed master branches of all repository dependencies.
As mentioned, this product repository is used by all developers and the buildsystem itself (jenkins here).

image

In this global gitman.yml there are groups defined which represents sub-workspaces for the main developer domains.

In the release phase I create a release branch in the product repository and reference tags over time. At the end of stabilization only tags are referenced in this product repository release branch.

image

For very complex features I mix these approaches and create a distinct development environment that reference feature branches and tags.

image

Over this way, I can ensure a stable feature environment where only the related feature repositories are mutable over time (e.g. 75% of all repositories are immutable and 25% are mutable).

This is also very helpful to exchange feature related workspaces between developers and buildsystem to build reproducible feature related builds.
At the end of the feature development all depedent feature repositories are merged in the corresponding master branch and the feature branch in the toplevel repository will be deleted.

Some parts of this worklow can also be achieved by locking sources but in my experience this approach is more explicit and pretty easy in terms of config management.

@jrisch
Copy link
Author

jrisch commented Aug 25, 2020

This is almost how I do it. I have a directory structure like this one:

tf-live/
├── gitman.yml
├── tf-bootstrap
│   └── modules
│       └── tf-aws
├── tf-network
│   ├── gitman.yml
│   └── modules
│       ├── tf-asg
│       └── tf-aws-network
│           └── modules
│               └── tf-vpn-gateway
└── tf-services
    └── modules
        └── tf-aws-service

tf-live is just a shell, containing a gitman.yml file and a script to wrap the calls to terraform in the right order. I have 3 environments, sandbox, preprod and prod. I want the toplevel gitman.yml file to check out the same tf-* repos, but it different versions, depending on the environment:

tf-live/
├── gitman.yml
├── tf-bootstrap 
├── tf-network
└── tf-services
  • Master branch, when it's sandbox
  • v1.0.0 if it's production
  • v1.1.0if it's preprod and I'm testing a new release

I was hoping that groups could do that for me, and it works fine in the top level. But when fx tf-network contains modules, these can also vary depending on the environment. And they can also have submodules, with differing versions depending on the environment (which means another gitman.yml file):

tf-live/tf-network/
├── gitman.yml
└── modules
    ├── tf-asg
    └── tf-aws-network
        ├── gitman.yml
        └── modules
            └── tf-vpn-gateway

If gitman supported sending the groupname to the subsequent gitman install command, it would work fine, but it doesn't.

Unfortunately I'm not a python programmer, so I'm not exactly sure how to solve this in the code, but I'll try to take a stab at it.

I might end up using a dedicated wrapper tool like terragrunt for this instead, as it handles environments with dependencies of differing versions just fine. But we also use this functionality in other code projects, and would like to use gitman if at all possible...

@jrisch
Copy link
Author

jrisch commented Aug 25, 2020

Just a quick thought: Would it be possible/nice to structure the config as follows, instead of the separate groups section?:

location: .gitman
sources:
  default:
    # Production sources
    - name: tf-module-production
      link: tf-module
      repo: git@githost:tf-module.git
      rev: 1.2.0
  dev:
    # Dev sources
    - name: tf-module-dev
      link: tf-module
      repo: git@githost:tf-module.git
      rev: master

Then always have a default "group" or section, which get's installed when just running gitman install.

@daniel-brosche
Copy link
Collaborator

daniel-brosche commented Aug 26, 2020

But when fx tf-network contains modules, these can also vary depending on the environment.

According to my described approach, there is no nested gitman.yml and then I would arrange it like this:

In the master Branch of tv-life:

location: .gitman
sources:
  - name: module0
    repo: [email protected]:username/gitman-subsub.git
    rev: master
    link: modules/module0
  - name: module1
    link: modules/module1
    repo: [email protected]:username/gitman-submodule.git
    rev: master

In the branch v1.0.0

location: .gitman
sources:
  - name: module0
    repo: [email protected]:username/gitman-subsub.git
    rev: 2.0.0
    link: modules/module0
 - name: module1
    link: modules/module1
    repo: [email protected]:username/gitman-submodule.git
    rev: 1.0.0

You could also change the gitman location to modules then the links are not needed.

I do import everything in defined imports directory also with subdirectories like infra\component1 directly over the name field (-name: infra/component1). All components look into this directory to resolve their own dependencies (by convention).

@jrisch
Copy link
Author

jrisch commented Sep 9, 2020

I've come up with an alternative approach that I like a bit better.

First we add a gitman config file for each environment:

gitman-production.yml
gitman-preprod.yml
gitman-sandbox.yml

Then a simple Makefile:

.PHONY: install clean uninstall

all: .gitman.yml
  @echo "Preparing for deploying to ${ENV}..."
  gitman install

.gitman.yml::
  @ln -sf gitman-${ENV}.yml .gitman.yml

clean:
  gitman uninstall --force
  -rm -f .gitman.yml

uninstall:
  gitman uninstall
  -rm -f .gitman.yml

All it does is symlinking the gitman-${ENV}.yml file to .gitman.yml and run gitman install (besides the clean and uninstall targets)

In all subrepos we add a similar structure (if it has dependencies on anything, of course):

gitman-production.yml
gitman-preprod.yml
gitman-sandbox.yml
Makefile

Each gitman-env.yml file looks like this (with multiple different repos in different revisions of course):

location: .gitman
sources:
  - name: testhest
    repo: [email protected]:username/gitman-test.git
    rev: master
    link: testhest
    scripts:
      - ENV=${ENV} make

As you can see it runs make in the end, with the $ENV var preprended. This makes the sub repos’ git install command use the correct gitman config file. The .gitman.yml (which is just a symlink) should be ignored in .gitignore.

So to sum it up: With a setup like this, we have a workflow looking like this (deploying to production):

  1. Check out main repo
  2. Run ENV=production make to install production sources
  3. Run deployment scripts
  4. Run make clean to clean up (this forces a gitman uninstall)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants