Appveyor is a great CI service for Windows apps. It’s simple, free (for open-source) and easy to setup. Sometimes even public, open source projects may want to have private subrepositories. Appveyor supports such a setup and in this post I will show you, how to configure private subrepo for mercruial.
The Git way
There already is a good guide for private git subrepos. Let’s try and do the same for mercurial. The git guide references GitHub as hosting platform, and for mercurial I will use BitBucket, wich has similar est of features but support both git and mercurial (and has unlimited number of free private repositories, yay!).
The Hg way
In case of mercurial, the solution is similar to git, but configuriaton may not be as straightforward.
We will split the process in three steps:
- Configure ssh clone on local machine
- Do the same in AppVeyor with an arbitrary repository
- Configure private hg subrepo and check it out in AppVeyor
Cloning HG over SSH (from Bitbucket)
Let’s start with a simple thing: clone a repository over ssh. I’ll use BitBucket for mercurial hosting and Appveyor for cloning and building. BitBucket has a guide on setting up ssh. Unfortunatelly, the Windows guide uses Putty and Pageant for managing SSH keys, which requires a GUI and isn’t commandline-friendly. We cannot use it from Appveyor scripts (plink can also be run in batch mode, but I will stick to plain ssh).
Lucky for me, a similar guide for git doesn’t include putty at all. We can use the same steps to configure mercurials ssh.
-
Install Git for Windows:
> choco install -y git
- Make sure you have
ssh.exe
on PATH (it will most probably be in ‘c:\Program Files\Git\usr\bin’) -
List the content of
$env:USERPROFILE/.ssh
directory> ls $env:USERPROFILE/.ssh
If you have a default identity already, you’ll some id_* files.
-
Generate a ssh key (or use an existing one)
> ssh-keygen -t rsa -b 4096 -C "[email protected]"
-
Set up SSH key on Bitbucket:
- Open a browser and log in to Bitbucket.
- Choose avatar > Bitbucket settings from the menu bar, then click SSH Settings on the left.
- Add a new key. This is a public key, which value is the content of
$env:USERPROFILE/.ssh/id_rsa.pub
(will probably start with “ssh-rsa …”)
-
create a private HG repo and clone it over SSH:
> hg clone ssh://[email protected]/heavymetaldev/top-secret
If you see
remote: Permission denied (publickey).
, then there is something wrong with SSH key, i.e.:- Mercurial doesn’t use the private key from
$env:USERPROFILE/.ssh/id_rsa
- Public SSH key is not properly configured in BitBucket
You can add
--debug
switch to see the commands that are invoked undearneath. You will see that mercurial calls:ssh [email protected] "hg -R heavymetaldev/top-secret serve --stdio"
You can use this command to further debug ssh issues.
- Mercurial doesn’t use the private key from
Private HG subprepos on Appveyor
Knowing that SSH clone works locally, we can configure AppVeyor to do the same.
These are general steps we need to take:
- Generate a new SSH key pair for AppVeyor access to Bitbucket repo
- Save private key in AppVeyor’s encrypted environment variable
In the build script (during install
phase), we need to:
- Extract private key from environment variable to file
$env:USERPROFILE/.ssh/id_rsa
- Add Bitbucket’s SSL certificate fingerprint to
$env:USERPROFILE/.ssh/known_hosts
First, generate a new SSH key that will be used by AppVeyor and add it to Bitbucket (like in the previous paragraph).
> ssh-keygen -t rsa -b 4096 -C "[email protected]" -f "id_rsa_appveyor_top-secret"
Instead of configuring it at account level, add it as read-only access key to specific repo that you will be cloning.
Now, we need to configure the SSH key in AppVeyor. The process is very similar to the git way.
Open the generated private key and copy base-64 body of the key between
-----BEGIN RSA PRIVATE KEY-----
and-----END RSA PRIVATE KEY-----
into clipboard (without these BEGIN / END lines).Copy the contents of private key to clipboard as shown above and open Encrypt data tool in AppVeyor. Encrypt the value of clipboard using that page.
Paste the encrypted value into the build script (or configure it in web UI). It will look something like this:
appveyor.yml
:
environment:
priv_key:
secure: <encryped-value>
subrepo_owner: heavymetaldev
subrepo_name: top-secret
subrepo_branch: default
install:
- ps: .\clone-subrepo.ps1
The additional environment variables (subrepo_*
) are used to determine repository url and branch name to checkout.
clone-subrepo.ps1
is where the real job is done:
# get repo url and branch from env variables
$owner = $env:subrepo_owner
$repoName = $env:subrepo_name
$repo = "$owner/$repoName"
$branch = $env:subrepo_branch
if ($branch -eq $null) {
$branch = "default"
write-host "will use default branch '$branch'"
} else {
write-host "will use configured branch '$branch'"
}
write-host "testing if ssh is available"
get-command "ssh.exe" -ErrorAction Stop
# use ssh.exe available on PATH
'[ui]' | out-file "$env:USERPROFILE/mercurial.ini" -Append -Encoding utf8
'ssh=ssh.exe' | out-file "$env:USERPROFILE/mercurial.ini" -Append -Encoding utf8
# add Bitbucket host fingerprint to known_hosts
$bbhostkey = @"
bitbucket.org,104.192.143.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==
"@
write-host "adding bitbucket to known_hosts"
$bbhostkey | out-file "$env:USERPROFILE/.ssh/known_hosts" -Append -Encoding utf8
# add private key to id_rsa
write-host "adding private key"
$fileContent = "-----BEGIN RSA PRIVATE KEY-----`n"
$fileContent += $env:priv_key.Replace(' ', "`n")
$fileContent += "`n-----END RSA PRIVATE KEY-----`n"
Set-Content "$env:USERPROFILE\.ssh\id_rsa" $fileContent
#clone private repo
write-host "cloning"
hg clone --verbose ssh://[email protected]/$repo $repoName
#update private repo to specified branch, get status
try {
pushd
cd $repoName
write-host "updating to $branch"
hg update $branch
hg summary
$message = hg log -r . -T "{desc}"
$id = hg log -r . -T "{node}"
$ts = hg log -r . -T "{date|isodate}"
$ts = [DateTime]::Parse($ts)
$authorname = hg log -r . -T "{author|person}"
$authormail = hg log -r . -T "{author|email}"
$br = hg log -r . -T "{branch}"
write-host "id:$id branch:$br msg:$message date:$ts author:$authorname mail:$authormail"
}
finally {
popd
}
This is everything you need to get this working. Commit appveyor.yml
and clone-subrepo.ps1
to a new, public repository and add it to appveyor.
Changing Appveyor build info
You may also want to include some information about the status of your subrepo in Appveyor’s build message. Update-AppveyorBuild can update build details. Add the following code to clone-subrepo.ps1
:
if ($env:appveyor -ne $null) {
Update-AppveyorBuild -message "subrepo [$br](https://bitbucket.org/$repo/commits/$id): $message" -Committed $ts -CommitterName $authorname -CommitterEmail $authorEmail
#-CommitId $id
}
A real subrepo
Until now, the inner repository was not a real hg subrepo - the script determined it’s location and branch. Let’s now make it a subrepo and tie the exact revision to parent repository revision.
Add .hgsub
to your public repo (this will be the “parent”):
top-secret = top-secret
[subpaths]
https://bitbucket\.org/([^/]*)/([^/]*)/([^/]*)$ = ssh://[email protected]/\1/\3
ssh://hg@bitbucket\.org/([^/]*)/([^/]*)/([^/]*)$ = ssh://[email protected]/\1/\3
top-secret
is the name of the private repository.
The subpaths
section is needed, because by default mercurial constructs subrepo url by adding it’s name after slash, so we need to remap: https://bitbucket.org/heavymetaldev/appveyor-wrapper/top-secret
to ssh://[email protected]/heavymetaldev/top-secret
. Appveyor clones repos over https, but private subrepo needs to be accessed over ssh.
After commiting, do a clean update:
> hg update -C
This will create top-secret
directory and set it’s default url to ssh://[email protected]/heavymetaldev/top-secret
. Go to top-secret
folder, update the subrepo to desired revision and commit changes in the parent repo.
One last thing we need to do is to move id_rsa
initalization directly to appveyor.yml
, to init
phase. The reason for this is the chicken-egg problem we now have: install
phase takes place after repo clone and update, but mercurial (unlike git) updates all subrepos on parent repo update, so it needs the ssh credentials before doing the update. Fortunatelly, appveyor is clever enough to read appveyor.yml
content before cloning, so it can execute init
script without the repo being checked out.
appveyor.yml
will now look like this (note that we don’t need subrepo_*
ariables any more):
environment:
priv_key:
secure: <encryped-value>
init:
- ps: $fileContent = "-----BEGIN RSA PRIVATE KEY-----`n"
- ps: $fileContent += $env:priv_key.Replace(' ', "`n")
- ps: $fileContent += "`n-----END RSA PRIVATE KEY-----`n"
- ps: Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent
Finally, commit changes and push the parent repo. Appveyor should now detect a new commit and start building. Hopefully, everything will be built smoothly.
Hapy hacking!
Notes and resources
- You can find sample repo at:
https://bitbucket.org/heavymetaldev/appveyor-wrapper
- The build status at https://ci.appveyor.com/project/qbikez/appveyor-wrapper.
- The private repo is at
https://bitbucket.org/heavymetaldev/top-secret
, but you won’t find it there, because, well.. it’s private :)