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

LibGit2 callback error in automerge for private registry #245

Closed
ararslan opened this issue May 6, 2020 · 11 comments · Fixed by #306
Closed

LibGit2 callback error in automerge for private registry #245

ararslan opened this issue May 6, 2020 · 11 comments · Fixed by #306
Labels

Comments

@ararslan
Copy link
Member

ararslan commented May 6, 2020

With an automerge.yaml set up nearly identically to that used in General, we've been getting the following consistently on our private registry:

    Cloning registry from "https://github.com/JuliaRegistries/General.git"
25l    Fetching: [>                                        ]  0.0 %
    Fetching: [=================>                       ]  41.9 %
    Resolving Deltas: [================================>        ]  79.9 %
      Added registry `General` to `/tmp/jl_e18Pfl/registries/General`
25hTest Summary:                                | Pass  Total
(Registry|Package|Versions|Deps|Compat).toml |  287    287
[ Info: Authenticated to GitHub as "github-actions[bot]"
[ Info: Attempting to clone...
[ Info: Attempting to clone...
ERROR: GitError(Code:EUSER, Class:Callback, Aborting, user cancelled credential request.)
Stacktrace:
 [1] macro expansion at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/LibGit2/src/error.jl:101 [inlined]
 [2] clone(::String, ::String, ::LibGit2.CloneOptions) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/LibGit2/src/repository.jl:459
 [3] clone(::String, ::String; branch::String, isbare::Bool, remote_cb::Ptr{Nothing}, credentials::Nothing, callbacks::Dict{Symbol,Tuple{Ptr{Nothing},Any}}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/LibGit2/src/LibGit2.jl:580
 [4] clone at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/LibGit2/src/LibGit2.jl:559 [inlined]
 [5] _clone_repo_into_dir(::String, ::String) at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/util.jl:25
[6] #111 at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/util.jl:16 [inlined]
 [7] (::Base.var"#58#60"{Base.var"#58#59#61"{ExponentialBackOff,Nothing,RegistryCI.AutoMerge.var"#111#113"{String,String}}})(; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at ./error.jl:301
 [8] #58 at ./error.jl:284 [inlined]
 [9] my_retry at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/api_rate_limiting.jl:2 [inlined] (repeats 2 times)
 [10] clone_repo(::String) at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/util.jl:16
 [11] clone_repo at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/util.jl:10 [inlined]
 [12] pull_request_build(::GitHub.PullRequest, ::String, ::GitHub.Repo, ::String; auth::GitHub.OAuth2, authorized_authors::Array{String,1}, authorized_authors_special_jll_exceptions::Array{String,1}, master_branch::String, master_branch_is_default_branch::Bool, suggest_onepointzero::Bool, whoami::String, registry_deps::Array{String,1}) at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/pull-requests.jl:88
 [13] pull_request_build(::Int64, ::String, ::GitHub.Repo, ::String; whoami::String, auth::GitHub.OAuth2, authorized_authors::Array{String,1}, authorized_authors_special_jll_exceptions::Array{String,1}, master_branch::String, master_branch_is_default_branch::Bool, suggest_onepointzero::Bool, registry_deps::Array{String,1}) at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/pull-requests.jl:42
[14] run(::Base.EnvDict, ::RegistryCI.AutoMerge.GitHubActions; merge_new_packages::Bool, merge_new_versions::Bool, new_package_waiting_period::Minute, new_jll_package_waiting_period::Minute, new_version_waiting_period::Minute, new_jll_version_waiting_period::Minute, registry::String, authorized_authors::Array{String,1}, authorized_authors_special_jll_exceptions::Array{String,1}, additional_statuses::Array{String,1}, additional_check_runs::Array{String,1}, master_branch::String, master_branch_is_default_branch::Bool, suggest_onepointzero::Bool, registry_deps::Array{String,1}) at /home/runner/.julia/packages/RegistryCI/ZPKNe/src/AutoMerge/public.jl:50
 [15] top-level scope at none:5
Username for 'https://github.com': Username for 'https://github.com': 
##[error]Process completed with exit code 1.
@ericphanson
Copy link
Member

Just to see, I tried running the integration tests with my test repo set to private, and they all passed. I got warnings like

┌ Warning: Some registries failed to update:
│     — `/tmp/jl_PT0rWD/registries/ExampleRegistry` — registry detached

though.

To me, the issue looks like LibGit2 not picking up the credentials properly when trying to clone in this line:

LibGit2.clone(url, repo_dir)

Not sure why that is, but also it seems like the registry gets cloned anyway by

- uses: actions/checkout@af513c7a016048ae468971c52ed77d9562c7c819 # v1.0.0
, so maybe we don't need to clone it again in
registry_master = clone_repo(registry)
?

@DilumAluthge
Copy link
Member

actions/checkout gives us the registry at the head of the PR branch.

We also need the latest master of the registry. We can't get that from actions/checkout.

I guess we need a way to pass credentials when doing clones.

@ararslan
Copy link
Member Author

ararslan commented May 8, 2020

Maybe something like

git config --global credential.helper store
echo "https://github-actions:${{ secrets.GITHUB_TOKEN }}@github.com" > ~/.git-credentials

would work? Not super safe, since someone could theoretically submit a PR that adds a cat ~/.git-credentials, which I believe GitHub does not redact from the build log.

@ararslan
Copy link
Member Author

ararslan commented May 8, 2020

@omus may have thoughts or insight regarding git credential helpers that could be useful here.

@jrevels
Copy link

jrevels commented May 15, 2020

We also need the latest master of the registry. We can't get that from actions/checkout.

Can't we though? Seems like this is doable just reading https://github.com/actions/checkout, but I'm new to GitHub Actions in general so I could be misunderstanding.

Seems like clone_repo only has a single callsite, where it's used to grab the registry's master branch. Could we just remove that utility entirely, grab master via actions/checkout, and have the former clone_repo callsite reference the repo location given in the automerge.yml? Here's a naive patch for this proposal:

diff --git a/example_github_workflow_files/automerge.yml b/example_github_workflow_files/automerge.yml
index aaf42fd..cbf8559 100644
--- a/example_github_workflow_files/automerge.yml
+++ b/example_github_workflow_files/automerge.yml
@@ -15,6 +15,9 @@ jobs:
         os: [ubuntu-latest]
     steps:
       - uses: actions/checkout@af513c7a016048ae468971c52ed77d9562c7c819 # v1.0.0
+      - uses: actions/checkout@v2
+        with:
+          ref: master
+          path: registry-repo-master
       - uses: julia-actions/setup-julia@082493e5c5d32c1fa68c35556429b0f1b2807453 # v1.0.1
         with:
           version: ${{ matrix.julia-version }}
diff --git a/src/AutoMerge/pull-requests.jl b/src/AutoMerge/pull-requests.jl
index 86d987a..e82efa8 100644
--- a/src/AutoMerge/pull-requests.jl
+++ b/src/AutoMerge/pull-requests.jl
@@ -54,6 +54,9 @@ function pull_request_build(pr_number::Integer,
     return result
 end

+# defined in `automerge.yml`
+const REGISTRY_REPO_MASTER_WORKSPACE_LOCATION = "registry-repo-master"
+
 function pull_request_build(pr::GitHub.PullRequest,
                             current_pr_head_commit_sha::String,
                             registry::GitHub.Repo,
@@ -67,42 +70,19 @@ function pull_request_build(pr::GitHub.PullRequest,
                             whoami::String,
                             registry_deps::Vector{<:AbstractString} = String[])::Nothing
     if is_new_package(pr)
-        registry_master = clone_repo(registry)
-        if !master_branch_is_default_branch
-            checkout_branch(registry_master, master_branch)
-        end
-        pull_request_build(NewPackage(),
-                           pr,
-                           current_pr_head_commit_sha,
-                           registry;
-                           auth = auth,
-                           authorized_authors=authorized_authors,
-                           authorized_authors_special_jll_exceptions=authorized_authors_special_jll_exceptions,
-                           registry_head = registry_head,
-                           registry_master = registry_master,
-                           suggest_onepointzero = suggest_onepointzero,
-                           whoami=whoami,
-                           registry_deps = registry_deps)
-        rm(registry_master; force = true, recursive = true)
-    elseif is_new_version(pr)
-        registry_master = clone_repo(registry)
-        if !master_branch_is_default_branch
-            checkout_branch(registry_master, master_branch)
-        end
-        pull_request_build(NewVersion(),
-                           pr,
-                           current_pr_head_commit_sha,
-                           registry;
-                           auth = auth,
-                           authorized_authors=authorized_authors,
-                           authorized_authors_special_jll_exceptions=authorized_authors_special_jll_exceptions,
-                           registry_head = registry_head,
-                           registry_master = registry_master,
-                           suggest_onepointzero = suggest_onepointzero,
-                           whoami=whoami,
-                           registry_deps = registry_deps)
-        rm(registry_master; force = true, recursive = true)
+        kind = NewPackage()
+    elseif is_new_version(pr)
+        kind = NewVersion()
     else
         throw(AutoMergeNeitherNewPackageNorNewVersion("Neither a new package nor a new version. Exiting..."))
     end
+    pull_request_build(kind, pr, current_pr_head_commit_sha, registry;
+                       auth=auth,
+                       authorized_authors=authorized_authors,
+                       authorized_authors_special_jll_exceptions=authorized_authors_special_jll_exceptions,
+                       registry_head=registry_head,
+                       registry_master=REGISTRY_REPO_MASTER_WORKSPACE_LOCATION,
+                       suggest_onepointzero=suggest_onepointzero,
+                       whoami=whoami,
+                       registry_deps=registry_deps)
 end
diff --git a/src/AutoMerge/util.jl b/src/AutoMerge/util.jl
index e4fb89f..468aadd 100644
--- a/src/AutoMerge/util.jl
+++ b/src/AutoMerge/util.jl
@@ -7,25 +7,6 @@ function checkout_branch(dir::AbstractString,
     cd(original_working_directory)
 end

-clone_repo(repo::GitHub.Repo) = clone_repo(repo_url(repo))
-
-function clone_repo(url::AbstractString)
-    parent_dir = mktempdir()
-    atexit(() -> rm(parent_dir; force = true, recursive = true))
-    repo_dir = joinpath(parent_dir, "REPO")
-    my_retry(() -> _clone_repo_into_dir(url, repo_dir))
-    @info("Clone was successful")
-    return repo_dir
-end
-
-function _clone_repo_into_dir(url::AbstractString, repo_dir)
-    @info("Attempting to clone...")
-    rm(repo_dir; force = true, recursive = true)
-    mkpath(repo_dir)
-    LibGit2.clone(url, repo_dir)
-    return repo_dir
-end
-
 function _comment_disclaimer()
     result = string("\n\n",
                     "Note that the guidelines are only required for the pull request ",

I can make a PR with this patch if this is the right approach.

@DilumAluthge
Copy link
Member

In the past, I have run into the issue where actions/checkout does not give me the latest master.

The only way I was able to fix it is by manually cloning the latest master myself.

The solution here is for us to remove the use of LibGit2 and instead call command-line git.

Then e.g. you can add an ssh key with the appropriate read permissions, and add it to ssh agent, and then when I use command-line git to clone, it will work.

@jrevels
Copy link

jrevels commented May 15, 2020

Is there a reference to that actions/checkout bug?

Regardless, if actions/checkout can't be used for this, would there be a security concern with simply interpolating the GITHUB_TOKEN into the url at the LibGit2.clone callsite? Seems like a more minimal change if it's reasonable to do so

@jrevels
Copy link

jrevels commented May 15, 2020

Just dogfooded the patch above over at Beacon's private registry; it does seem to resolve the issue in the OP + clone master correctly (though maybe I just got lucky, if it's an intermittent issue that Dilum was referring to), but there's still a remaining issue of propagating read credentials to Pkg itself.

To resolve that, we're applying the workflow step suggested by @ararslan above which configures the git credential helper with secrets.BEACON_SPECIFIC_READ_TOKEN, where we added BEACON_SPECIFIC_READ_TOKEN as a GitHub Actions Secret. Will report back on how that goes.

@jrevels
Copy link

jrevels commented May 16, 2020

So, in the end, the above worked, but we had to make the following additional patch in our fork of RegistryCI or else we still got ERROR: GitError(Code:EUSER, Class:Callback, Aborting, user cancelled credential request.) when running AutoMerge.run:

diff --git a/src/AutoMerge/guidelines.jl b/src/AutoMerge/guidelines.jl
index 03e3fee..885d306 100644
--- a/src/AutoMerge/guidelines.jl
+++ b/src/AutoMerge/guidelines.jl
@@ -310,31 +310,14 @@ function _run_pkg_commands(working_directory::String,
                            failure_message,
                            failure_return_1,
                            failure_return_2)
-    original_directory = pwd()
-    tmp_dir_1 = mktempdir()
-    tmp_dir_2 = mktempdir()
-    atexit(() -> rm(tmp_dir_1; force = true, recursive = true))
-    atexit(() -> rm(tmp_dir_2; force = true, recursive = true))
-    cd(tmp_dir_1)
-    # We need to be careful with what environment variables we pass to the child
-    # process. For example, we don't want to pass an environment variable containing
-    # our GitHub token to the child process. Because if the Julia package that we are
-    # testing has malicious code in its __init__() function, it could try to steal
-    # our token. So we only pass four environment variables:
-    # 1. PATH. If we don't pass PATH, things break. And PATH should not contain any
-    #    sensitive information.
-    # 2. PYTHON. We set PYTHON to the empty string. This forces any packages that use
-    #    PyCall to install their own version of Python instead of using the system
-    #    Python.
-    # 3. JULIA_DEPOT_PATH. We set JULIA_DEPOT_PATH to the temporary directory that
-    #    we created. This is because we don't want the child process using our
-    #    real Julia depot. So we set up a fake depot for the child process to use.
-    # 4. R_HOME. We set R_HOME to "*".
-    cmd = Cmd(`$(Base.julia_cmd()) -e $(code)`;
-              env = Dict("PATH" => ENV["PATH"],
-                         "PYTHON" => "",
-                         "JULIA_DEPOT_PATH" => tmp_dir_2,
-                         "R_HOME" => "*"))
+    # XXX: upstream RegistryCI.jl passes `ENV` here with only a few restricted
+    # variables to disallow the to-be-registered packages from escalating
+    # priveleges/stealing tokens from within their `__init__` function and
+    # enforcing better e.g. Python dependency management. However, this also
+    # breaks git credential setup for private registries. Since Beacon's registry
+    # isn't publically available anyway, this isn't really a problem for us, so
+    # we simply patched it out to allow us to use this privately....
+    cmd = Cmd(`$(Base.julia_cmd()) -e $(code)`)
     # GUI toolkits may need a display just to load the package
     xvfb = Sys.which("xvfb-run")
     @info("xvfb: ", xvfb)
@@ -344,9 +327,6 @@ function _run_pkg_commands(working_directory::String,
     end
     @info(before_message)
     cmd_ran_successfully = success(pipeline(cmd, stdout=stdout, stderr=stderr))
-    cd(original_directory)
-    rm(tmp_dir_1; force = true, recursive = true)
-    rm(tmp_dir_2; force = true, recursive = true)
     if cmd_ran_successfully
         @info(success_message)
         return success_return_1, success_return_2

This obviously isn't a patch that could be applied to JuliaRegistries/RegistryCI (since it has to run on public non-vetted submissions), but out of curiosity, does anybody have an idea for why stripping the ENV here appears to break our git credential setup and/or requires credentialing to be interactive (which seems to directly result in the failed clones)?

@omus
Copy link
Contributor

omus commented May 16, 2020

I can see how reducing the set environmental variables is causing non-interactive cloning to fail as the git credential helper system calls out to the shell to run helpers (this is just how the interface works).

I think I can assist in debugging this with a little more information. Can you confirm you are attempting to clone via HTTPS? Running the following should show any credential helper details which could/should be used:

git config --show-origin --get-regexp 'credential.*'

@christopher-dG
Copy link
Member

Turns out this is just Git not knowing how to apply its global config (~/.gitconfig) when HOME is unset.

bors bot added a commit that referenced this issue Dec 8, 2020
306: Pass HOME to subprocesses r=christopher-dG a=christopher-dG

Closes #245 

Lots of stuff needs HOME to work properly.

More concretely, both Git CLI and LibGit2 get their config from `$HOME/.gitconfig` by default, and that breaks when it's unset.

Co-authored-by: Chris de Graaf <me@cdg.dev>
bors bot added a commit that referenced this issue Dec 8, 2020
306: Pass HOME to subprocesses r=christopher-dG a=christopher-dG

Closes #245 

Lots of stuff needs HOME to work properly.

More concretely, both Git CLI and LibGit2 get their config from `$HOME/.gitconfig` by default, and that breaks when it's unset.

Co-authored-by: Chris de Graaf <me@cdg.dev>
@bors bors bot closed this as completed in 7f15fef Dec 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants