Rails 콘솔

Tier: Free, Premium, Ultimate Offering: Self-Managed

GitLab의 핵심은 Ruby on Rails 프레임워크를 사용하여 개발된 웹 애플리케이션입니다(여기 참조). Rails 콘솔을 통해 GitLab 인스턴스와 상호작용할 수 있으며 Rails에 내장된 놀라운 도구에도 액세스할 수 있습니다.

경고: Rails 콘솔은 GitLab과 직접 상호작용합니다. 많은 경우, 생산 데이터를 영구적으로 수정, 손상 또는 파괴할 수 있는 방지 장치가 없습니다. 어떠한 영향도 미치지 않는 환경에서 Rails 콘솔을 탐색하려면 테스트 환경을 권장합니다.

Rails 콘솔은 문제 해결이 필요한 GitLab 시스템 관리자를 위한 것이며, 직접적인 GitLab 애플리케이션 액세스로만 수행할 수 있는 데이터를 검색해야 하는 경우에 사용됩니다. Ruby의 기본 지식이 필요하며(이 30분 튜토리얼이나 빠른 소개하세요.) Rails 경험은 유용하지만 필수는 아닙니다.

Rails 콘솔 세션 시작

Rails 콘솔 세션 시작 프로세스는 GitLab 설치 유형에 따라 다릅니다.

Linux 패키지 (Omnibus)
sudo gitlab-rails console
Docker
docker exec -it <container-id> gitlab-rails console
Self-compiled (source)
sudo -u git -H bundle exec rails console -e production
Helm 차트 (Kubernetes)

콘솔은 toolbox pod에 있습니다. 자세한 내용은 Kubernetes cheat sheet를 참조하세요.

콘솔을 종료하려면 quit를 입력하세요.

Active Record 로깅 활성화

Rails 콘솔 세션에서 Active Record 디버그 로깅 출력을 활성화하려면 다음을 실행하세요:

ActiveRecord::Base.logger = Logger.new($stdout)

이렇게 하면 콘솔에서 실행하는 어떤 Ruby 코드에 의해 트리거된 데이터베이스 쿼리에 대한 정보가 표시됩니다. 로깅을 다시 끄려면 다음을 실행하세요:

ActiveRecord::Base.logger = nil

속성

가능한 속성을 확인하고, pretty print(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 Readline::HISTORY.to_a

그런 다음 클립 보드에 복사하여 나중에 참고할 수 있습니다.

Rails Runner 사용

GitLab 생산 환경의 컨텍스트에서 몇 가지 Ruby 코드를 실행해야 하는 경우, Rails Runner를 사용할 수 있습니다. 명령 또는 스크립트 파일을 실행할 때, 스크립트는 git 사용자가 액세스할 수 있어야 합니다.

명령이나 스크립트가 완료되면, Rails Runner 프로세스가 종료됩니다. 이것은 예를 들어 스크립트나 크론 작업에서 실행하는 데 유용합니다.

  • Linux 패키지 설치의 경우:

    sudo gitlab-rails runner "RAILS_COMMAND"
    
    # 두 줄짜리 Ruby 스크립트 예제
    sudo gitlab-rails runner "user = User.first; puts user.username"
    
    # Ruby 스크립트 파일을 사용하는 예제 (전체 경로를 사용해야 함)
    sudo gitlab-rails runner /path/to/script.rb
    
  • 직접 컴파일된 설치의 경우:

    sudo -u git -H bundle exec rails runner -e production "RAILS_COMMAND"
    
    # 두 줄짜리 Ruby 스크립트 예제
    sudo -u git -H bundle exec rails runner -e production "user = User.first; puts user.username"
    
    # Ruby 스크립트 파일을 사용하는 예제 (전체 경로를 사용해야 함)
    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

# project.private?를 호출할 때의 예시
project.method(:private?).source_location

출력 제한

문장의 끝에 세미콜론(;)과 후속 문장을 추가하여 기본 암시적 반환 출력을 방지합니다. 이미 명시적으로 세부 정보를 출력하고 반환 출력이 많을 수 있는 경우에 사용할 수 있습니다:

puts ActiveRecord::Base.descendants; :ok
Project.select(&:pages_deployed?).each {|p| puts p.path }; true

마지막 동작의 결과 가져오거나 저장하기

Undercore(_)는 이전 문장의 암시적 반환을 나타냅니다. 이를 사용하여 빠르게 이전 명령의 출력에서 변수를 할당할 수 있습니다:

Project.last
# => #<Project id:2537 root/discard>>
project = _
# => #<Project id:2537 root/discard>>
project.id
# => 2537

동작 시간 측정

하나 이상의 동작을 시간 측정하려면 다음 형식을 사용하십시오. <operation> 자리 표시자를 사용자 지정한 Ruby 또는 Rails 명령으로 대체합니다:

# 단일 동작
Benchmark.measure { <operation> }

# 여러 동작 세부사항
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>

이전 명령에서 _로 선언한 변수에 대한 출력입니다:

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 = 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>

더 많은 정보를 원하시면 Active Record Query Interface 문서를 참조하십시오.

m = Model.where('attribute like ?', 'ex%')

# 예를 들어 프로젝트를 쿼리하는 경우
projects = Project.where('path like ?', 'Oumua%')

활성 레코드 모델을 사용하여 데이터베이스 질의

이전 섹션에서 활성 레코드를 사용하여 데이터베이스 레코드를 검색하는 방법에 대해 배웠습니다. 이제 데이터베이스에 변경 내용을 작성하는 방법에 대해 알아봅시다.

먼저 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를 반환하여 비밀번호 변경이 데이터베이스에 성공적으로 저장되었음을 나타냅니다.

또한 저장 작업이 다른 어떤 동작을 일으켰음을 볼 수 있습니다. 이 경우에는 이메일 알림을 전달하는 백그라운드 작업입니다. 이것은 활성 레코드 콜백의 예로, 즉 활성 레코드 객체 수명주기의 이벤트에 응답하여 실행되는 코드입니다. 이것은 데이터에 직접적인 변경을 통해 만든 변경사항이 이러한 콜백을 트리거하지 않는 것이기 때문에 데이터를 변경할 때는 보통 Rails 콘솔을 사용하는 것이 좋습니다.

또한, 한 줄로 속성을 업데이트할 수도 있습니다:

user.update(password: 'password')

또는 한 번에 여러 속성을 업데이트할 수도 있습니다:

user.update(password: 'password', email: 'hunter2@example.com')

이제 다른 방법을 시도해 보겠습니다:

# 객체를 다시 검색하여 최신 상태를 얻을 수 있도록 함
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)

뭐라고! 활성 레코드 유효성 검사를 발견했습니다. 유효성 검사는 데이터베이스에 원치 않는 데이터가 저장되는 것을 방지하기 위해 애플리케이션 수준에서 구현된 비즈니스 로직이며, 대부분의 경우 문제 입력을 고치는 방법을 알려주는 유용한 메시지가 딸려옵니다.

우리는 또한 .update 뒤에 느낌표(bang)를 추가할 수도 있습니다:

user.update!(password: 'password', password_confirmation: 'hunter2')

루비에서 느낌표로 끝나는 메서드는 보통 “뱅 메서드”로 알려져 있습니다. 일반적으로 느낌표는 메서드가 작용하는 객체를 직접 수정한다는 것을 나타내며, 변경된 결과를 반환하지 않고 기존 객체를 그대로 유지하는 대신, 데이터베이스에 쓰는 활성 레코드 메서드의 경우 느낌표 메서드는 오류가 발생할 때마다 명시적인 예외를 일으킵니다. 단점이 있음에도 불구하고 무시해 버릴 수도 있습니다.

또한, 유효성 검사를 완전히 건너뛸 수도 있습니다:

# 객체를 다시 검색하여 최신 상태를 얻을 수 있도록 함
user = User.find_by(username: 'root')
user.password = 'password'
user.password_confirmation = 'hunter2'
user.save!(validate: false)

이것은 권장되지는 않지만, 유효성 검사는 보통 사용자가 제공한 데이터의 무결성과 일관성을 보장하기 위해 구현됩니다.

유효성 오류는 전체 객체가 데이터베이스에 저장되는 것을 방지합니다. 아래 섹션에서 약간 볼 수 있습니다. GitLab UI에서 어떤 식으로든 양식을 제출할 때 신비한 빨간 배너가 표시된다면, 이것은 문제의 근본을 찾는 가장 빠른 방법일 수 있습니다.

활성 레코드 객체와 상호 작용

마지막으로, 활성 레코드 객체는 그냥 표준 루비 객체에 불과합니다. 따라서 임의의 작업을 수행하는 메서드를 정의할 수 있습니다.

예를 들어, 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이 사용하는 젬 또는 루비 소프트웨어 패키지에 의해 정의될 수도 있습니다. 예를 들어 GitLab에서 사용하는 사용자 상태를 관리하기 위해 사용하는 StateMachines 젬:

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 메서드는 Rails에서 제공하는 기본 메서드가 아닌 GitLab 개발자가 추가한 사용자 정의 메서드입니다.

관리자 사용자 컬렉션 가져오기:

User.admins

adminsscope convenience method로 내부에서 where(admin: true)를 수행합니다.

경로로 프로젝트 가져오기:

Project.find_by_full_path('group/subgroup/project')

find_by_full_path는 Rails에서 제공하는 기본 메서드가 아니라 GitLab 개발자가 추가한 사용자 정의 메서드입니다.

프로젝트의 이슈 또는 병합 요청 가져오기:

project = Project.find_by_full_path('group/subgroup/project')
project.issues.find_by(iid: 42)
project.merge_requests.find_by(iid: 42)

iid는 “내부 ID”를 의미하며 이는 각 GitLab 프로젝트에 범위가 지정된 이슈 및 병합 요청 ID를 유지하는 방법입니다.

경로로 그룹 가져오기:

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에서 객체 열기

경고: 데이터를 변경하는 명령은 올바르게 실행되지 않거나 올바른 조건에서 실행되지 않으면 손상을 초래할 수 있습니다. 항상 먼저 테스트 환경에서 명령을 실행하고 복원할 수 있는 백업 인스턴스를 준비하십시오.

가끔은 객체의 컨텍스트에서 메서드를 통해 진행하는 것이 더 쉬울 수 있습니다. Object의 이름 공간으로 들어가서 어떤 객체의 컨텍스트에서 irb을 열 수 있습니다:

Object.define_method(:irb) { binding.irb }

project = Project.last
# => #<Project id:2537 root/discard>>
project.irb
# 새로운 컨텍스트 주목
irb(#<Project>)> web_url
# => "https://gitlab-example/root/discard"

문제 해결

Rails Runner syntax error

gitlab-rails 명령은 기본적으로 git:git를 사용하여 루트 계정과 그룹이 아닌 계정 및 그룹을 사용하여 Rails Runner를 실행합니다.

루비 스크립트 파일 이름이 gitlab-rails runner에 전달될 때 루트 계정의 홈 디렉토리에 스크립트 이름이 없는 경우 구문 오류가 아니라 파일에 액세스할 수 없는 오류가 발생할 수 있습니다.

이와 같은 경우에 대한 일반적인 이유는 스크립트가 루트 계정의 홈 디렉토리에 위치한 경우입니다.

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