prodder 1.7.6 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +55 -0
- data/config/database.yml.github-actions +8 -0
- data/features/commit.feature +3 -3
- data/features/dump.feature +7 -7
- data/features/support/prodder__blog_prod.sql +1 -0
- data/lib/prodder/pg.rb +1 -3
- data/lib/prodder/prodder.rake +9 -9
- data/lib/prodder/project.rb +1 -4
- data/lib/prodder/version.rb +1 -1
- metadata +19 -6
- data/.travis.yml +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e8921f51e1bb936a99eaccc98d04bcd34ff72acbe20cb5547f6e680f8e4106a
|
4
|
+
data.tar.gz: 0e47795334cfcd4944d877dd5c24ab72e4fa5b082d66400b5c0a318228ba0f5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12817961230a0fd95776ff3bdf4a95b62fb8b961bbf2db9d93413f8cf93c32a9f11c4e036ffd89b0e4e5c0807ef40cf5d342706b0cd7a1f214c1f9c3a840547b
|
7
|
+
data.tar.gz: 34c918bd1587a1c055ffd76866028a5cd7e7c371c42cec0381e01294af4f308b5478d59f2406a8d5dea73755e5c16cb0d33b2b206e8a450448cceca6b5a2b748
|
@@ -0,0 +1,55 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
workflow_dispatch:
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- main
|
8
|
+
pull_request:
|
9
|
+
|
10
|
+
permissions:
|
11
|
+
contents: read
|
12
|
+
|
13
|
+
jobs:
|
14
|
+
test:
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
strategy:
|
17
|
+
matrix:
|
18
|
+
ruby-version: [2.6, 2.7, 3.0]
|
19
|
+
services:
|
20
|
+
postgres:
|
21
|
+
image: postgres:12.1-alpine
|
22
|
+
ports:
|
23
|
+
- 5432:5432
|
24
|
+
env:
|
25
|
+
POSTGRES_USER: postgres
|
26
|
+
POSTGRES_PASSWORD: postgres
|
27
|
+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
28
|
+
steps:
|
29
|
+
- name: Checkout Project
|
30
|
+
uses: actions/checkout@v3
|
31
|
+
|
32
|
+
- name: Set up Ruby
|
33
|
+
uses: ruby/setup-ruby@v1
|
34
|
+
with:
|
35
|
+
ruby-version: ${{ matrix.ruby-version }}
|
36
|
+
bundler-cache: true
|
37
|
+
|
38
|
+
- name: Install Library Dependencies
|
39
|
+
run: sudo apt-get install libpq-dev
|
40
|
+
|
41
|
+
- name: Setup Database
|
42
|
+
run: |
|
43
|
+
cp config/database.yml.github-actions config/database.yml
|
44
|
+
env:
|
45
|
+
RAILS_ENV: test
|
46
|
+
POSTGRES_USER: postgres
|
47
|
+
POSTGRES_PASSWORD: postgres
|
48
|
+
|
49
|
+
- name: Test with RSpec
|
50
|
+
env:
|
51
|
+
RAILS_ENV: "test"
|
52
|
+
POSTGRES_USER: postgres
|
53
|
+
POSTGRES_PASSWORD: postgres
|
54
|
+
run: |
|
55
|
+
bundle exec rspec
|
data/features/commit.feature
CHANGED
@@ -29,7 +29,7 @@ Feature: Commiting updated dumps to a project's repository
|
|
29
29
|
And I run `prodder dump -c prodder.yml`
|
30
30
|
And I run `prodder commit -c prodder.yml`
|
31
31
|
And 2 commits by "prodder auto-commit" should be in the "blog" repository
|
32
|
-
And the latest commit should have changed "db/structure.sql" to contain "CREATE TABLE linkbacks"
|
32
|
+
And the latest commit should have changed "db/structure.sql" to contain "CREATE TABLE public.linkbacks"
|
33
33
|
And the latest commit should not have changed "db/seeds.sql"
|
34
34
|
And the latest commit should not have changed "db/quality_checks.sql"
|
35
35
|
|
@@ -52,7 +52,7 @@ Feature: Commiting updated dumps to a project's repository
|
|
52
52
|
And I run `prodder dump -c prodder.yml`
|
53
53
|
And I run `prodder commit -c prodder.yml`
|
54
54
|
Then 2 commits by "prodder auto-commit" should be in the "blog" repository
|
55
|
-
And the latest commit should have changed "db/structure.sql" to contain "CREATE TABLE captchas"
|
55
|
+
And the latest commit should have changed "db/structure.sql" to contain "CREATE TABLE public.captchas"
|
56
56
|
And the latest commit should have changed "db/seeds.sql" to contain "Bob McBobbington"
|
57
57
|
And the latest commit should not have changed "db/quality_checks.sql"
|
58
58
|
|
@@ -64,6 +64,6 @@ Feature: Commiting updated dumps to a project's repository
|
|
64
64
|
And I run `prodder dump -c prodder.yml`
|
65
65
|
And I run `prodder commit -c prodder.yml`
|
66
66
|
And 2 commits by "prodder auto-commit" should be in the "blog" repository
|
67
|
-
And the latest commit should have changed "db/permissions.sql" to contain "GRANT ALL ON TABLE gotchas TO prodder"
|
67
|
+
And the latest commit should have changed "db/permissions.sql" to contain "GRANT ALL ON TABLE public.gotchas TO prodder"
|
68
68
|
And the latest commit should not have changed "db/seeds.sql"
|
69
69
|
And the latest commit should not have changed "db/quality_checks.sql"
|
data/features/dump.feature
CHANGED
@@ -6,11 +6,10 @@ Feature: prodder dump
|
|
6
6
|
Scenario: Happy path: dump structure.sql, listed seed tables, quality_checks.sql, permissions.sql and settings.sql
|
7
7
|
When I run `prodder dump -c prodder.yml`
|
8
8
|
Then the exit status should be 0
|
9
|
-
And the workspace file "blog/db/structure.sql" should match /CREATE TABLE posts/
|
10
|
-
And the workspace file "blog/db/structure.sql" should match /CREATE TABLE authors/
|
11
|
-
And the workspace file "blog/db/seeds.sql" should match /COPY posts/
|
12
|
-
And the workspace file "blog/db/seeds.sql" should match /COPY authors/
|
13
|
-
And the workspace file "blog/db/quality_checks.sql" should match /SET search_path/
|
9
|
+
And the workspace file "blog/db/structure.sql" should match /CREATE TABLE public.posts/
|
10
|
+
And the workspace file "blog/db/structure.sql" should match /CREATE TABLE public.authors/
|
11
|
+
And the workspace file "blog/db/seeds.sql" should match /COPY public.posts/
|
12
|
+
And the workspace file "blog/db/seeds.sql" should match /COPY public.authors/
|
14
13
|
And the workspace file "blog/db/quality_checks.sql" should match /CREATE TRIGGER /
|
15
14
|
And the workspace file "blog/db/permissions.sql" should match /GRANT /
|
16
15
|
And the workspace file "blog/db/settings.sql" should match /ALTER DATABASE /
|
@@ -123,6 +122,7 @@ Feature: prodder dump
|
|
123
122
|
Given I add a custom parameter "c.p" with value "v" in the "blog" project's database
|
124
123
|
When I run `prodder dump -c prodder.yml`
|
125
124
|
Then the workspace file "blog/db/settings.sql" should match /ALTER DATABASE :DBNAME SET c.p=v;/
|
125
|
+
And the workspace file "blog/db/settings.sql" should match /ALTER DATABASE :DBNAME SET search_path=foo,bar,public;/
|
126
126
|
|
127
127
|
Scenario: Verify empty db setting is quoted
|
128
128
|
Given I add a custom parameter "empty.setting" with value "" in the "blog" project's database
|
@@ -184,8 +184,8 @@ Feature: prodder dump
|
|
184
184
|
"""
|
185
185
|
When I run `prodder dump -c prodder.yml`
|
186
186
|
Then the exit status should be 0
|
187
|
-
And the workspace file "blog/db/seeds.sql" should match /COPY posts/
|
188
|
-
But the workspace file "blog/db/seeds.sql" should not match /COPY authors/
|
187
|
+
And the workspace file "blog/db/seeds.sql" should match /COPY public.posts/
|
188
|
+
But the workspace file "blog/db/seeds.sql" should not match /COPY public.authors/
|
189
189
|
|
190
190
|
Scenario: YAML file listing seed tables does not exist
|
191
191
|
Given the prodder config in "prodder.yml" says to read the "blog" seed tables from "db/seeds.yml"
|
data/lib/prodder/pg.rb
CHANGED
@@ -125,7 +125,6 @@ module Prodder
|
|
125
125
|
ACL_REVOKE = /^REVOKE /
|
126
126
|
DEFAULT_PRIVILEGES = /^ALTER DEFAULT PRIVILEGES /
|
127
127
|
SET_OBJECT_OWNERSHIP = /.* OWNER TO /
|
128
|
-
SEARCH_PATH = /SET search_path = .*/
|
129
128
|
|
130
129
|
def dump_db_access_control(db_name, user_list, options)
|
131
130
|
perm_out_sql = ""
|
@@ -153,8 +152,7 @@ module Prodder
|
|
153
152
|
if line.match(ACL_GRANT) ||
|
154
153
|
line.match(ACL_REVOKE) ||
|
155
154
|
line.match(DEFAULT_PRIVILEGES) ||
|
156
|
-
line.match(SET_OBJECT_OWNERSHIP)
|
157
|
-
line.match(SEARCH_PATH)
|
155
|
+
line.match(SET_OBJECT_OWNERSHIP)
|
158
156
|
|
159
157
|
unless irrelevant_login_roles.include?(line.match(/ (\S*);$/)[1])
|
160
158
|
perm_out_sql << line
|
data/lib/prodder/prodder.rake
CHANGED
@@ -169,7 +169,7 @@ namespace :db do
|
|
169
169
|
end
|
170
170
|
as("superuser", in: environments) do
|
171
171
|
ActiveRecord::Tasks::DatabaseTasks.create_current
|
172
|
-
|
172
|
+
Rails.configuration.database_configuration.each do |env, config|
|
173
173
|
if environments.include?(env) && config["migration_user"] && config['database']
|
174
174
|
set_psql_env config
|
175
175
|
`psql --no-psqlrc --command "ALTER DATABASE #{config['database']} OWNER TO #{config['migration_user']}" #{Shellwords.escape(config['database'])}`
|
@@ -182,7 +182,7 @@ namespace :db do
|
|
182
182
|
task :all => dependencies do
|
183
183
|
as("superuser") do
|
184
184
|
ActiveRecord::Tasks::DatabaseTasks.create_all
|
185
|
-
|
185
|
+
Rails.configuration.database_configuration.each do |env, config|
|
186
186
|
if config["migration_user"] && config['database']
|
187
187
|
set_psql_env config
|
188
188
|
`psql --no-psqlrc --command "ALTER DATABASE #{config['database']} OWNER TO #{config['migration_user']}" #{Shellwords.escape(config['database'])}`
|
@@ -204,7 +204,7 @@ namespace :db do
|
|
204
204
|
desc "Load db/structure.sql into the current environment's database"
|
205
205
|
task :load => dependencies do
|
206
206
|
as("superuser", in: ENV['RAILS_ENV'] || Rails.env) do
|
207
|
-
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
207
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env].with_indifferent_access
|
208
208
|
set_psql_env config
|
209
209
|
puts "Loading db/structure.sql into database '#{config['database']}'"
|
210
210
|
`psql --no-psqlrc -f db/structure.sql #{Shellwords.escape(config['database'])}`
|
@@ -217,7 +217,7 @@ namespace :db do
|
|
217
217
|
task :seed => dependencies do
|
218
218
|
if File.exist?('db/seeds.sql')
|
219
219
|
as("superuser", in: ENV['RAILS_ENV'] || Rails.env) do
|
220
|
-
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
220
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env].with_indifferent_access
|
221
221
|
set_psql_env config
|
222
222
|
puts "Loading db/seeds.sql into database '#{config['database']}'"
|
223
223
|
`psql --no-psqlrc -f db/seeds.sql #{Shellwords.escape(config['database'])}`
|
@@ -232,7 +232,7 @@ namespace :db do
|
|
232
232
|
task :quality_check => dependencies do
|
233
233
|
if File.exist?('db/quality_checks.sql')
|
234
234
|
as("superuser", in: ENV['RAILS_ENV'] || Rails.env) do
|
235
|
-
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
235
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env].with_indifferent_access
|
236
236
|
set_psql_env config
|
237
237
|
puts "Loading db/quality_checks.sql into database '#{config['database']}'"
|
238
238
|
`psql --no-psqlrc -f db/quality_checks.sql #{Shellwords.escape(config['database'])}`
|
@@ -247,7 +247,7 @@ namespace :db do
|
|
247
247
|
task :permission => dependencies do
|
248
248
|
if File.exist?('db/permissions.sql')
|
249
249
|
as("superuser", in: ENV['RAILS_ENV'] || Rails.env) do
|
250
|
-
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
250
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env].with_indifferent_access
|
251
251
|
set_psql_env config
|
252
252
|
puts "Loading db/permissions.sql into database '#{config['database']}'"
|
253
253
|
result = ActiveRecord::Base.connection.execute(<<-SQL).first
|
@@ -268,7 +268,7 @@ namespace :db do
|
|
268
268
|
task :settings => dependencies do
|
269
269
|
if File.exist?('db/settings.sql')
|
270
270
|
as("superuser", in: ENV['RAILS_ENV'] || Rails.env) do
|
271
|
-
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env]
|
271
|
+
config = ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env].with_indifferent_access
|
272
272
|
set_psql_env config
|
273
273
|
puts "Loading db/settings.sql into database '#{config['database']}'"
|
274
274
|
result = ActiveRecord::Base.connection.execute(<<-SQL).first
|
@@ -362,7 +362,7 @@ namespace :db do
|
|
362
362
|
|
363
363
|
def as(user, opts = {}, &block)
|
364
364
|
if File.exist?('db/permissions.sql')
|
365
|
-
config, config_was =
|
365
|
+
config, config_was = Rails.configuration.database_configuration, ActiveRecord::Base.configurations.deep_dup
|
366
366
|
in_env = Array(opts[:in]) || config.keys
|
367
367
|
if config.all? { |env, config_hash| in_env.include?(env) ? config_hash[user] : true }
|
368
368
|
disconnect
|
@@ -393,5 +393,5 @@ end
|
|
393
393
|
|
394
394
|
# Yes, I really want migrations to run against the test DB.
|
395
395
|
Rake::Task['db:migrate'].actions.unshift(proc {
|
396
|
-
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env])
|
396
|
+
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ENV['RAILS_ENV'] || Rails.env].with_indifferent_access)
|
397
397
|
})
|
data/lib/prodder/project.rb
CHANGED
@@ -45,11 +45,8 @@ module Prodder
|
|
45
45
|
contents = File.readlines(structure_file_name)
|
46
46
|
rgx = /^\-\- .* Type: INDEX; |^\-\- .* Type: TRIGGER; |^\-\- .* Type: FK CONSTRAINT; /
|
47
47
|
structure, *quality = contents.slice_before(rgx).to_a
|
48
|
-
# the first search path setting for constraints gets left over
|
49
|
-
# in the structure, so we need to *attempt* to grab that
|
50
|
-
quality_checks = (structure.grep(/SET search_path/).last || '') + quality.join
|
51
48
|
|
52
|
-
File.open(quality_check_file_name, 'w') { |f| f.write(
|
49
|
+
File.open(quality_check_file_name, 'w') { |f| f.write(quality.join) }
|
53
50
|
File.open(structure_file_name, 'w') { |f| f.write(structure.join) }
|
54
51
|
end
|
55
52
|
|
data/lib/prodder/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prodder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle Hargraves
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deject
|
@@ -32,13 +32,14 @@ executables:
|
|
32
32
|
extensions: []
|
33
33
|
extra_rdoc_files: []
|
34
34
|
files:
|
35
|
+
- ".github/workflows/ci.yml"
|
35
36
|
- ".gitignore"
|
36
|
-
- ".travis.yml"
|
37
37
|
- Gemfile
|
38
38
|
- LICENSE.txt
|
39
39
|
- README.md
|
40
40
|
- Rakefile
|
41
41
|
- bin/prodder
|
42
|
+
- config/database.yml.github-actions
|
42
43
|
- features/commit.feature
|
43
44
|
- features/dump.feature
|
44
45
|
- features/init.feature
|
@@ -81,10 +82,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
82
|
- !ruby/object:Gem::Version
|
82
83
|
version: '0'
|
83
84
|
requirements: []
|
84
|
-
|
85
|
-
rubygems_version: 2.7.7
|
85
|
+
rubygems_version: 3.0.3.1
|
86
86
|
signing_key:
|
87
87
|
specification_version: 4
|
88
88
|
summary: Maintain your Rails apps' structure, seed and quality_checks files using
|
89
89
|
production dumps
|
90
|
-
test_files:
|
90
|
+
test_files:
|
91
|
+
- features/commit.feature
|
92
|
+
- features/dump.feature
|
93
|
+
- features/init.feature
|
94
|
+
- features/lint.feature
|
95
|
+
- features/prodder.feature
|
96
|
+
- features/push.feature
|
97
|
+
- features/step_definitions/git_steps.rb
|
98
|
+
- features/step_definitions/prodder_steps.rb
|
99
|
+
- features/support/blog.git.tgz
|
100
|
+
- features/support/env.rb
|
101
|
+
- features/support/prodder__blog_prod.sql
|
102
|
+
- spec/config_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
data/.travis.yml
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
cache: bundler
|
3
|
-
|
4
|
-
rvm:
|
5
|
-
- 2.4.2
|
6
|
-
- 2.3.5
|
7
|
-
- 2.2.8
|
8
|
-
|
9
|
-
env:
|
10
|
-
matrix:
|
11
|
-
- PG_VERSION=9.3
|
12
|
-
- PG_VERSION=9.4
|
13
|
-
- PG_VERSION=9.5
|
14
|
-
|
15
|
-
before_install:
|
16
|
-
- git config --global user.name "Prodder In Travis-CI"
|
17
|
-
- git config --global user.email "prodder@example.com"
|
18
|
-
# install postgresql v9.5
|
19
|
-
- if [[ "$PG_VERSION" = "9.5" ]]; then echo "installing pg9.5"; sudo /etc/init.d/postgresql stop; sudo apt-get -y autoremove; sudo apt-key adv --keyserver keys.gnupg.net --recv-keys 7FCC7D46ACCC4CF8; sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main 9.5" >> /etc/apt/sources.list.d/postgresql.list'; sudo apt-get update; sudo apt-get -y install postgresql-9.5; sudo cp /etc/postgresql/9.4/main/pg_hba.conf /etc/postgresql/9.5/main/pg_hba.conf; sudo sed -i 's/5433/5432/' /etc/postgresql/9.5/main/postgresql.conf; sudo /etc/init.d/postgresql restart; else echo "not installing pg9.5"; fi
|
20
|
-
# setup travis user
|
21
|
-
- if [[ "$PG_VERSION" = "9.5" ]]; then echo "setting up users for pg9.5"; PGUSER=postgres createuser --superuser travis || echo role travis already exists.; fi
|
22
|
-
# setup pg_dump
|
23
|
-
- sudo ln -sfn /usr/lib/postgresql/$PG_VERSION/bin/pg_dump /usr/bin/pg_dump
|
24
|
-
# start up the specific version of PG
|
25
|
-
- sudo -E sh -c 'service postgresql stop'
|
26
|
-
- sleep 5s
|
27
|
-
- sudo -E sh -c 'service postgresql start $PG_VERSION'
|
28
|
-
- psql -U postgres -d postgres -c 'select setting from pg_settings where name = $m$server_version$m$;'
|
29
|
-
|
30
|
-
script:
|
31
|
-
- psql --version
|
32
|
-
- pg_lsclusters
|
33
|
-
- psql -U postgres -d postgres -c 'select 1;'
|
34
|
-
- ls -al `which pg_dump`
|
35
|
-
- bundle exec rake spec
|
36
|
-
- bundle exec rake cucumber
|
37
|
-
|
38
|
-
deploy:
|
39
|
-
provider: rubygems
|
40
|
-
api_key:
|
41
|
-
secure: "UhUkPFhEuI1dLPa4skTUdOBcGY2SEkRP3N9jLDQad04DflV+GutcjrfN1iQxWk59gVt3zqird5FS8SdwCFuOn8DAU9ACtg73xiPPWRRTdzma4Qw+4thuOHcdwPBz3762YFTRyH7IbRTAlxaD6qPz6US3BnYAkJU7C8c30rHLX6cZutjLV4FsvWonkzxcjyEUViVEdBM0kzI+tdBnQovpcM67a9AfxxBZITJLIfIcah1qc/RANpLkUFJCwNyH9oARWsGIvpIKcQEJBhsl04tvbNRLpiMCk1e1RS1bjMdbbx/rVm3C7dvAjUznbr3ON9abgoe6QDDYr6kXPJbylmxFUzA7ftBWjz2nNruRncsohx08LaM4ADRJWKB3XbP5BXkwUgE672Fi20+Z78LwWfjrr3iRVm7u9Mt9pZHG6Ih8Jy64Uq3647kdVZu9APPfn1NZETFG7vLAMZUtPXv7HBkujlq23XdYXax1XYYbYsM0LOlnG6ol2y6OrBrxWIqC+E8UmLXf/+/MS4j3v2RAe7jXh6fFlw+5MjLr3HXqZ12CrAChp22NRPp1OY4Hac4zzRwGeVOgewknpOK7qQfVFFaQoQksU6VaenSx+TxcYOZYuQdrQjfbO6c+Q/vvZ1RoPOEwH0AelkrW2eGqQTNVWIbH5vvfhys68SA8ov8gNnIzMtU="
|
42
|
-
gem: prodder
|
43
|
-
on:
|
44
|
-
tags: true
|
45
|
-
repo: enova/prodder
|