How to auto deploying git repositories with submodules on AWS?
Asked Answered
C

8

14

I have a submodule in my git repository and my directory structure is like,

app
  -- folder1
  -- folder2
  -- submodule @5855

I have deployed my code on AWS by using autodeploy service. Now, on server I have code in the parent-directory but submodule directories are empty.

Q1) How can I get data in submodules. My repository on server is not git repository. Do I need to convert it firstly into git repo and then run submodule commands to get it ?

Q2) How can I automate the submodule deployment as well?

Thanks

Copenhaver answered 10/3, 2017 at 7:20 Comment(9)
Are you still looking for this? I can provide a work around that I'm using. AWS pipeline does not support submodules.Anetta
No, I am not. But please post your answer. It may help someone else.Copenhaver
I'm also looking for a solution for this. ThanksMerce
Im looking for solution tooErinn
@MarcosCasagrande why dont you include your work around as an answer....?Erinn
@MarcosCasagrande can you post your workaround?Oxbow
@Erinn I've posted a workaroundOxbow
@JoseNunez I've posted a workaroundOxbow
I ended up using NPM packages instead of git submodules for my Node projects. I like this approach more, as you encapsulate and share functionality better. Thanks @MattBucciMerce
O
19

Edit: Codebuild now has a "submodules" flag https://docs.aws.amazon.com/codebuild/latest/APIReference/API_GitSubmodulesConfig.html

Here's what worked for me

We're going to reinitialize the git repository and then trigger a submodule clone during the build phase of our deploy, essentially patching in support for submodules in codepipeline / codebuild

  • Generate a new SSH key for your github account, if using an organization you may want to create a deploy user
  • Store this ssh key in your aws parameter store using aws ssm put-parameter --name build_ssh_key --type String --value "$(cat id_rsa)" ideally use SecureString instead of String but the guide I was following simply used string so I'm not sure if the commandline will require any extra params
  • Go into IAM and grant your CodePipeline user read access to your paramstore, I just granted read access to SSM

Then make your buildspec.yml look like the following:

version: 0.2

env:
  parameter-store:
    build_ssh_key: "build_ssh_key"

phases:
  install:
    commands:
      - mkdir -p ~/.ssh
      - echo "$build_ssh_key" > ~/.ssh/id_rsa
      - chmod 600 ~/.ssh/id_rsa
      - ssh-keygen -F github.com || ssh-keyscan github.com >>~/.ssh/known_hosts
      - git config --global url."[email protected]:".insteadOf "https://github.com/"
      - git init
      - git remote add origin <Your Repo url here using the git protocol>
      - git fetch
      - git checkout -t origin/master
      - git submodule init
      - git submodule update --recursive
  build:
    commands:
      - echo '...replace with real build commands...'

artifacts:
  files:
    - '**/*'
Oxbow answered 31/10, 2018 at 6:35 Comment(4)
This aws CodeCommit is really trashed when we cant use submodules. I guess I will just make a batch script which is a lot more easier than this non-working CI/CD "tools"Encore
Did you write this blog? adrianhesketh.com/2018/05/02/…Shererd
this is the only solution that works with codepipeline + codebuild. hopefully they will add submodule support in the codepipeline source actions s we dont hve to do this hack anymore.Hither
CodeBuild supports GitSubmodulesConfig. How can I integrate it with my pipeline of type "AWS::CodePipeline::Pipeline"?Culmiferous
R
18

After banging my head against this all day, I've found a simple solution (for Code Pipeline) that doesn't require any SSH key juggling in the buildspec. I am using Bitbucket but I would think this would work for other providers. I'm also cloning my submodule via https, I'm not sure if that's a requirement or not.

  1. Configure your source to do a full clone of the repository. This will pass along the git metadata that you need. Source configuration

  2. Configure your build role to add a customer-managed UseConnection permission to give your build action access to the credentials you configured for your source. Documentation from AWS here: https://docs.aws.amazon.com/codepipeline/latest/userguide/troubleshooting.html#codebuild-role-connections

  3. Set up your env to include git-credential-helper: yes and clone the submodule in your buildspec.yml:

enter image description here

And that's it! Submodule will be available for build, and without having to do a bunch of key configuration for every submodule you want to use.

Maybe a good addition to the documentation if this ends up being useful for people.

Rubidium answered 1/10, 2020 at 20:52 Comment(6)
This actually works, for those using CodePipeline, you need to do this: go to the CodeBuild Project, Edit, Sources, Add a new source, Select Github, Connect via Oauth or Personal Access token, set the repo, and save ( you can delete the source later ) what you need is to create a connection in CODEBUILD, not CodePipeline. This will enable the credential helper in the CodeBuild Container. You can debug by checking the build logs, right bellow "Git credential helper enabled" you should not see: GITHUB Git credential unavailable. Or BITBUCKET if thats the case. Hope this helps someone .Personalty
This doesn't work for me with BitBucket: Git credential helper enabled GITHUB Git credential unavailable. GITHUB_ENTERPRISE Git credential unavailable.. Not sure why it thinks it's GitHub. I don't use that for any projects. Also confirmed I have the correct codestar statement for the role. This is strange.. man this is insane they still lack support for submodules. CDK is such a painBamberg
Thanks @Personalty ! This works with our CodePipeline + CodeBuild + GitHub submodules setup. Without the CodeBuild source update, it will prompt remote: Invalid username or password. and then fatal: Authentication failed for 'https://github.com/xxx.git/' and fatal: clone of 'https://github.com/xxx.git' into submodule path '/codebuild/output/xxx' failedIleum
This + @Personalty guide works perfectly. I can only add that for bitbucket you must specify the HTTPS link to the repo, not the GIT based.Billowy
For Github, you should use HTTPS too, not GIT-based URL.Personalty
This is the way. Thank you so much. Worked using bitbucket cloud. I love that the trio of the credential-helper (ensuring IAM useconnection of course), full clone and submodule update is sufficient.Castora
W
10

I ran into this issue myself and, thanks to the awesome suggestions by @matt-bucci I was able to come up with what seems like a robust solution.

My specific use-case is slightly different - I am using Lambda Layers to reduce lambda redundancy, but still need to include the layers as submodules in the Lambda function repositories so that CodeBuild can build and test PRs. I am also using CodePipeline to assist with continuous delivery - so I need a system that works with both CodePipeline and CodeBuild by itself

  1. I created a new SSH key for use by a "machine user" following these instructions. I am using a machine user in this case so that a new ssh key doesn't need to be generated for every project, as well as for potential support of multiple private submodules

  2. I stored the private key in the AWS Parameter Store as a SecureString. This doesn't actually change anything within CodeBuild, since it's smart enough to just know how to decrypt the key

  3. I gave the "codebuild" role AWS managed property: AmazonSSMReadOnlyAccess - allowing CodeBuild to access the private key

  4. I made my buildspec.yml file, using a bunch of the commands suggested by @matt-bucci, as well as some new ones

# This example buildspec will enable submodules for CodeBuild projects that are both 
# triggered directly and via CodePipeline
#
# This buildspec is designed with help from Stack Overflow: 
# https://mcmap.net/q/790273/-how-to-auto-deploying-git-repositories-with-submodules-on-aws
version: 0.2  # Always use version 2
env:
  variables:
    # The remote origin that will be used if building through CodePipeline
    remote_origin: "[email protected]:your/gitUri"
  parameter-store:
    # The SSH RSA Key used by our machine user
    ssh_key: "ssh_key_name_goes_here"
phases:
  install:
    commands:
      # Add the "machine user's" ssh key and activate it - this allows us to get private (sub) repositories
      - mkdir -p ~/.ssh                   # Ensure the .ssh directory exists
      - echo "$ssh_key" > ~/.ssh/ssh_key  # Save the machine user's private key
      - chmod 600 ~/.ssh/ssh_key          # Adjust the private key permissions (avoids a critical error)
      - eval "$(ssh-agent -s)"            # Initialize the ssh agent
      - ssh-add ~/.ssh/ssh_key            # Add the machine user's key to the ssh "keychain"
      # SSH Credentials have been set up. Check for a .git directory to determine if we need to set up our git package
      - |
        if [ ! -d ".git" ]; then
          git init                                              # Initialize Git
          git remote add origin "$remote_origin"                # Add the remote origin so we can fetch
          git fetch                                             # Get all the things
          git checkout -f "$CODEBUILD_RESOLVED_SOURCE_VERSION"  # Checkout the specific commit we are building
        fi
      # Now that setup is complete, get submodules
      - git submodule init
      - git submodule update --recursive
      # Additional install steps... (npm install, etc)
  build:
    commands:
      # Build commands...
artifacts:
  files:
    # Artifact Definitions...

This install script performs three discrete steps

  1. It installs and enables the ssh private key used to access private repositories

  2. It determines if there is a .git folder - if there isn't then the script will initialize git and checkout the exact commit that is being built. Note: According to the AWS docs, the $CODEBUILD_RESOLVED_SOURCE_VERSION envar is not guranteed to be present in CodePipeline builds. However, I have not seen this fail

  3. Finally, it actually gets the submodules

Obviously, this is not a great solution to this problem. However, it's the best I can come up with given the (unnecessary) limitations of CodePipeline. A side effect of this process is that the "Source" CodePipeline stage is completely worthless, since we just overwrite the archived source files - it's only used to listen for changes to the repository

Better functionality has been requested for over 2 years now: https://forums.aws.amazon.com/thread.jspa?threadID=248267

Edited January, 23, 2019

I realized (the hard way) that my previous response didn't support CodePipeline builds, only builds run through CodeBuild directly. When CodeBuild responds to a GitHub Webhook, it will clone the entire GitHub repository, including the .git folder

However, when using CodePipeline, the "Source" action will clone the repository, check out the appropriate branch, then artifact the raw files without the .git folder. This means that we do have to initialize the github repository to get access to submodules

Weedy answered 23/1, 2019 at 0:1 Comment(1)
What if ssh-add requires passphrase to be entered? How can it be automated/bypassed?Koan
D
6

I faced the same issue on AWS CodeBuild. I tick Use Git submodules like below image to update my submodule.

enter image description here

When I run the build I got following error,

CLIENT_ERROR: Submodule error error creating SSH agent: "SSH agent requested but SSH_AUTH_SOCK not-specified" for primary source and source version refs/heads/dev

So I googled the above error and got this DOWNLOAD_SOURCE Fails with Git submodules thread from AWS Forum. They've mentioned,

The submodules must be configured as https and not ssh.

I think this is useless, what will happen someone setup submodule as ssh. I also did the same, here is my .gitmodules file.

[submodule "common"]
    path = common
    url = [email protected]:organization_id/common.git

Really I don't want to change it to https. Then I found this Working with Git Submodules in CodePipeline article from medium. I would like to visualize what I did to solve this issue and there was an error that didn't mention in that article. Let's do this in more secure way.


First go to the AWS Key Management Service (KMS) and go to the Customer managed keys section and click the Create key to create the key.

  1. Click the Symmetric and click Next.

enter image description here

  1. Give any name (ex:- bitbucket-credentials) to create Alias and click Next.

enter image description here

  1. Most probably you already have an AWS Role to configure any of Developer Tools on AWS, so in my case I created a AWS Role call ecsCodeBuildRole for AWS CodeBuild and give the Define key administrative permissions for it and click Next.

enter image description here

  1. Next give the Define key usage permissions for your AWS Role and click Next.

enter image description here

  1. Finally review what you did so far and and click Finish to create the CMK.

enter image description here

  1. You can review it like below.

enter image description here


So AWS Key Management Service (KMS) part is done, now go the AWS Systems Manager and find the Parameter Store section. Click Create parameter.

  1. Just name it as id_rsa and put the same things like below.

enter image description here enter image description here

  1. For the value section, just run cat ~/.ssh/id_rsa command in your terminal and you'll get the output like below. Add it to the value section.
-----BEGIN RSA PRIVATE KEY-----
qdjbXp+42VTnccC7pxOZcofomfwGXPWuqcv99sQEPtToODvGIxWoooJUpb6qMIWY
1zccEuwAhmqcPvpsJyWhcctZB/wWglNvViZcOYjrQ8HBUBKJT8pF
-----END RSA PRIVATE KEY-----
  1. Create another parameter and name it as id_rsa.pub. Follow the same steps like above.

  2. For the value section, just run cat ~/.ssh/id_rsa.pub command in your terminal and you'll get the output like below. Add it to the value section.

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGtf8jjkogWxRGNGjJlOZ1G+pWExgDOdA5wVML6TMTT2YtvhPJD60nPx5TfA8zRzGWubwrPp40SPAhSs5wiAAg38HlS4pz9X wasdkiller@wasdkiller

As my research(maybe I'm wrong, please correct me) I don't have any other way to pass ssh credentials to the AWS CodeBuild without this much of effort. So I changed my buildspec.yml file manually like this.

version: 0.2

env:
  parameter-store:
    ssh_key: id_rsa
    ssh_pub: id_rsa.pub

phases:
  install:
    commands:
      - mkdir -p ~/.ssh
      - echo "$ssh_key" > ~/.ssh/id_rsa   
      - echo "$ssh_pub" > ~/.ssh/id_rsa.pub
      - chmod 600 ~/.ssh/id_rsa
      - eval "$(ssh-agent -s)"
      - git submodule update --init --recursive

When you continue you'll get below error surely,

Decrypted Variables Error: AccessDeniedException: User: arn:aws:sts::organization_id:assumed-role/ecsCodeBuildRole/AWSCodeBuild-12896abb-bdcf-4cfc-a12b-bcf30d6e96ab is not authorized to perform: ssm:GetParameters on resource: arn:aws:ssm:ap-southeast-2:organization_id:parameter/wasd status code: 400, request id: 23b94bc2-961e-4d86-9b73-d16e3bda357c

It'll ask you for ssm:GetParameters permission, Just attach AmazonSSMReadOnlyAccess policy or create policy manually with the ssm:GetParameters permission and attach it to your AWS Role, it'll solve this issue.

Dercy answered 24/5, 2020 at 0:9 Comment(2)
Thanks for this it worked perfectly - I also had to add - ssh-keygen -F github.com || ssh-keyscan github.com >>~/.ssh/known_hosts to get it to connect to the github repo. CheersRetinitis
You're welcome @JoeKeene, glad to hear that.Dercy
A
5

While @MattBucci answer works, it has the caveat that you can only pull a specific branch, and not the specific commit that the submodule is using.

In order to handle that case, which is likely when using submodules, there are multiple things that needs to be done:

1) Create a git pre-commit hook with the following content:

#!/bin/bash

#   This file is used in post-commit hook
#   if .commit exists you know a commit has just taken place but a post-commit hasn't run yet
#
touch .commit

If you already have one, you can add that line at the beginning.

2) Create a git post-commit hook with the following contnet:

#!/bin/bash


DIR=$(git rev-parse --show-toplevel);

if [[ -e $DIR/.commit ]]; then
    echo "Generating submodule integrity file"
    rm .commit

    SUBMODULE_TRACKING_FILE=$DIR/.submodule-hash
    MODULE_DIR=module
    #   Get submodule hash, this will be used by AWS Code Build to pull the correct version.
    #   AWS Code Build does not support git submodules at the moment
    #   https://forums.aws.amazon.com/thread.jspa?messageID=764680#764680
    git ls-tree $(git symbolic-ref --short HEAD) $MODULE_DIR/ | awk '{ print $3 }' > $SUBMODULE_TRACKING_FILE

    git add $SUBMODULE_TRACKING_FILE
    git commit --amend -C HEAD --no-verify
fi

exit 0

This hook will put the current commit hash into .submodule-hash file, this file needs to be committed to version control.

3) Go to your AWS Code build project

Developer Tools > CodeBuild > Build projects > YOUR_PROJECT > Edit Environment

Add an environment variable called: GIT_KEY, and the value will be the ssh key base 64 encoded. (Without line breaks, otherwise it won't work).

You can convert it online, or use any tool or programming language.

enter image description here

4) On your buildspec.yml add a pre_build script.

version: 0.2

phases:
  pre_build:
    commands:
      - bash build/aws-pre-build.sh
...

5) Create build/aws-pre-build.sh with the following content:

#!/bin/bash

set -e

#   Get root path
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"

MODULE_HASH=$(cat $DIR/.submodule-hash);
GIT_HOST=bitbucket.org
MODULE_DIR=module
REPO=user/repo.git


if [[ ! -d ~/.ssh ]]; then
    mkdir ~/.ssh
fi

if [[ ! -f ~/.ssh/known_hosts ]]; then
    touch ~/.ssh/known_hosts
fi

#   Base64 decode private key, and save it to ~/.ssh/git
echo "- Adding git private key"

echo $GIT_KEY | base64 -d > ~/.ssh/git

#   Add correct permissions to key
chmod 600 ~/.ssh/git

#   Add $GIT_HOST to ssh config
echo "- Adding ssh config file"

cat > ~/.ssh/config <<_EOF_
Host $GIT_HOST
    User git
    IdentityFile ~/.ssh/git
    IdentitiesOnly yes
_EOF_

#   Check if host is present in known_hosts
echo "- Checking $GIT_HOST in known_hosts"

if ! ssh-keygen -F $GIT_HOST > /dev/null; then
    echo "- Adding $GIT_HOST to known hosts"
    ssh-keyscan -t rsa $GIT_HOST >> ~/.ssh/known_hosts
fi

#   AWS Code build does not send submodules, remove the empty folder
rm -rf $MODULE_DIR

# Clone submodule in the right folder
git clone git@$GIT_HOST:$REPO $MODULE_DIR

# cd to submodule
cd $DIR/$MODULE_DIR

# Checkout the right commit
echo "- Checking out $MODULE_HASH"

git checkout $MODULE_HASH


Extras

If you have an extra step before going to AWS Code Build, something like bitbucket pipelines or similar, you can check that the actual git submodule hash, matches the hash from the generated file: .submodule-hash.

If it does not match, it means who ever pushed, didn't have the git hook.

#!/bin/bash

$MODULE_DIR=module

echo "- Checking submodules integrity"

SUBMODULE_TRACKING_FILE=.submodule-hash


#   Check submodule hash, this will be used by AWS Code Build to pull the correct version.
#   AWS Code Build does not support git submodules at the moment
#   https://forums.aws.amazon.com/thread.jspa?messageID=764680#764680

#   Git submodule actual hash
SUBMODULE_HASH=$(git ls-tree $(git symbolic-ref --short HEAD) $MODULE_DIR/ | awk '{ print $3 }')

if [[ ! -e $SUBMODULE_TRACKING_FILE ]]; then

    echo "ERROR: $SUBMODULE_TRACKING_FILE file not found."
    submoduleError

    exit 1
fi

#   Custom submodule hash - The is used by AWS Code Build
SUBMODULE_TRACKING_FILE_HASH=$(cat $SUBMODULE_TRACKING_FILE)

if [[ "$SUBMODULE_TRACKING_FILE_HASH" != "$SUBMODULE_HASH"  ]]; then

    echo "ERROR: $SUBMODULE_TRACKING_FILE file content does not match submodule hash: $SUBMODULE_HASH"

    echo -e "\tYou should have pre-commit && post-commit hook enabled or update $SUBMODULE_TRACKING_FILE manually:"
    echo -e "\tcmd: git ls-tree $(git symbolic-ref --short HEAD) $MODULE_DIR/ | awk '{ print \$3 }' > $SUBMODULE_TRACKING_FILE"

    exit 1
fi

NOTE: You can also create that file on the pipeline before AWS Code Build, create a commit, tag it, and push it so the AWS Code Build pipeline begins.

git ls-tree $(git symbolic-ref --short HEAD) module/ | awk '{ print \$3 }' > .submodule-hash
Anetta answered 24/1, 2019 at 14:5 Comment(0)
M
3

SSH is not needed if you're using CodeCommit as a repository. Use the AWS CLI Credential Helper and clone over https.

git config --global credential.helper '!aws codecommit credential-helper $@'
git config --global credential.UseHttpPath true
git clone https://git-codecommit.[region].amazonaws.com/v1/repos/[repo]
Mathew answered 14/5, 2019 at 3:40 Comment(0)
C
0

It has been so long since the issue with submodules was found. But aws cannot fix it. So codepipeline cannot send .git directory to codebuild. So we have to invent new bicecles, my fix is this in buildspec.yml pre-build command

rm -rf $PWD/*
git clone --depth 1 https://<REPO NAME>  -b develop .
git submodule update --init --recursive

AWS hurry up, cuz our team is considering move back to github.

Croupier answered 15/8, 2019 at 10:26 Comment(0)
C
0

Borrowing from the concepts of @MattBucci and @JoshuaEvans. Here is what we did. We could not install git in our pipeline, because of strict access/security issues with ssh. So we ended up doing this thru HTTP instead.

  1. I created a new Github Personal Access Token
  2. I stored the PAT in the AWS Secrets manager as a SecureString. This doesn't actually change anything within CodeBuild, since it's smart enough to just know how to decrypt the key
  3. I gave the "codebuild" role to read from Secrets manager
  4. I made my buildspec.yml file, using a bunch of the commands suggested by @matt-bucci and @JoshuaEvans, as well as some new ones
version: 0.2
env:
    variables:
      token: " token "
    secrets-manager:
      personalAccessToken: $personalAccessTokenPath
phases:
    install:
      runtime-versions:
        nodejs: 12
      commands:
          - cd <to the folder where you want the submodules>
          - wget --header="Authorization:$token$personalAccessToken" --content-disposition  https://github.com/uri/zipball/$branch/
          - unzip project*.zip -d project-folder
          - rm -rf project*.zip
          - cd project-folder
          - mv project*/* .
          - rm -rf project*
          - cd <back to your base folder where the buildspec is>
    pre_build:
      commands:
        - xxx
    build:
      commands:
        - xxx
    post_build:
      commands:
        - xxx
artifacts:
    files:
      - '**/*'
    base-directory: dist

Hope this helps!

Chindwin answered 27/3, 2020 at 12:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.