- 启动 Rails 控制台会话
- 启用 Active Record 日志记录
- 属性
- 属性
- 禁用数据库语句超时
- 输出 Rails 控制台会话历史记录
- 使用 Rails Runner
- 查找对象的特定方法
- 查找方法源
- 限制输出
- 获取或存储上次操作的结果
- 定时操作
- Active Record 对象
- 使用 Active Record 模型查询数据库
Rails 控制台
Rails 控制台提供了一种使用命令行与极狐GitLab 实例交互的方法,并且还允许访问 Rails 中内置的工具。
Rails 控制台适用于正在对问题进行故障排除,或需要检索某些只能通过直接访问极狐GitLab 应用程序来完成的数据的系统管理员。
此功能需要 Ruby 的基本知识(尝试 30 分钟教程的快速介绍)。Rails 经验很有用,但不是必需的。
启动 Rails 控制台会话
Omnibus 安装实例
sudo gitlab-rails console
Docker 安装实例
docker exec -it <container-id> gitlab-rails console
源安装实例
sudo -u git -H bundle exec rails console -e production
Kubernetes 部署
控制台位于 toolbox pod。
要退出控制台,请键入:quit
。
启用 Active Record 日志记录
您可以通过运行以下命令,在 Rails 控制台会话中启用 Active Record 调试日志的输出:
ActiveRecord::Base.logger = Logger.new($stdout)
显示有关由您可能在控制台中运行的任何 Ruby 代码触发的数据库查询的信息。要再次关闭日志记录,请运行:
ActiveRecord::Base.logger = nil
属性
查看可用属性,使用漂亮打印(pp
)格式化。
例如,确定哪些属性包含用户名和电子邮件地址:
u = User.find_by_username('someuser')
pp u.attributes
部分输出:
{"id"=>1234,
"email"=>"someuser@example.com",
"sign_in_count"=>99,
"name"=>"S User",
"username"=>"someuser",
"first_name"=>nil,
"last_name"=>nil,
"bot_type"=>nil}
然后使用属性,例如测试 SMTP:
e = u.email
n = u.name
Notify.test_email(e, "Test email for #{n}", 'Test email').deliver_now
#
Notify.test_email(u.email, "Test email for #{u.name}", 'Test email').deliver_now
属性
查看可用属性,使用优雅打印 (pp
) 格式化。
例如,确定哪些属性包含用户名和电子邮件地址:
u = User.find_by_username('someuser')
pp u.attributes
部分输出:
{"id"=>1234,
"email"=>"someuser@example.com",
"sign_in_count"=>99,
"name"=>"S User",
"username"=>"someuser",
"first_name"=>nil,
"last_name"=>nil,
"bot_type"=>nil}
然后使用属性,例如测试 SMTP:
e = u.email
n = u.name
Notify.test_email(e, "Test email for #{n}", 'Test email').deliver_now
#
Notify.test_email(u.email, "Test email for #{u.name}", 'Test email').deliver_now
禁用数据库语句超时
您可以通过运行以下命令,禁用当前 Rails 控制台会话的 PostgreSQL 语句超时:
ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
此更改仅影响当前 Rails 控制台会话,不会保留在极狐GitLab 生产环境或下一个 Rails 控制台会话中。
输出 Rails 控制台会话历史记录
在 rails 控制台输入以下命令,显示您的命令历史记录。
puts Readline::HISTORY.to_a
然后,您可以将其复制到剪贴板并保存以备将来参考。
使用 Rails Runner
如果您需要在极狐GitLab 生产环境的上下文中运行一些 Ruby 代码,您可以使用 Rails Runner 来完成。
执行脚本文件时,该脚本必须可由 git
用户访问。
当命令或脚本完成时,Rails Runner 进程就完成了。 例如,它对于在其他脚本或 cron 作业中运行很有用。
Omnibus 安装实例
sudo gitlab-rails runner "RAILS_COMMAND"
# Example with a two-line Ruby script
sudo gitlab-rails runner "user = User.first; puts user.username"
# Example with a ruby script file (make sure to use the full path)
sudo gitlab-rails runner /path/to/script.rb
源安装实例
sudo -u git -H bundle exec rails runner -e production "RAILS_COMMAND"
# Example with a two-line Ruby script
sudo -u git -H bundle exec rails runner -e production "user = User.first; puts user.username"
# Example with a ruby script file (make sure to use the full path)
sudo -u git -H bundle exec rails runner -e production /path/to/script.rb
Rails Runner 不会产生与控制台相同的输出。
如果在控制台上设置变量,控制台将生成有用的调试输出,例如引用实体的变量内容或属性:
irb(main):001:0> user = User.first
=> #<User id:1 @root>
Rails Runner 不会这样做:您必须明确生成输出:
$ sudo gitlab-rails runner "user = User.first"
$ sudo gitlab-rails runner "user = User.first; puts user.username ; puts user.id"
root
1
Ruby 的一些基本知识将非常有用。尝试 这个 30 分钟的教程 快速介绍。 Rails 经验很有帮助,但不是必需的。
Rails Runner 故障排查
gitlab-rails
命令使用非 root 帐户和组执行 Rails Runner,默认情况下:git:git
。
如果非 root 帐户找不到传递给 gitlab-rails runner
的 Ruby 脚本文件名,您可能会收到语法错误,而不是无法访问文件的错误。
一个常见的原因是脚本已放在 root 帐户的主目录中。
runner
尝试将路径和文件参数解析为 Ruby 代码。
例如:
[root ~]# echo 'puts "hello world"' > ./helloworld.rb
[root ~]# sudo gitlab-rails runner ./helloworld.rb
Please specify a valid ruby command or the path of a script to run.
Run 'rails runner -h' for help.
/opt/gitlab/..../runner_command.rb:45: syntax error, unexpected '.'
./helloworld.rb
^
[root ~]# sudo gitlab-rails runner /root/helloworld.rb
Please specify a valid ruby command or the path of a script to run.
Run 'rails runner -h' for help.
/opt/gitlab/..../runner_command.rb:45: unknown regexp options - hllwrld
[root ~]# mv ~/helloworld.rb /tmp
[root ~]# sudo gitlab-rails runner /tmp/helloworld.rb
hello world
如果可以访问目录,但不能访问文件,则应生成有意义的错误:
[root ~]# chmod 400 /tmp/helloworld.rb
[root ~]# sudo gitlab-rails runner /tmp/helloworld.rb
Traceback (most recent call last):
[traceback removed]
/opt/gitlab/..../runner_command.rb:42:in `load': cannot load such file -- /tmp/helloworld.rb (LoadError)
如果您遇到与此类似的错误:
[root ~]# sudo gitlab-rails runner helloworld.rb
Please specify a valid ruby command or the path of a script to run.
Run 'rails runner -h' for help.
undefined local variable or method `helloworld' for main:Object
您可以将文件移动到 /tmp
目录或创建一个由用户 git
拥有的新目录并将脚本保存在该目录中,如下所示:
sudo mkdir /scripts
sudo mv /script_path/helloworld.rb /scripts
sudo chown -R git:git /scripts
sudo chmod 700 /scripts
sudo gitlab-rails runner /scripts/helloworld.rb
查找对象的特定方法
Array.methods.select { |m| m.to_s.include? "sing" }
Array.methods.grep(/sing/)
查找方法源
instance_of_object.method(:foo).source_location
# Example for when we would call project.private?
project.method(:private?).source_location
限制输出
在语句末尾添加分号(;
)和后续语句可防止默认隐式返回输出。如果您已经显式打印详细信息并且可能有很多返回输出,则可以使用此方法:
puts ActiveRecord::Base.descendants; :ok
Project.select(&:pages_deployed?).each {|p| puts p.pages_url }; true
获取或存储上次操作的结果
下划线(_
)表示隐式返回前面的语句。您可以使用它从上一个命令的输出中快速分配一个变量:
Project.last
# => #<Project id:2537 root/discard>>
project = _
# => #<Project id:2537 root/discard>>
project.id
# => 2537
定时操作
如果您想为一个或多个操作定时,请使用以下格式,将占位符 <operation>
替换为您选择的 Ruby 或 Rails 命令:
# A single operation
Benchmark.measure { <operation> }
# A breakdown of multiple operations
Benchmark.bm do |x|
x.report(:label1) { <operation_1> }
x.report(:label2) { <operation_2> }
end
Active Record 对象
查找数据库持久化对象
在底层,Rails 使用 Active Record,一种对象关系映射系统,来读取、写入应用程序对象并将其映射到 PostgreSQL 数据库。这些映射由 Active Record 模型处理,这些模型是在 Rails 应用程序中定义的 Ruby 类。对于极狐GitLab,模型类可以在 /opt/gitlab/embedded/service/gitlab-rails/app/models
中找到。
让我们为 Active Record 启用调试日志记录,以便我们可以看到进行的底层数据库查询:
ActiveRecord::Base.logger = Logger.new($stdout)
现在,让我们尝试从数据库中检索用户:
user = User.find(1)
返回:
D, [2020-03-05T16:46:25.571238 #910] DEBUG -- : User Load (1.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
=> #<User id:1 @root>
我们可以看到,我们已经在数据库中的 users 表中查询了 id 列值为 1 的行,并且 Active Record 将该数据库记录转换为我们可以与之交互的 Ruby 对象。尝试以下一些方法:
user.username
user.created_at
user.admin
按照惯例,列名直接转换为 Ruby 对象属性,因此您应该能够执行 user.<column_name>
来查看属性值。
同样按照惯例,Active Record 类名(单数和驼峰式)直接映射到表名(复数和蛇形),反之亦然。例如,users
表映射到 User
类,而 application_settings
表映射到 ApplicationSetting
类。
您可以在 Rails 数据库架构中找到表和列名称的列表,位于 /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb
。
您还可以通过属性名称从数据库中查找对象:
user = User.find_by(username: 'root')
返回:
D, [2020-03-05T17:03:24.696493 #910] DEBUG -- : User Load (2.1ms) SELECT "users".* FROM "users" WHERE "users"."username" = 'root' LIMIT 1
=> #<User id:1 @root>
请尝试以下操作:
User.find_by(email: 'admin@example.com')
User.where.not(admin: true)
User.where('created_at < ?', 7.days.ago)
您是否注意到最后两个命令返回的 ActiveRecord::Relation
对象似乎包含多个 User 对象?
到目前为止,我们一直在使用 .find
或 .find_by
,它们旨在仅返回一个对象(注意生成的 SQL 查询中的 LIMIT 1
吗?)。当需要获取对象集合时使用 .where
。
让我们获取非管理员用户的集合,看看可以用它做什么:
users = User.where.not(admin: true)
返回:
D, [2020-03-05T17:11:16.845387 #910] DEBUG -- : User Load (2.8ms) SELECT "users".* FROM "users" WHERE "users"."admin" != TRUE LIMIT 11
=> #<ActiveRecord::Relation [#<User id:3 @support-bot>, #<User id:7 @alert-bot>, #<User id:5 @carrie>, #<User id:4 @bernice>, #<User id:2 @anne>]>
现在,尝试以下操作:
users.count
users.order(created_at: :desc)
users.where(username: 'support-bot')
在最后一个命令中,我们看到我们可以链接 .where
语句来生成更复杂的查询。还要注意,虽然返回的集合只包含一个对象,但我们不能直接与它交互:
users.where(username: 'support-bot').username
返回:
Traceback (most recent call last):
1: from (irb):37
D, [2020-03-05T17:18:25.637607 #910] DEBUG -- : User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."admin" != TRUE AND "users"."username" = 'support-bot' LIMIT 11
NoMethodError (undefined method `username' for #<ActiveRecord::Relation [#<User id:3 @support-bot>]>)
Did you mean? by_username
让我们通过使用 .first
方法获取集合中的第一项,从集合中检索单个对象:
users.where(username: 'support-bot').first.username
现在得到了我们想要的结果:
D, [2020-03-05T17:18:30.406047 #910] DEBUG -- : User Load (2.6ms) SELECT "users".* FROM "users" WHERE "users"."admin" != TRUE AND "users"."username" = 'support-bot' ORDER BY "users"."id" ASC LIMIT 1
=> "support-bot"
有关使用 Active Record 从数据库检索数据的不同方法的更多信息,请参阅 Active Record 查询接口文档。
使用 Active Record 模型查询数据库
m = Model.where('attribute like ?', 'ex%')
# for example to query the projects
projects = Project.where('path like ?', 'Oumua%')
修改 Active Record 对象
在上一节中,我们了解了如何使用 Active Record 检索数据库记录。现在,让我们学习如何将更改写入数据库。
首先,让我们检索 root
用户:
user = User.find_by(username: 'root')
接下来,让我们尝试更新用户的密码:
user.password = 'password'
user.save
返回:
Enqueued ActionMailer::MailDeliveryJob (Job ID: 05915c4e-c849-4e14-80bb-696d5ae22065) to Sidekiq(mailers) with arguments: "DeviseMailer", "password_change", "deliver_now", #<GlobalID:0x00007f42d8ccebe8 @uri=#<URI::GID gid://gitlab/User/1>>
=> true
在这里,我们看到 .save
命令返回了 true
,表明密码更改已成功保存到数据库中。
我们还看到保存操作触发了一些其他操作——在本例中是发送电子邮件通知的后台作业。这是一个 Active Record 回调的示例——指定运行以响应 Active Record 对象生命周期中的事件的代码。这也是在需要直接更改数据时首选使用 Rails 控制台的原因,因为通过直接数据库查询所做的更改不会触发这些回调。
也可以在一行中更新属性:
user.update(password: 'password')
或者一次更新多个属性:
user.update(password: 'password', email: 'hunter2@example.com')
现在,让我们尝试一些不同的东西:
# Retrieve the object again so we get its latest state
user = User.find_by(username: 'root')
user.password = 'password'
user.password_confirmation = 'hunter2'
user.save
这将返回 false
,表示我们所做的更改未保存到数据库中。确定一下:
user.save!
返回:
Traceback (most recent call last):
1: from (irb):64
ActiveRecord::RecordInvalid (Validation failed: Password confirmation doesn't match Password)
我们触发了 Active Record Validation。 验证是在应用程序级别放置的业务逻辑,以防止将不需要的数据保存到数据库中,并且在大多数情况下会附带有用的消息,让您知道如何解决问题输入。
我们还可以在 .update 中添加 bang(Ruby 中的 !
):
user.update!(password: 'password', password_confirmation: 'hunter2')
在 Ruby 中,以 !
结尾的方法名称通常被称为 “bang 方法”。按照惯例,bang 表示该方法直接修改它正在作用的对象,而不是返回转换后的结果并保持底层对象不变。对于写入数据库的 Active Record 方法,bang 方法还有一个附加功能:只要发生错误,它们就会引发显式异常,而不仅仅是返回 false
。
我们也可以完全跳过验证:
# Retrieve the object again so we get its latest state
user = User.find_by(username: 'root')
user.password = 'password'
user.password_confirmation = 'hunter2'
user.save!(validate: false)
不建议这样做,因为通常会进行验证以确保用户提供的数据的完整性和一致性。
验证错误会阻止将整个对象保存到数据库中。您可以在下面的部分中看到一些内容。 如果您在提交表单时在 UI 中看到红色横幅,这通常是找到问题根源的最快方法。
与 Active Record 对象交互
归根结底,Active Record 对象只是普通的 Ruby 对象。因此,我们可以在它们上定义执行任意操作的方法。
例如,开发人员添加了一些有助于双重身份验证的方法:
def disable_two_factor!
transaction do
update(
otp_required_for_login: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
otp_grace_period_started_at: nil,
otp_backup_codes: nil
)
self.u2f_registrations.destroy_all # rubocop: disable DestroyAll
end
end
def two_factor_enabled?
two_factor_otp_enabled? || two_factor_u2f_enabled?
end
(查看:/opt/gitlab/embedded/service/gitlab-rails/app/models/user.rb
)
然后我们可以在任何用户对象上使用这些方法:
user = User.find_by(username: 'root')
user.two_factor_enabled?
user.disable_two_factor!
一些方法由极狐GitLab 使用的 Gem 或 Ruby 软件包定义。例如,极狐GitLab 用来管理用户状态的 StateMachines gem:
state_machine :state, initial: :active do
event :block do
...
event :activate do
...
end
尝试:
user = User.find_by(username: 'root')
user.state
user.block
user.state
user.activate
user.state
早些时候,我们提到验证错误会阻止将整个对象保存到数据库中。让我们看看这如何产生意想不到的相互作用:
user.password = 'password'
user.password_confirmation = 'hunter2'
user.block
我们得到 false
返回!让我们像之前一样通过添加 bang 来了解发生了什么:
user.block!
返回:
Traceback (most recent call last):
1: from (irb):87
StateMachines::InvalidTransition (Cannot transition state via :block from :active (Reason(s): Password confirmation doesn't match Password))
我们看到,当我们尝试以任何方式更新用户时,感觉像是一个完全独立的属性的验证错误会回来困扰我们。
实际上,我们有时会在极狐GitLab 管理设置中看到这种情况 —— 有时会在极狐GitLab 更新中添加或更改验证,导致以前保存的设置现在无法通过验证。因为您只能通过 UI 一次更新设置的子集,所以在这种情况下,恢复到良好状态的唯一方法是通过 Rails 控制台直接操作。
常用的 Active Record 模型以及如何查找对象
通过主要电子邮件地址或用户名获取用户:
User.find_by(email: 'admin@example.com')
User.find_by(username: 'root')
通过主要或次要电子邮件地址获取用户:
User.find_by_any_email('user@example.com')
find_by_any_email
方法是极狐GitLab 开发人员添加的自定义方法,而不是 Rails 提供的默认方法。
获取管理员用户的集合:
User.admins
admins
是一种范围便捷方法,它在后台执行 where(admin: true)
。
通过其路径获取项目:
Project.find_by_full_path('group/subgroup/project')
find_by_full_path
是极狐GitLab 开发者添加的自定义方法,而不是 Rails 提供的默认方法。
通过数字 ID 获取项目的议题或合并请求:
project = Project.find_by_full_path('group/subgroup/project')
project.issues.find_by(iid: 42)
project.merge_requests.find_by(iid: 42)
iid
的意思是“内部 ID”,这是我们将议题和合并请求 ID 限制在每个极狐GitLab 项目范围内的方式。
通过其路径获取一个群组:
Group.find_by_full_path('group/subgroup')
获取群组的相关群组:
group = Group.find_by_full_path('group/subgroup')
# Get a group's parent group
group.parent
# Get a group's child groups
group.children
获取群组的项目:
group = Group.find_by_full_path('group/subgroup')
# Get group's immediate child projects
group.projects
# Get group's child projects, including those in subgroups
group.all_projects
获取 CI 流水线或构建:
Ci::Pipeline.find(4151)
Ci::Build.find(66124)
流水线和作业 ID 号在您的极狐GitLab 实例中全局递增,因此无需使用内部 ID 属性来查找它们,这与议题或合并请求不同。
获取当前应用程序设置对象:
ApplicationSetting.current
在 irb
中打开对象
有时,如果您处于对象的上下文中,则通过方法会更容易。您可以填充到 Object
的命名空间中,以便在任何对象的上下文中打开 irb
:
Object.define_method(:irb) { binding.irb }
project = Project.last
# => #<Project id:2537 root/discard>>
project.irb
# Notice new context
irb(#<Project>)> web_url
# => "https://gitlab-example/root/discard"