- 启动 Rails 控制台会话
- 启用 Active Record 日志记录
- 属性
- 禁用数据库语句超时
- 输出 Rails 控制台会话历史记录
- 使用 Rails Runner
- 查找对象的特定方法
- 查找方法源
- 限制输出
- 获取或存储最后一次操作的结果
- 计时操作
- Active Record 对象
- 使用 Active Record 模型查询数据库
- 故障排除
{{< details >}}
- Tier: 基础版, 专业版, 旗舰版
- Offering: 私有化部署
{{< /details >}}
在极狐GitLab的核心是一个 使用 Ruby on Rails 框架构建的网络应用程序。Rails 控制台提供了一种从命令行与极狐GitLab实例交互的方法,并授予对 Rails 内置的优秀工具的访问权限。
{{< alert type=”warning” >}}
Rails 控制台直接与极狐GitLab交互。在许多情况下,没有防护措施来防止您永久修改、损坏或销毁生产数据。如果您希望在没有后果的情况下探索 Rails 控制台,强烈建议您在测试环境中进行。
{{< /alert >}}
Rails 控制台适用于正在排查问题或需要检索只能通过直接访问极狐GitLab应用程序才能完成的数据的极狐GitLab系统管理员。需要基本的 Ruby 知识(可以尝试 这个 30 分钟的教程 进行快速入门)。Rails 经验有用但不是必需的。
启动 Rails 控制台会话
启动 Rails 控制台会话的过程取决于极狐GitLab安装类型。
{{< tabs >}}
{{< tab title=”Linux package (Omnibus)” >}}
sudo gitlab-rails console
{{< /tab >}}
{{< tab title=”Docker” >}}
docker exec -it <container-id> gitlab-rails console
{{< /tab >}}
{{< tab title=”Self-compiled (source)” >}}
sudo -u git -H bundle exec rails console -e production
{{< /tab >}}
{{< tab title=”Helm chart (Kubernetes)” >}}
# find the pod
kubectl get pods --namespace <namespace> -lapp=toolbox
# open the Rails console
kubectl exec -it -c toolbox <toolbox-pod-name> -- gitlab-rails console
{{< /tab >}}
{{< /tabs >}}
要退出控制台,请输入:quit
。
禁用自动完成
Ruby 自动完成可能会减慢终端速度。如果您想要:
- 禁用自动完成,请运行
Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE] = false
。 - 重新启用自动完成,请运行
Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE] = true
。
启用 Active Record 日志记录
您可以通过运行以下命令在 Rails 控制台会话中启用 Active Record 调试日志输出:
ActiveRecord::Base.logger = Logger.new($stdout)
默认情况下,前面的脚本将日志记录到标准输出。您可以通过将 $stdout
替换为所需的文件路径来指定日志文件以重定向输出。例如,此代码将所有内容记录到 /tmp/output.log
:
ActiveRecord::Base.logger = Logger.new('/tmp/output.log')
这将显示您在控制台中运行的任何 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
禁用数据库语句超时
您可以为当前 Rails 控制台会话禁用 PostgreSQL 语句超时。
在极狐GitLab 15.11 及更早版本中,要禁用数据库语句超时,请运行:
ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
在极狐GitLab 16.0 及更高版本中,极狐GitLab 默认使用两个数据库连接。要禁用数据库语句超时,请运行:
ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
Ci::ApplicationRecord.connection.execute('SET statement_timeout TO 0')
运行极狐GitLab 16.0 及更高版本的实例重新配置为使用单个数据库连接的,应使用极狐GitLab 15.11 及更早版本的代码禁用数据库语句超时。
禁用数据库语句超时仅影响当前的 Rails 控制台会话,并不会在极狐GitLab生产环境或下一个 Rails 控制台会话中持久化。
输出 Rails 控制台会话历史记录
在 Rails 控制台中输入以下命令以显示您的命令历史记录。
puts Reline::HISTORY.to_a
然后您可以将其复制到剪贴板并保存以供将来参考。
使用 Rails Runner
如果您需要在极狐GitLab生产环境的上下文中运行一些 Ruby 代码,您可以使用 Rails Runner 来实现。当执行脚本文件时,脚本必须可以被 git
用户访问。
当命令或脚本完成时,Rails Runner 进程结束。它在其他脚本或 cron 作业中运行时非常有用。
-
对于 Linux 包安装:
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 经验是有帮助的,但不是必须的。
查找对象的特定方法
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.path }; true
获取或存储最后一次操作的结果
下划线(_
)表示前一个语句的隐式返回。您可以使用它快速从上一个命令的输出中分配一个变量:
Project.last
# => #<Project id:2537 root/discard>>
project = _
# => #<Project id:2537 root/discard>>
project.id
# => 2537
计时操作
如果您想要计时一个或多个操作,请使用以下格式,用您选择的 Ruby 或 Rails 命令替换占位符 <operation>
:
# 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(username: 'root')
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 模型查询数据库
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 验证。验证是在应用程序级别设置的业务逻辑,以防止不希望的数据被保存到数据库中,并且在大多数情况下会附带有用的消息,告诉您如何修复问题输入。
我们也可以将叹号(Ruby 术语为 !
)添加到 .update
中:
user.update!(password: 'password', password_confirmation: 'hunter2')
在 Ruby 中,方法名以 !
结尾的通常被称为“叹号方法”。按照惯例,叹号表示方法直接修改其作用对象,而不是返回转换结果并保持底层对象不变。对于写入数据库的 Active Record 方法,叹号方法还具有额外功能:在发生错误时引发显式异常,而不是仅返回 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)
这不推荐,因为验证通常用于确保用户提供的数据的完整性和一致性。
验证错误会阻止整个对象被保存到数据库中。您可以在下面的部分中看到一些内容。如果您在极狐GitLab UI 中提交表单时遇到神秘的红色横幅,这通常是找到问题根源的最快方法。
与 Active Record 对象交互
最终,Active Record 对象只是标准的 Ruby 对象。因此,我们可以在其上定义执行任意操作的方法。
例如,极狐GitLab 开发人员添加了一些有助于双重身份验证的方法:
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.webauthn_registrations.destroy_all # rubocop: disable DestroyAll
end
end
def two_factor_enabled?
two_factor_otp_enabled? || two_factor_webauthn_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
返回!让我们通过像之前那样添加叹号来找出发生了什么:
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')
# 获取群组的父群组
group.parent
# 获取群组的子群组
group.children
获取群组的项目:
group = Group.find_by_full_path('group/subgroup')
# 获取群组的直接子项目
group.projects
# 获取群组的子项目,包括子群组中的项目
group.all_projects
获取 CI 流水线或构建:
Ci::Pipeline.find(4151)
Ci::Build.find(66124)
流水线和作业 ID 数字在您的极狐GitLab 实例中全局递增,因此不需要使用内部 ID 属性来查找它们,与议题或合并请求不同。
获取当前应用程序设置对象:
ApplicationSetting.current
在 irb
中打开对象
{{< alert type=”warning” >}}
更改数据的命令如果未正确运行或在正确的条件下运行,可能会造成损害。始终在测试环境中先运行命令,并准备好备份实例以便恢复。
{{< /alert >}}
有时在对象的上下文中执行方法更容易。您可以在 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"
故障排除
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
过滤控制台输出
控制台中的某些输出可能会被默认过滤,以防止泄露某些值,如变量、日志或密钥。此输出显示为 [FILTERED]
。例如:
> Plan.default.actual_limits
=> ci_instance_level_variables: "[FILTERED]",
要解决此过滤问题,请直接从对象读取值。例如:
> Plan.default.limits.ci_instance_level_variables
=> 25