GitLab Rails Console Cheat Sheet (FREE SELF)

This is the GitLab Support Team's collection of information regarding the GitLab Rails console, for use while troubleshooting. It is listed here for transparency, and for users with experience with these tools. If you are currently having an issue with GitLab, it is highly recommended that you first check our guide on our Rails console, and your support options, before attempting to use this information.

WARNING: Some of these scripts could be damaging if not run correctly, or under the right conditions. We highly recommend running them under the guidance of a Support Engineer, or running them in a test environment with a backup of the instance ready to be restored, just in case.

WARNING: As GitLab changes, changes to the code are inevitable, and so some scripts may not work as they once used to. These are not kept up-to-date as these scripts/commands were added as they were found/needed. As mentioned above, we recommend running these scripts under the supervision of a Support Engineer, who can also verify that they continue to work as they should and, if needed, update the script for the latest version of GitLab.

Attributes

View available attributes, formatted using pretty print (pp).

For example, determine what attributes contain users' names and email addresses:

u = User.find_by_username('someuser')
pp u.attributes

Partial output:

{"id"=>1234,
 "email"=>"someuser@example.com",
 "sign_in_count"=>99,
 "name"=>"S User",
 "username"=>"someuser",
 "first_name"=>nil,
 "last_name"=>nil,
 "bot_type"=>nil}

Then make use of the attributes, testing SMTP, for example:

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

Imports and exports

Import a project

# Find the project and get the error
p = Project.find_by_full_path('<username-or-group>/<project-name>')

p.import_error

# To finish the import on GitLab running version before 11.6
p.import_finish

# To finish the import on GitLab running version 11.6 or after
p.import_state.mark_as_failed("Failed manually through console.")

Rename imported repository

In a specific situation, an imported repository needed to be renamed. The Support Team was informed of a backup restore that failed on a single repository, which created the project with an empty repository. The project was successfully restored to a development instance, then exported, and imported into a new project under a different name.

The Support Team was able to transfer the incorrectly named imported project into the correctly named empty project using the steps below.

Move the new repository to the empty repository:

mv /var/opt/gitlab/git-data/repositories/<group>/<new-project> /var/opt/gitlab/git-data/repositories/<group>/<empty-project>

Make sure the permissions are correct:

chown -R git:git <path-to-directory>.git

Clear the cache:

sudo gitlab-rake cache:clear

Export a project

It's typically recommended to export a project through the web interface or through the API. In situations where this is not working as expected, it may be preferable to export a project directly via the Rails console:

user = User.find_by_username('<username>')
# Sufficient permissions needed
# Read https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions

project = Project.find_by_full_path('<username-or-group>/<project-name')
Projects::ImportExport::ExportService.new(project, user).execute

If this all runs successfully, you see an output like the following before being returned to the Rails console prompt:

=> nil

The exported project is located in a .tar.gz file in /var/opt/gitlab/gitlab-rails/uploads/-/system/import_export_upload/export_file/.

If this fails, enable verbose logging, repeat the above procedure after, and report the output to GitLab Support.

Mirrors

Find mirrors with "bad decrypt" errors

This content has been converted to a Rake task, see verify database values can be decrypted using the current secrets.

Transfer mirror users and tokens to a single service account

This content has been moved to Troubleshooting Repository mirroring.

Users

Create new user

u = User.new(username: 'test_user', email: 'test@example.com', name: 'Test User', password: 'password', password_confirmation: 'password')
u.skip_confirmation! # Use it only if you wish user to be automatically confirmed. If skipped, user receives confirmation e-mail
u.save!

Skip reconfirmation

user = User.find_by_username('<username>')
user.skip_reconfirmation!

Disable 2fa for single user

In GitLab 13.5 and later:

Use the code under Disable 2FA | For a single user so that the target user is notified that 2FA has been disabled.

In GitLab 13.4 and earlier:

user = User.find_by_username('<username>')
user.disable_two_factor!

Active users & Historical users

# Active users on the instance, now
User.active.count

# Users taking a seat on the instance
User.billable.count

# The historical max on the instance as of the past year
::HistoricalData.max_historical_user_count(from: 1.year.ago.beginning_of_day, to: Time.current.end_of_day)

Using cURL and jq (up to a max 100, see Pagination):

curl --silent --header "Private-Token: ********************" \
     "https://gitlab.example.com/api/v4/users?per_page=100&active" | jq --compact-output '.[] | [.id,.name,.username]'

Update Daily Billable & Historical users

# Forces recount of historical (max) users
::HistoricalDataWorker.new.perform

# Forces recount of daily billable users
identifier = Analytics::UsageTrends::Measurement.identifiers[:billable_users]
::Analytics::UsageTrends::CounterJobWorker.new.perform(identifier, User.minimum(:id), User.maximum(:id), Time.zone.now)

Block or Delete Users that have no projects or groups

users = User.where('id NOT IN (select distinct(user_id) from project_authorizations)')

# How many users are removed?
users.count

# If that count looks sane:

# You can either block the users:
users.each { |user|  user.blocked? ? nil  : user.block! }

# Or you can delete them:
  # need 'current user' (your user) for auditing purposes
current_user = User.find_by(username: '<your username>')

users.each do |user|
  DeleteUserWorker.perform_async(current_user.id, user.id)
end

Deactivate Users that have no recent activity

days_inactive = 90
inactive_users = User.active.where("last_activity_on <= ?", days_inactive.days.ago)

inactive_users.each do |user|
    puts "user '#{user.username}': #{user.last_activity_on}"
    user.deactivate!
end

Block Users that have no recent activity

days_inactive = 90
inactive_users = User.active.where("last_activity_on <= ?", days_inactive.days.ago)

inactive_users.each do |user|
    puts "user '#{user.username}': #{user.last_activity_on}"
    user.block!
end

Find a user's max permissions for project/group

user = User.find_by_username 'username'
project = Project.find_by_full_path 'group/project'
user.max_member_access_for_project project.id
user = User.find_by_username 'username'
group = Group.find_by_full_path 'group'
user.max_member_access_for_group group.id

Merge requests

Close a merge request

u = User.find_by_username('<username>')
p = Project.find_by_full_path('<namespace/project>')
m = p.merge_requests.find_by(iid: <iid>)
MergeRequests::CloseService.new(project: p, current_user: u).execute(m)

Delete a merge request

u = User.find_by_username('<username>')
p = Project.find_by_full_path('<namespace/project>')
m = p.merge_requests.find_by(iid: <iid>)
Issuable::DestroyService.new(project: m.project, current_user: u).execute(m)

Rebase manually

u = User.find_by_username('<username>')
p = Project.find_by_full_path('<namespace/project>')
m = p.merge_requests.find_by(iid: <iid>)
MergeRequests::RebaseService.new(project: m.target_project, current_user: u).execute(m)

Set a merge request as merged

Use when a merge request was accepted and the changes merged into the Git repository, but the merge request still shows as open.

If the changes are not merged yet, this action causes the merge request to incorrectly show merged into <branch-name>.

u = User.find_by_username('<username>')
p = Project.find_by_full_path('<namespace/project>')
m = p.merge_requests.find_by(iid: <iid>)
MergeRequests::PostMergeService.new(project: p, current_user: u).execute(m)

CI

Cancel stuck pending pipelines

For more information, see the confidential issue https://gitlab.com/gitlab-com/support-forum/issues/2449#note_41929707.

Ci::Pipeline.where(project_id: p.id).where(status: 'pending').count
Ci::Pipeline.where(project_id: p.id).where(status: 'pending').each {|p| p.cancel if p.stuck?}
Ci::Pipeline.where(project_id: p.id).where(status: 'pending').count

Remove artifacts more than a week old

This section has been moved to the job artifacts troubleshooting documentation.

Find reason failure (for when build trace is empty) (Introduced in 10.3.0)

See https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41111.

build = Ci::Build.find(78420)

build.failure_reason

build.dependencies.each do |d| { puts "status: #{d.status}, finished at: #{d.finished_at},
  completed: #{d.complete?}, artifacts_expired: #{d.artifacts_expired?}, erased: #{d.erased?}" }

Try CI integration

p = Project.find_by_full_path('<project_path>')
m = project.merge_requests.find_by(iid: )
m.project.try(:ci_integration)

Validate the .gitlab-ci.yml

project = Project.find_by_full_path 'group/project'
content = project.repository.gitlab_ci_yml_for(project.repository.root_ref_sha)
Gitlab::Ci::Lint.new(project: project,  current_user: User.first).validate(content)

Disable AutoDevOps on Existing Projects

Project.all.each do |p|
  p.auto_devops_attributes={"enabled"=>"0"}
  p.save
end

Obtain runners registration token

Gitlab::CurrentSettings.current_application_settings.runners_registration_token

Seed runners registration token

appSetting = Gitlab::CurrentSettings.current_application_settings
appSetting.set_runners_registration_token('<new-runners-registration-token>')
appSetting.save!

Run pipeline schedules manually

You can run pipeline schedules manually through the Rails console to reveal any errors that are usually not visible.

# schedule_id can be obtained from Edit Pipeline Schedule page
schedule = Ci::PipelineSchedule.find_by(id: <schedule_id>)

# Select the user that you want to run the schedule for
user = User.find_by_username('<username>')

# Run the schedule
ps = Ci::CreatePipelineService.new(schedule.project, user, ref: schedule.ref).execute!(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)

License

See current license information

# License information (name, company, email address)
License.current.licensee

# Plan:
License.current.plan

# Uploaded:
License.current.created_at

# Started:
License.current.starts_at

# Expires at:
License.current.expires_at

# Is this a trial license?
License.current.trial?

# License ID for lookup on CustomersDot
License.current.license_id

# License data in Base64-encoded ASCII format
License.current.data

Check if a project feature is available on the instance

Features listed in https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/models/license.rb.

License.current.feature_available?(:jira_dev_panel_integration)

Check if a project feature is available in a project

Features listed in license.rb.

p = Project.find_by_full_path('<group>/<project>')
p.feature_available?(:jira_dev_panel_integration)

Add a license through the console

key = "<key>"
license = License.new(data: key)
license.save
License.current # check to make sure it applied

This is needed for example in a known edge-case with expired license and multiple LDAP servers.

Remove licenses

To clean up the License History table:

TYPE = :trial?
# or :expired?

License.select(&TYPE).each(&:destroy!)

# or even License.all.each(&:destroy!)

Registry

Registry Disk Space Usage by Project

Find this content in the Container Registry troubleshooting documentation.

Run the Cleanup policy now

Find this content in the Container Registry troubleshooting documentation.

Sidekiq

This content has been moved to Troubleshooting Sidekiq.

LFS

Get information about LFS objects and associated project

o = LfsObject.find_by(oid: "<oid>")
p = Project.find(LfsObjectsProject.find_by_lfs_object_id(o.id).project_id)

You can then delete these records from the database with:

LfsObjectsProject.find_by_lfs_object_id(o.id).destroy
o.destroy

You would also want to combine this with deleting the LFS file in the LFS storage area on disk. It remains to be seen exactly how or whether the deletion is useful, however.

Decryption Problems

Bad Decrypt Script (for encrypted variables)

This content has been converted to a Rake task, see verify database values can be decrypted using the current secrets.

As an example of repairing, if ProjectImportData Bad count: is detected and the decision is made to delete the encrypted credentials to allow manual reentry:

  # Find the ids of the corrupt ProjectImportData objects
  total = 0
  bad = []
  ProjectImportData.find_each do |data|
    begin
      total += 1
      data.credentials
    rescue => e
      bad << data.id
    end
  end

  puts "Bad count: #{bad.count} / #{total}"

  # See the bad ProjectImportData ids
  bad

  # Remove the corrupted credentials
  import_data = ProjectImportData.where(id: bad)
  import_data.each do |data|
    data.update_columns({ encrypted_credentials: nil, encrypted_credentials_iv: nil, encrypted_credentials_salt: nil})
  end

If User OTP Secret Bad count: is detected. For each user listed disable/enable two-factor authentication.

The following script searches in some of the tables for encrypted tokens that are causing decryption errors, and update or reset as needed:

wget -O /tmp/encrypted-tokens.rb https://gitlab.com/snippets/1876342/raw
gitlab-rails runner /tmp/encrypted-tokens.rb

Decrypt Script for encrypted tokens

This content has been converted to a Rake task, see verify database values can be decrypted using the current secrets.

Geo

Reverify all uploads (or any SSF data type which is verified)

  1. SSH into a GitLab Rails node in the primary Geo site.

  2. Open Rails console.

  3. Mark all uploads as "pending verification":

    Upload.verification_state_table_class.each_batch do |relation|
      relation.update_all(verification_state: 0)
    end
  4. This will cause the primary to start checksumming all Uploads.

  5. When a primary successfully checksums a record, then all secondaries rechecksum as well, and they compare the values.

A similar thing can be done for all Models handled by the Geo Self-Service Framework which have implemented verification:

  • LfsObject
  • MergeRequestDiff
  • Packages::PackageFile
  • Terraform::StateVersion
  • SnippetRepository
  • Ci::PipelineArtifact
  • PagesDeployment
  • Upload
  • Ci::JobArtifact
  • Ci::SecureFile

NOTE: GroupWikiRepository is not in the previous list since verification is not implemented. There is an issue to implement this functionality in the Admin UI.

Artifacts

Moved to Geo replication troubleshooting.

Repository verification failures

Moved to Geo replication troubleshooting.

Resync repositories

Moved to Geo replication troubleshooting.

Blob types

Moved to Geo replication troubleshooting.

Generate Service Ping

The Service Ping Guide in our developer documentation has more information about Service Ping.

Generate or get the cached Service Ping

Gitlab::Usage::ServicePingReport.for(output: :all_metrics_values, cached: true)

Generate a fresh new Service Ping

This also refreshes the cached Service Ping displayed in the Admin Area

Gitlab::Usage::ServicePingReport.for(output: :all_metrics_values)

Generate and print

Generates Service Ping data in JSON format.

rake gitlab:usage_data:generate

Generates Service Ping data in YAML format:

rake gitlab:usage_data:dump_sql_in_yaml

Generate and send Service Ping

Prints the metrics saved in conversational_development_index_metrics.

rake gitlab:usage_data:generate_and_send

GraphQL

Call a GraphQL endpoint through the Rails console:

query = <<~EOQ
query securityGetProjects($search: String!) {
  projects(search: $search) {
    nodes {
      path
    }
  }
}
EOQ

variables = { "search": "gitlab" }

result = GitlabSchema.execute(query, variables: variables, context: { current_user: current_user })
result.to_h