GitLab and go get
I was trying to run go get
on a git repository hosted on a private GitLab
instance. This repository was stored under a sub-group. I was working from a
non-default branch. Since it is a monorepo the module/package was in a
subdirectory. So a combination of four factors complicated matters for me.
Easiest Solution
These are the first few things we must clarify,
-
go get
,go mod tidy
, andgo mod download
can use https or ssh to fetch Go modules from a git server (like GitLab or GitHub) -
go
will by default fetch modules without first authenticating -
go
needs to fetch subgroups depending on the Go module's name. For example, if the module is called gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo,go
will need to fetch all subgroups of mygroup. -
go
needs to fetch tags and commits from the git server - GitLab has designed private subgroups to not be accessible by unauthenticated users. This includes repositories, tags, commits, etc.
The easiest way to deal with this situation is a combination of,
-
go
must use https not ssh to work with GitLab -
go
must use .netrc stored in the user's home directory - Always use either tag or commit to specify version; never use branch name
-
Always use
go get
because it also updates go.mod and go.sum files - Environment variables GOPROXY and GOPRIVATE must be configured appropriately
Big thanks to Jonathan Hall for this.
Setup
go
will prefer https over ssh (from what I have observed). The next step is
to create a GitLab API read-only (because go
just needs to read)
personal access token.
Create ~/.netrc as below (change values per your environment),
$ export GITLAB_HOSTNAME='<gitlab.my-internal-domain.com>' $ export GITLAB_USERNAME='<your_username>' $ export GITLAB_API_TOKEN='<your_read_only_token>' $ printf 'machine %s\n login %s\n password %s\n\n' $GITLAB_HOSTNAME $GITLAB_USERNAME $GITLAB_API_TOKEN >> ~/.netrc
Read more about environment variables GOPROXY and GOPRIVATE.
go get
With the above setup complete, you can start using various ways to get modules,
$ go get gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo
The above will get the latest from the default branch. If you want to specify a different tag or commit ID, append @ID (where ID is one of tag or commit),
$ go get gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo@tag $ go get gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo@commit-id
I have not had success with using branch names. I always get error message, invalid version: disallowed version string.
Alternative But Finicky Solution
This solution tries to get you going with ssh instead of https.
Let's say the repository URL I was working with was gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo. The go.mod in that repo was
module gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo/src/mygopackage go 1.20
I was trying go get gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo/src/mygopackage
in a separate package, essentially using mygopackage as a dependency. I got
this error,
go: module gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo/src/mygopackage: git ls-remote -q origin in /Users/aikchar/go/pkg/mod/cache/vcs/27f0a3bd58674de4db4e2a3e38601eecc893ec233ba6addcf6eeb2b43f240ef9: exit status 128: fatal: could not read Username for 'https://gitlab.my-internal-domain.com': terminal prompts disabled Confirm the import path was entered correctly. If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.
I figured I made a mistake. I needed to add .git to my go.mod in myrepo so gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo/src/mygopackage becomes gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage.
After fixing go.mod in gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage, it was,
module gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage go 1.20
I ran go get gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage
and got this error,
go: downloading gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git v0.0.0-20220303202727-f334bf068bdb go: gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage: gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git@v0.0.0-20220303202727-f334bf068bdb: verifying module: gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git@v0.0.0-20220303202727-f334bf068bdb: reading https://sum.golang.org/lookup/gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git@v0.0.0-20220303202727-f334bf068bdb: 410 Gone server response: not found: gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git@v0.0.0-20220303202727-f334bf068bdb: invalid version: git ls-remote -q origin in /tmp/gopath/pkg/mod/cache/vcs/b9eaa09fa03cd0f3139730aa61579fcd85cd6fd748674c4aa228cd6a57e99b31: exit status 128: fatal: unable to look up gitlab.my-internal-domain.com (port 9418) (Name or service not known)
I added @commit-id
to go get
like so: go get gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage@e091a5ea01d7
and got this error,
gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage@v0.0.0-20220531210353-e091a5ea01d7: verifying module: gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage@v0.0.0-20220531210353-e091a5ea01d7: reading https://sum.golang.org/lookup/gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage@v0.0.0-20220531210353-e091a5ea01d7: 410 Gone server response: not found: gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage@v0.0.0-20220531210353-e091a5ea01d7: invalid version: git ls-remote -q origin in /tmp/gopath/pkg/mod/cache/vcs/b9eaa09fa03cd0f3139730aa61579fcd85cd6fd748674c4aa228cd6a57e99b31: exit status 128: fatal: unable to look up gitlab.my-internal-domain.com (port 9418) (Name or service not known)
I added environment variable GOPRIVATE and ran this command: GOPRIVATE=gitlab.my-internal-domain.com go get gitlab.my-internal-domain.com/mygroup/mysubgroup/myrepo.git/src/mygopackage@e091a5ea01d7
and IT WORKED!
go: added gitlab.my-internal-domain.commygroup/mysubgroup/myrepo.git/src/mygopackage v0.0.0-20220531210353-e091a5ea01d7
Basically, to counter the four factors I mentioned at the top, I needed to do this,
- Make sure .git is part of the module name and during import or
go get
. This is not always possible to do. - Add commit-id (NOT branch name) at the end of
go get
when getting from non-default branch - Since GitLab is private and not publically accessible, set the GOPRIVATE environment variable to the right hostname
Let's add one more factor: the GitLab server is not reachable over port 22 for ssh but instead uses a custom port. How about one more factor i.e. anonymous, non-logged in read access is not allowed; you need to login to even read a repository.
The symptom of the second new factor, no anonymous read allowed, is an error message like fatal: could not read Username for 'https://gitlab.my-internal-domain.com': terminal prompts disabled.
The fix for this factor is to use ssh instead of http or https to connect to GitLab. This way we can setup ssh keys to work seamlessly without needing a human to enter username and password to login. We will use git config's insteadOf option (thanks to Set GOPRIVATE to match your github organization).
$ git config --global url."ssh://git@gitlab.my-internal-domain.com".insteadOf "https://gitlab.my-internal-domain.com"
Remember the other factor we wanted to consider i.e. ssh is accessible on a non-standard port e.g. 2222? Modify the config to include the port,
$ git config --global url."ssh://git@gitlab.my-internal-domain.com:2222".insteadOf "https://gitlab.my-internal-domain.com"
One more thing to possibly cause issues is that regardless of the configrations
discussed above, go get
will use http(s) instead of ssh. I couldn't find a
good solution for it. A workaround is to always use go mod tidy
instead.
Another less-than-optimial workaround is to modify the module name by appending
.git. This has additional effects which may make this impossible in most (in
my opinion) cases. This ties into the next point as well.
You may need to use both require and replace in go.mod. I had a situation
where the module I wanted to pull in was named
gitlab.my-internal-domain.com/group/subgroup/module and the git repository was
in a sub-group like
gitlab.my-internal-domain.com:2222/group/subgroup/module.git. When I tried
go get
or even go mod tidy
(with debug flag -x
) it showed that instead of
pulling module.git it was pulling subgroup.git. That was a real surprise. The
fix for it was to add the following lines in go.mod (v0.4.7 is the
version/tag of the module I needed, change it to suit your situation),
require gitlab.my-internal-domain.com/group/subgroup/module v0.4.7 replace gitlab.my-internal-domain.com/group/subgroup/module => gitlab.my-internal-domain.com/group/subgroup/module.git v0.4.7
What the above does is when you run go mod tidy
it will treat module.git as
the git respository instead of assuming subgroup was the git repository. I
can't find the Stack Overflow post anymore but it said that GitLab doesn't give
visibility to repositories in subgroups to go get
so when you have
require gitlab.my-internal-domain.com/group/subgroup/module
in go.mod then
go get
assumes submodule.git is the repository and module/ is the
directory/path to the go.mod in it. But with replace and go mod tidy
it
all works like it should. To repeat, in this situation, always use
go mod tidy
because go get
will not work (it didn't for me with Go 1.20.3).