- Rails 콘솔 세션 시작하기
- Active Record 로깅 활성화
- 속성
- 데이터베이스 문 제한 시간 비활성화
- Rails 콘솔 세션 히스토리 출력
- Rails 러너 사용하기
- 객체의 특정 메서드 찾기
- 메서드 소스 찾기
- 출력 제한
- 마지막 작업 결과 가져오거나 저장하기
- 작업 시간 메트릭
- Active Record 객체
- 일반적으로 사용되는 Active Record 모델 및 객체 조회 방법
- 문제 해결
Rails 콘솔
GitLab의 핵심은 Ruby on Rails 프레임워크로 구축된 웹 응용 프로그램입니다. Rails 콘솔은 GitLab 인스턴스와 상호 작용하고 또한 Rails에 기본으로 내장된 놀라운 도구에 액세스할 수 있는 명령줄에서의 방법을 제공합니다.
경고: Rails 콘솔은 GitLab과 직접 상호 작용합니다. 여러 경우에 따라, 영구적으로 데이터를 수정, 손상 또는 파괴하는 데 제한자가 없습니다. 어떠한 결과도 가져오지 않는 Rails 콘솔을 테스트 환경에서 권장합니다.
Rails 콘솔은 문제를 해결하는 GitLab 시스템 관리자들과, GitLab 애플리케이션에 직접적인 액세스로만 수행할 수 있는 데이터 검색이 필요한 사용자들을 위한 것입니다. Ruby의 기본 지식이 필요합니다(빠른 소개를 원하신다면, 30분 튜토리얼을 확인해보세요). Rails 경험은 유용하지만 필수는 아닙니다.
Rails 콘솔 세션 시작하기
Rails 콘솔 세션을 시작하는 과정은 GitLab 설치 유형에 따라 다릅니다.
sudo gitlab-rails console
docker exec -it <container-id> gitlab-rails console
sudo -u git -H bundle exec rails console -e production
콘솔은 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 및 이후 버전에서는 기본적으로 두 개의 데이터베이스 연결을 사용합니다. 데이터베이스 문 제한 시간을 비활성화하려면 다음을 실행하세요:
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 러너 사용하기
GitLab 프로덕션 환경의 컨텍스트에서 일부 Ruby 코드를 실행해야 하는 경우, Rails 러너를 사용할 수 있습니다. 명령 또는 스크립트를 실행할 때, 스크립트는 git
사용자가 액세스할 수 있어야 합니다.
명령 또는 스크립트 완료 시, Rails 러너 프로세스가 종료됩니다. 예를 들어 다른 스크립트나 cron 작업에서 실행에 유용합니다.
-
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 러너는 콘솔과 동일한 출력을 생성하지 않습니다.
콘솔에서 변수를 설정하는 경우, 콘솔은 변수 내용 또는 참조된 엔터티의 속성과 같은 유용한 디버그 출력을 생성합니다:
irb(main):001:0> user = User.first
=> #<User id:1 @root>
Rails 러너에서는 이렇게 하지 않으며, 명시적으로 출력을 생성해야 합니다:
$ 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
마지막 작업 결과 가져오거나 저장하기
언더스코어(_
)는 이전 명령문의 암시적 반환을 나타냅니다. 이를 사용하여 빠르게 이전 명령의 출력에서 변수를 할당할 수 있습니다:
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>
… (중략)
더 많은 데이터베이스에서 데이터를 검색하는 다양한 방법에 대한 자세한 내용은 Active Record 쿼리 인터페이스 문서를 참조하십시오.
m = Model.where('attribute like ?', 'ex%')
# 예를 들어 프로젝트를 조회하는 쿼리
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')
이제 다른 방법을 시도해 보겠습니다:
# 최신 상태의 개체를 다시 가져오기
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)
바로 validation 오류에 걸렸군요. validation은 응용 프로그램 수준에서 불필요한 데이터가 데이터베이스에 저장되는 것을 방지하기 위해 결정된 비즈니스 로직으로, 대부분의 경우 입력 문제를 해결하는 방법을 알려주는 유용한 메시지와 함께 제공됩니다.
뱅(Ruby로 말하는 !
)을 .update
에 추가할 수도 있습니다:
user.update!(password: 'password', password_confirmation: 'hunter2')
Ruby에서 메서드 이름 끝에 !
가 있는 경우 이를 “뱅 메서드”라고 합니다. 관례상, 뱅은 메서드가 변환된 결과를 반환하지 않고 기존의 객체를 직접 수정한다는 것을 나타냅니다. 데이터베이스에 쓰기 위한 Active Record 메서드의 경우, 뱅 메서드는 오류가 발생할 때 직접적으로 예외를 발생시키므로 단순히 false
를 반환하는 것과 달리 명시적인 예외를 일으킵니다.
또한 validation을 완전히 건너뛸 수도 있습니다:
# 최신 상태의 개체를 다시 가져오기
user = User.find_by(username: 'root')
user.password = 'password'
user.password_confirmation = 'hunter2'
user.save!(validate: false)
이것은 권장되지 않으며, validation은 일반적으로 사용자가 제공한 데이터의 무결성과 일관성을 보장하기 위해 적용됩니다.
validation 오류로 인해 전체 객체가 데이터베이스에 저장되지 못하는 경우가 있습니다. 이것은 아래 섹션에서 조금 볼 수 있습니다. 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
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에서 사용하는 젬 또는 Ruby 소프트웨어 패키지에 의해 정의됩니다. 예를 들어, 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
앞에서 언급했듯이, validation 오류로 인해 전체 객체가 데이터베이스에 저장되는 것을 방해할 수 있습니다. 어떤식으로든 GitLab 관리 설정에서 이런 일이 발생하는데, GitLab 업데이트로 validation이 추가 또는 변경되어 이전에 저장된 설정이 validation에 실패하는 경우가 종종 있습니다. 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
admins
는 scope 편의 메서드로 실제로는 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
명령은 비루트 계정 및 그룹을 사용하여 Rails Runner를 실행하는데, 기본값은 git:git
입니다.
비루트 계정이 gitlab-rails 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