/
hex.repo.ex
220 lines (167 loc) · 5.29 KB
/
hex.repo.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
defmodule Mix.Tasks.Hex.Repo do
use Mix.Task
@shortdoc "Manages Hex repositories"
@moduledoc """
Manages the list of available Hex repositories.
The repository is where packages and the registry of packages is stored.
You can fetch packages from multiple different repositories and packages
can depend on packages from other repositories. To use a package from another
repository than the global default `hexpm` add `repo: "my_repo"` to the
dependency declaration in `mix.exs`:
{:plug, "~> 1.0", repo: "my_repo"}
By default all dependencies of plug will also be fetched from `my_repo`
unless plug has declared otherwise in its dependency definition.
To use packages from `my_repo` you need to add it to your configuration
first. You do that by calling `mix hex.repo add my_repo https://myrepo.example.com`.
The default repo is called `hexpm` and points to https://repo.hex.pm. This
can be overridden by using `mix hex.repo set ...`.
A repository configured from an organization will have `hexpm:` prefixed to
its name. To depend on packages from an organization add `repo: "hexpm:my_organization"`
to the dependency declaration or simply `organization: "my_organization"`.
To configure organizations, see the `hex.organization` task.
## Add a repo
mix hex.repo add NAME URL
### Command line options
* `--public-key PATH` - Path to public key used to verify the registry (optional).
* `--auth-key KEY` - Key used to authenticate HTTP requests to repository (optional).
## Set config for repo
mix hex.repo set NAME --url URL
mix hex.repo set NAME --public-key PATH
mix hex.repo set NAME --auth-key KEY
## Remove repo
mix hex.repo remove NAME
## Show repo config
mix hex.repo show NAME
## List all repos
mix hex.repo list
"""
@behaviour Hex.Mix.TaskDescription
@switches [url: :string, public_key: :string, auth_key: :string]
@impl true
def run(args) do
Hex.start()
{opts, args} = Hex.OptionParser.parse!(args, strict: @switches)
case args do
["add", name, url] ->
add(name, url, opts)
["set", name] ->
set(name, opts)
["remove", name] ->
remove(name)
["show", name] ->
show(name)
["list"] ->
list()
_ ->
invalid_args()
end
end
defp invalid_args() do
Mix.raise("""
Invalid arguments, expected one of:
mix hex.repo add NAME URL
mix hex.repo set NAME
mix hex.repo remove NAME
mix hex.repo show NAME
mix hex.repo list
""")
end
@impl true
def tasks() do
[
{"add NAME URL", "Add a repo"},
{"set NAME", "Set config for repo"},
{"remove NAME URL", "Remove repo"},
{"list", "List all repos"}
]
end
defp add(name, url, opts) do
public_key = read_public_key(opts[:public_key])
repo =
%{
url: url,
public_key: nil,
auth_key: nil
}
|> Map.merge(Enum.into(opts, %{}))
|> Map.put(:public_key, public_key)
Hex.State.fetch!(:repos)
|> Map.put(name, repo)
|> Hex.Config.update_repos()
end
defp set(name, opts) do
opts =
if public_key = opts[:public_key] do
Keyword.put(opts, :public_key, read_public_key(public_key))
else
opts
end
Hex.State.fetch!(:repos)
|> Map.update!(name, &Map.merge(&1, Enum.into(opts, %{})))
|> Hex.Config.update_repos()
end
defp remove(name) do
Hex.State.fetch!(:repos)
|> Map.delete(name)
|> Hex.Config.update_repos()
end
defp list() do
header = ["Name", "URL", "Public key", "Auth key"]
values =
Enum.map(Hex.State.fetch!(:repos), fn {name, config} ->
[
name,
config[:url],
show_public_key(config[:public_key]),
config[:auth_key]
]
end)
Mix.Tasks.Hex.print_table(header, values)
end
defp read_public_key(nil) do
nil
end
defp read_public_key(path) do
key =
path
|> Path.expand()
|> File.read!()
decode_public_key(key)
key
end
defp decode_public_key(key) do
[pem_entry] = :public_key.pem_decode(key)
:public_key.pem_entry_decode(pem_entry)
rescue
_ ->
Mix.raise("""
Could not decode public key. The public key contents are shown below.
#{key}
Public keys must be valid and be in the PEM format.
""")
end
defp show_public_key(nil), do: nil
defp show_public_key(public_key) do
[pem_entry] = :public_key.pem_decode(public_key)
public_key = :public_key.pem_entry_decode(pem_entry)
ssh_hostkey_fingerprint(public_key)
end
# Adapted from https://github.com/erlang/otp/blob/3eddb0f762de248d3230b38bc9d478bfbc8e7331/lib/public_key/src/public_key.erl#L824
defp ssh_hostkey_fingerprint(key) do
"SHA256:#{sshfp_string(key)}"
end
defp sshfp_string(key) do
:crypto.hash(:sha256, :public_key.ssh_encode(key, :ssh2_pubkey))
|> Base.encode64(padding: false)
end
defp show(name) do
case Map.fetch(Hex.State.fetch!(:repos), name) do
{:ok, repo} ->
header = ["URL", "Public key", "Auth key"]
rows = [[repo.url, show_public_key(repo.public_key), repo.auth_key]]
Mix.Tasks.Hex.print_table(header, rows)
:error ->
Mix.raise("Config does not contain repo #{name}")
end
end
end