# SPDX-License-Identifier: EPL-1.0
##############################################################################
# Copyright (c) 2019 The Linux Foundation and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################
"""Gerrit REST API interface."""
import json
import logging
import os
import time
import urllib
import lftools.api.client as client
from lftools import config
log = logging . getLogger ( __name__ )
class Gerrit ( client . RestApi ):
"""API endpoint wrapper for Gerrit.
Be sure to always include the trailing "/" when adding
new methods.
"""
def __init__ ( self , ** params ):
"""Initialize the class."""
self . params = params
self . fqdn = self . params [ "fqdn" ]
if "creds" not in self . params :
creds = {
"authtype" : "basic" ,
"username" : config . get_setting ( self . fqdn , "username" ),
"password" : config . get_setting ( self . fqdn , "password" ),
"endpoint" : config . get_setting ( self . fqdn , "endpoint" ),
}
params [ "creds" ] = creds
super ( Gerrit , self ). __init__ ( ** params )
def add_file ( self , fqdn , gerrit_project , filename , issue_id , file_location , ** kwargs ):
"""Add a file for review to a Project.
File can be sourced from any location
but only lands in the root of the repo.
unless file_location is specified
Example:
gerrit_url gerrit.o-ran-sc.org
gerrit_project test/test1
filename /tmp/INFO.yaml
file_location="somedir/example-INFO.yaml"
"""
signed_off_by = config . get_setting ( fqdn , "sob" )
basename = os . path . basename ( filename )
payload = self . create_change ( basename , gerrit_project , issue_id , signed_off_by )
if file_location :
file_location = urllib . parse . quote ( file_location , safe = "" , encoding = None , errors = None )
basename = file_location
log . info ( payload )
access_str = "changes/"
result = self . post ( access_str , data = payload )[ 1 ]
log . info ( result [ "id" ])
changeid = result [ "id" ]
my_file = open ( filename )
my_file_size = os . stat ( filename )
headers = { "Content-Type" : "text/plain" , "Content-length" : "{}" . format ( my_file_size )}
self . r . headers . update ( headers )
access_str = "changes/{}/edit/{}" . format ( changeid , basename )
payload = my_file
result = self . put ( access_str , data = payload )
log . info ( result )
access_str = "changes/{}/edit:publish" . format ( changeid )
headers = { "Content-Type" : "application/json; charset=UTF-8" }
self . r . headers . update ( headers )
payload = json . dumps (
{
"notify" : "NONE" ,
}
)
result = self . post ( access_str , data = payload )
return result
##############################################################
def add_info_job ( self , fqdn , gerrit_project , jjbrepo , reviewid , issue_id , ** kwargs ):
"""Add an INFO job for a new Project.
Adds info verify jenkins job for project.
result['id'] can be used to ammend a review
so that multiple projects can have info jobs added
in a single review
Example:
fqdn gerrit.o-ran-sc.org
gerrit_project test/test1
jjbrepo ci-mangement
"""
###############################################################
# Setup
signed_off_by = config . get_setting ( fqdn , "sob" )
gerrit_project_dashed = gerrit_project . replace ( "/" , "-" )
filename = "{}.yaml" . format ( gerrit_project_dashed )
if not reviewid :
payload = self . create_change ( filename , jjbrepo , issue_id , signed_off_by )
log . info ( payload )
access_str = "changes/"
result = self . post ( access_str , data = payload )[ 1 ]
log . info ( result )
log . info ( result [ "id" ])
changeid = result [ "id" ]
else :
changeid = reviewid
if fqdn == "gerrit.o-ran-sc.org" :
buildnode = "centos7-builder-1c-1g"
else :
buildnode = "centos7-builder-2c-1g"
my_inline_file = """---
- project:
name: {0}-project-view
project-name: {0}
views:
- project-view \n
- project:
name: {0}-info
project: {1}
project-name: {0}
build-node: {2}
jobs:
- gerrit-info-yaml-verify \n """ . format (
gerrit_project_dashed , gerrit_project , buildnode
)
my_inline_file_size = len ( my_inline_file . encode ( "utf-8" ))
headers = { "Content-Type" : "text/plain" , "Content-length" : "{}" . format ( my_inline_file_size )}
self . r . headers . update ( headers )
access_str = "changes/{0}/edit/jjb%2F{1}%2F{1}.yaml" . format ( changeid , gerrit_project_dashed )
payload = my_inline_file
log . info ( access_str )
result = self . put ( access_str , data = payload )
log . info ( result )
access_str = "changes/{}/edit:publish" . format ( changeid )
headers = { "Content-Type" : "application/json; charset=UTF-8" }
self . r . headers . update ( headers )
payload = json . dumps (
{
"notify" : "NONE" ,
}
)
result = self . post ( access_str , data = payload )
log . info ( result )
return result
def vote_on_change ( self , fqdn , gerrit_project , changeid , ** kwargs ):
"""Helper that votes on a change.
POST /changes/{change-id}/revisions/{revision-id}/review
"""
log . info ( fqdn , gerrit_project , changeid )
access_str = "changes/{}/revisions/2/review" . format ( changeid )
headers = { "Content-Type" : "application/json; charset=UTF-8" }
self . r . headers . update ( headers )
payload = json . dumps (
{
"tag" : "automation" ,
"message" : "Vote on file" ,
"labels" : {
"Verified" : + 1 ,
"Code-Review" : + 2 ,
},
}
)
result = self . post ( access_str , data = payload )
# Code for projects that don't allow self merge.
if config . get_setting ( self . fqdn + ".second" ):
second_username = config . get_setting ( self . fqdn + ".second" , "username" )
second_password = config . get_setting ( self . fqdn + ".second" , "password" )
self . r . auth = ( second_username , second_password )
result = self . post ( access_str , data = payload )
self . r . auth = ( self . username , self . password )
return result
def submit_change ( self , fqdn , gerrit_project , changeid , payload , ** kwargs ):
"""Method so submit a change."""
# submit a change id
access_str = "changes/{}/submit" . format ( changeid )
log . info ( access_str )
headers = { "Content-Type" : "application/json; charset=UTF-8" }
self . r . headers . update ( headers )
result = self . post ( access_str , data = payload )
return result
def abandon_changes ( self , fqdn , gerrit_project , ** kwargs ):
"""."""
gerrit_project_encoded = urllib . parse . quote ( gerrit_project , safe = "" , encoding = None , errors = None )
access_str = "changes/?q=project:{}" . format ( gerrit_project_encoded )
log . info ( access_str )
headers = { "Content-Type" : "application/json; charset=UTF-8" }
self . r . headers . update ( headers )
result = self . get ( access_str )[ 1 ]
payload = { "message" : "Abandoned by automation" }
for id in result :
if ( id [ "status" ]) == "NEW" :
id = id [ "id" ]
access_str = "changes/{}/abandon" . format ( id )
log . info ( access_str )
result = self . post ( access_str , data = payload )[ 1 ]
return result
def create_change ( self , filename , gerrit_project , issue_id , signed_off_by , ** kwargs ):
"""Method to create a gerrit change."""
if issue_id :
subject = "Automation adds {0} \n\n Issue-ID: {1} \n\n Signed-off-by: {2}" . format (
filename , issue_id , signed_off_by
)
else :
subject = "Automation adds {0} \n\n Signed-off-by: {1}" . format ( filename , signed_off_by )
payload = json . dumps (
{
"project" : "{}" . format ( gerrit_project ),
"subject" : "{}" . format ( subject ),
"branch" : "master" ,
}
)
return payload
def sanity_check ( self , fqdn , gerrit_project , ** kwargs ):
"""Perform a sanity check."""
# Sanity check
gerrit_project_encoded = urllib . parse . quote ( gerrit_project , safe = "" , encoding = None , errors = None )
mylist = [ "projects/" , "projects/{}" . format ( gerrit_project_encoded )]
for access_str in mylist :
log . info ( access_str )
try :
result = self . get ( access_str )[ 1 ]
except Exception :
log . info ( "Not found {}" . format ( access_str ))
exit ( 1 )
log . info ( "found {} {}" . format ( access_str , mylist ))
return result
def add_git_review ( self , fqdn , gerrit_project , issue_id , ** kwargs ):
"""Add and Submit a .gitreview for a project.
Example:
fqdn gerrit.o-ran-sc.org
gerrit_project test/test1
issue_id: CIMAN-33
"""
signed_off_by = config . get_setting ( fqdn , "sob" )
self . sanity_check ( fqdn , gerrit_project )
###############################################################
# Create A change set.
filename = ".gitreview"
payload = self . create_change ( filename , gerrit_project , issue_id , signed_off_by )
log . info ( payload )
access_str = "changes/"
result = self . post ( access_str , data = payload )[ 1 ]
log . info ( result )
changeid = result [ "id" ]
###############################################################
# Add a file to a change set.
my_inline_file = """
[gerrit]
host={0}
port=29418
project={1}
defaultbranch=master
""" . format (
fqdn , gerrit_project
)
my_inline_file_size = len ( my_inline_file . encode ( "utf-8" ))
headers = { "Content-Type" : "text/plain" , "Content-length" : "{}" . format ( my_inline_file_size )}
self . r . headers . update ( headers )
access_str = "changes/{}/edit/{}" . format ( changeid , filename )
payload = my_inline_file
result = self . put ( access_str , data = payload )
if result . status_code == 409 :
log . info ( result )
log . info ( "Conflict detected exiting" )
exit ( 0 )
else :
access_str = "changes/{}/edit:publish" . format ( changeid )
headers = { "Content-Type" : "application/json; charset=UTF-8" }
self . r . headers . update ( headers )
payload = json . dumps (
{
"notify" : "NONE" ,
}
)
result = self . post ( access_str , data = payload )
log . info ( result )
result = self . vote_on_change ( fqdn , gerrit_project , changeid )
log . info ( result )
time . sleep ( 5 )
result = self . submit_change ( fqdn , gerrit_project , changeid , payload )
log . info ( result )
def create_saml_group ( self , fqdn , ldap_group , ** kwargs ):
"""Create saml group from ldap group."""
###############################################################
payload = json . dumps ({ "visible_to_all" : "false" })
saml_group = "saml/{}" . format ( ldap_group )
saml_group_encoded = urllib . parse . quote ( saml_group , safe = "" , encoding = None , errors = None )
access_str = "groups/{}" . format ( saml_group_encoded )
log . info ( "Encoded SAML group name: {}" . format ( saml_group_encoded ))
result = self . put ( access_str , data = payload )
return result
def add_github_rights ( self , fqdn , gerrit_project , ** kwargs ):
"""Grant github read to a project."""
###############################################################
# Github Rights
gerrit_project_encoded = urllib . parse . quote ( gerrit_project , safe = "" , encoding = None , errors = None )
# GET /groups/?m=test%2F HTTP/1.0
access_str = "groups/?m=GitHub%20Replication"
log . info ( access_str )
result = self . get ( access_str )[ 1 ]
time . sleep ( 5 )
githubid = result [ "GitHub Replication" ][ "id" ]
log . info ( githubid )
# POST /projects/MyProject/access HTTP/1.0
if githubid :
payload = json . dumps (
{
"add" : {
"refs/*" : {
"permissions" : {
"read" : { "rules" : { "{}" . format ( githubid ): { "action" : "{}" . format ( "ALLOW" )}}}
}
}
}
}
)
access_str = "projects/{}/access" . format ( gerrit_project_encoded )
result = self . post ( access_str , data = payload )[ 1 ]
pretty = json . dumps ( result , indent = 4 , sort_keys = True )
log . info ( pretty )
else :
log . info ( "Error no githubid found" )
def create_project ( self , fqdn , gerrit_project , ldap_group , description , check ):
"""Create a project via the gerrit API.
Creates a gerrit project.
Converts ldap group to saml group and sets as owner.
Example:
gerrit_url gerrit.o-ran-sc.org/r
gerrit_project test/test1
ldap_group oran-gerrit-test-test1-committers
--description="This is a demo project"
"""
gerrit_project = urllib . parse . quote ( gerrit_project , safe = "" , encoding = None , errors = None )
access_str = "projects/{}" . format ( gerrit_project )
result = self . get ( access_str )[ 0 ]
if result . status_code == 404 :
log . info ( result )
log . info ( "Project not found." )
projectexists = False
elif result . status_code == 401 :
log . info ( result )
log . info ( "Unauthorized." )
exit ( 1 )
else :
log . info ( "found {}" . format ( access_str ))
log . info ( result )
projectexists = True
if projectexists :
log . info ( "Project already exists" )
exit ( 1 )
if check :
exit ( 0 )
saml_group = "saml/{}" . format ( ldap_group )
log . info ( "SAML group name: {}" . format ( saml_group ))
access_str = "projects/{}" . format ( gerrit_project )
payload = json . dumps (
{
"description" : "{}" . format ( description ),
"submit_type" : "INHERIT" ,
"create_empty_commit" : "True" ,
"owners" : [ "{}" . format ( saml_group )],
}
)
log . info ( payload )
result = self . put ( access_str , data = payload )
return result
def list_project_permissions ( self , project ):
"""List a projects owners."""
result = self . get ( "access/?project={}" . format ( project ))[ 1 ][ project ][ "local" ]
group_list = []
for k , v in result . items ():
for kk , vv in result [ k ][ "permissions" ][ "owner" ][ "rules" ]. items ():
group_list . append ( kk . replace ( "ldap:cn=" , "" ). replace ( ",ou=Groups,dc=freestandards,dc=org" , "" ))
return group_list
def list_project_inherits_from ( self , gerrit_project ):
"""List who a project inherits from."""
gerrit_project = urllib . parse . quote ( gerrit_project , safe = "" , encoding = None , errors = None )
result = self . get ( "projects/{}/access" . format ( gerrit_project ))[ 1 ]
inherits = result [ "inherits_from" ][ "id" ]
return inherits