How to properly pull an apk using adb and subprocess in python - python

I'm trying to get apks based on the number it falls on on the select menu but I can't seem to get apk path when I enter it's number on the selection menu using adb and subprocess I aim to use apktool to decompile it when it's done but this is what I get when I enter the number on the select menu.
/system/bin/sh: <stdin>[1]: 15: inaccessible or not found
I read the docs and followed it closely below here is my code what am I doing wrong?
I would love to know.
# Create a select menu of all the available packages on the emulator and let the user choose one
output = subprocess.run(["adb", "-s", device_id, "shell", "pm", "list", "packages", "-3"], capture_output=True).stdout.decode("utf-8")
packages = output.strip().split("\n")
print("Available packages:")
for i, package in enumerate(packages):
print(f"{i+1}: {package}")
package_index = int(input("Enter the number of the package to extract: ")) - 1
print(package_index)
# Print the path of the selected package and extract it to the local filesystem
package = packages[package_index]
print(package)
##print(packages)
package_name = package.split(":")[1]
print(package_name)
new_output = subprocess.run(["adb","-s", device_id, "shell", "pm", "path", package_name], capture_output=True).stdout.decode("utf-8")
package_path = new_output.strip().split(":")[1]
#print(output)
#print(package_path)
#print(f"Full path of {package_name}: {package_path}")
apk_type = input("Enter 1 to extract a single APK file, or 2 to extract a split APK: ")
if apk_type == "1":
print("Hhhhh")
#subprocess.run(["adb" ,"-s", device_id, "pull", package_path, f"{package_name}.apk"])
else:
print("Working in progress......................................")

You should use adb shell pm path packageId instead. So from
new_output = subprocess.run(["adb","-s", device_id, "shell", "pm grant", "path", package_name], capture_output=True).stdout.decode("utf-8")
to
new_output = subprocess.run(["adb","-s", device_id, "shell", "pm", "path", package_name], capture_output=True).stdout.decode("utf-8")
after this change your python script will work.
Below the updated script, running against an emulator (tested with Python3.8.12, MacOS):
import subprocess
device_id="emulator-5554"
# Create a select menu of all the available packages on the emulator and let the user choose one
output = subprocess.run(["adb", "-s", device_id, "shell", "pm", "list", "packages", "-3"], capture_output=True).stdout.decode("utf-8")
packages = output.strip().split("\n")
print("Available packages:")
for i, package in enumerate(packages):
print(f"{i+1}: {package}")
package_index = int(input("Enter the number of the package to extract: ")) - 1
print(package_index)
# Print the path of the selected package and extract it to the local filesystem
package = packages[package_index]
print(package)
##print(packages)
package_name = package.split(":")[1]
print(package_name)
new_output = subprocess.run(["adb","-s", device_id, "shell", "pm", "path", package_name], capture_output=True).stdout.decode("utf-8")
package_path = new_output.strip().split(":")[1]
#print(output)
#print(package_path)
#print(f"Full path of {package_name}: {package_path}")
apk_type = input("Enter 1 to extract a single APK file, or 2 to extract a split APK: ")
if apk_type == "1":
print("Hhhhh")
subprocess.run(["adb" ,"-s", device_id, "pull", package_path, f"{package_name}.apk"])
else:
print("Working in progress......................................")

Related

Generating shell scripts using python

Hi I'm currently working on a python script that generates shell scripts to install agents on a linux server. The .sh files that the python scripts output keeps ending up with a "syntax error: unexpected end of file" but when i manually type in the exact output in vi, there seems to be no issue. Is there any issue with how I'm writing it in python or is it feasible to do it through python?
python script
import csv
def menu():
print("type of scripts")
print("1. Install + Generation")
print("2. Unregister + Reregister")
print("3. Unregister + Uninstall")
#Converts numeral choice into type for script naming
def choicename(choice):
choice = int(choice)
if choice==1:
return "install"
elif choice == 2 :
return "rereg"
else:
return "uninstall"
#Generates the install agent scripts
def installScript(agentname,agentfile,mgrfile,prigw,secgw,ostype):
#Generates the script for Linux agents (.sh)
if ostype=="Linux":
agentpath = 'agent="/opt/test/ragent/bin/racli"'
installerpath = '\ninstaller="/opt/test/installer/bin/racli"'
checkAgent = '\nif [ ! -e "$agent" ]; then' +"\n" + "./" + agentfile + " -n -d /opt/test" + '\nelse\necho "Agent is already installed"\nfi'
checkInstaller = '\nif [ ! -e "$installer" ]; then' + "\n" +"./" + mgrfile + " -n -d /opt/test"+ '\nelse\necho "Manager is already installed"\nfi'
regAgent = "\n/opt/test/ragent/bin/cli registration advanced-register registration-type=Primary ragent-name="+ agentname+ " gw-ip="+ prigw+ " gw-port=443 manual-settings-activation=Automatic monitor-networkchannels=Both password=$1"
if secgw!="":
regAgent+="\n/opt/test/ragent/bin/cli registration advanced-register registration-type=Secondary ragent-name="+ agentname+ " gw-ip="+ secgw+ " gw-port=443 manual-settings-activation=Automatic monitor-networkchannels=Both password=$1"
startAgent="\n/opt/test/ragent/bin/rainit start"
regInstaller="\n/opt/test/installer/bin/cliinstaller registration register-use-existing package-folder-path=\".\" package-folder-size=1024"
startInstaller="\n/opt/test/installer/bin/rainstallerinit start"
sf = open(agentname+ "_install.sh","w")
sf.write(agentpath+installerpath+checkAgent+checkInstaller+regAgent+startAgent+regInstaller+startInstaller)
sf.close()
def scriptSplit(option,agentname,agentfile,mgrfile,prigw,secgw,ostype):
if option=="install":
installScript(agentname,agentfile,mgrfile,prigw,secgw,ostype)
elif option =="rereg":
reregScript(agentname,agentfile,mgrfile,prigw,secgw,ostype)
elif option =="uninstall":
uninstallScript()
#Collects user input for function type
def main():
menu()
choice = input("Please choose the type of script you would like to generate: ")
option = choicename(choice)
filename = input("Please enter the name of the csv file: ")
with open(filename) as csv_file:
creader = csv.reader(csv_file, delimiter=",")
line_count = 0
for row in creader:
if line_count!=0:
agentname=row[0]
agentfile=row[1]
mgrfile=row[2]
prigw=row[3]
secgw=row[4]
installtype=row[5]
scriptSplit(option,agentname,agentfile,mgrfile,prigw,secgw,installtype)
line_count += 1
#### END OF FUNCTIONS ###
main()
output from above script
agent="/opt/test/ragent/bin/racli"
installer="/opt/test/installer/bin/racli"
if [ ! -e "$agent" ]; then
./agent1.bsx -n -d /opt/test
else
echo "Agent is already installed"
fi
if [ ! -e "$installer" ]; then
./installer1.bsx -n -d /opt/test
else
echo "Manager is already installed"
fi
/opt/test/ragent/bin/cli registration advanced-register registration-type=Primary ragent-name=agent1 gw-ip=10.255.0.80 gw-port=443 manual-settings-activation=Automatic monitor-networkchannels=Both password=$1
/opt/test/ragent/bin/cli registration advanced-register registration-type=Secondary ragent-name=agent1 gw-ip=10.255.0.81 gw-port=443 manual-settings-activation=Automatic monitor-networkchannels=Both password=$1
/opt/test/ragent/bin/rainit start
/opt/test/installer/bin/cliinstaller registration register-use-existing package-folder-path="." package-folder-size=1024
/opt/test/installer/bin/rainstallerinit start
csv file it reads from
Agent Name,Agent File,Installer File,Primary IP,Secondary IP,Type
agent1,agent1.bsx,installer1.bsx,10.255.0.80,10.255.0.81,Linux
agent2,agent2.bsx,installer2.bsx,10.255.0.81,,Linux
The newline convention is \r\n on windows. Bash interprets newline character as... newline character, and the \r is a normal character for bash.
Fix:
sf = open(agentname+ "_install.sh", "w", newline='\n')

How can i get the user input as argument for delete function in my code while executing program from CLI

This program runs stand alone perfectly when executed, but when this script gets called from another script, it gives 'EOFError: EOF when reading a line'
Below is my code:
'''
#!/usr/bin/python
import pdb
#pdb.set_trace()
from lxml import etree as ET
import argparse
import os
import sys
from colors import *
filename = "/appl/OMS/Build_Details/build_details.xml"
parser = ET.XMLParser(remove_blank_text=True)
tree = ET.parse(filename, parser)
root = tree.getroot()
parser = argparse.ArgumentParser()
parser.add_argument("r", help="Provide Release Number")
parser.add_argument("b", help="Provide Build number")
parser.add_argument("oms", help="Provide OMS Storage Name")
parser.add_argument("pc", help="Provide PC version")
parser.add_argument("omni", help="Provide OMNI Storage Name")
args = parser.parse_args()
def display():
sys.stdout.write(RESET)
print "Displaying all existing releases in Build XML"
for elem in root.iter("Release"):
sys.stdout.write(GREEN)
print int(elem.attrib['number']),
print " "
def add_details():
counter=0
for elem in root.iter("Release"):
#print int(elem.attrib['number']),int(args.r)
if int(elem.attrib['number']) == int(args.r):
sys.stdout.write(YELLOW)
print "Release found!!",
counter=counter+1
break
if counter == 1:
for a in elem.iter('Build'):
#print a.attrib['number']
if int(a.attrib['number']) == int(args.b):
print "Build already exists"
break
else:
#Take backup od existing build xml
os.system('cp /appl/OMS/Build_Details/build_details.xml /appl/OMS/Build_Details/Backup/build_details.xml_$(date +"%Y%m%d-%H%M%S")')
#Create new OMS element
build_elem = ET.Element("Build", {"number": args.b})
oms_elem = ET.Element("OMS")
build_path_elem = ET.Element("Build_path")
build_path_elem.text = args.oms
pc_version_elem = ET.Element("Pc_version")
pc_version_elem.text = args.pc
oms_elem.append(build_path_elem)
oms_elem.append(pc_version_elem)
# OMNI element
omni_elem = ET.Element("OMNI")
build_path_omni_elem = ET.Element("Build_path")
build_path_omni_elem.text = args.omni
omni_elem.append(build_path_omni_elem)
build_elem.append(oms_elem)
build_elem.append(omni_elem)
elem.append(build_elem)
# Write to file
tree.write("/appl/OMS/Build_Details/build_details.xml",pretty_print=True) # After adding the new element
print "Adding build details"
else:
#Take backup of existing build xml
os.system('cp /appl/OMS/Build_Details/build_details.xml /appl/OMS/Build_Details/Backup/build_details.xml_$(date +"%Y%m%d-%H%M%S")')
# Create new Release Element
Release = ET.SubElement(root, "Release",number=args.r)
#Build elementq
Build = ET.SubElement(Release, "Build", number=args.b)
OMS=ET.SubElement(Build,"OMS")
ET.SubElement(OMS,"Build_path").text =args.oms
ET.SubElement(OMS,"Pc_version").text =args.pc
OMNI=ET.SubElement(Build,"OMNI")
ET.SubElement(OMNI,"Build_path").text =args.omni
tree.write("/appl/OMS/Build_Details/build_details.xml",pretty_print=True) # After adding the new element
sys.stdout.write(YELLOW)
print "Release details added"
def delete():
sys.stdout.write(RESET)
release_number = int(input("Enter the release number you want to delete or else enter 0\n"))
for elem in root.iter("Release"):
if int(elem.attrib['number']) == int(release_number):
sys.stdout.write(YELLOW)
print "Release found"
root.remove(elem)
#Take backup od existing build xml
os.system('cp /appl/OMS/Build_Details/build_details.xml /appl/OMS/Build_Details/Backup/build_details.xml_$(date +"%Y%m%d-%H%M%S")')
tree.write("/appl/OMS/Build_Details/build_details.xml",pretty_print=True)
print "Delete complete"
def main():
#print "Calling add details"
add_details()
#print "calling display"
print "\n"
display()
#print "Calling delete "
print "\n"
delete()
#print "calling second display"
print "\n"
display()
sys.stdout.write(RESET)
main()
'''
Please let me know how can i pass the runtime input i am taking for my delete function as an argument like others.
I an new to python and experimenting with the language

How can this Python script be changed such that it sets Ubuntu shortkeys successfully?

I am attempting to get a Python script working that is designed to set Ubuntu shortkeys. It does not appear to be working in Ubuntu 16.10 and I am unsure why. In addition, I am finding it difficult to keep track of the quotation marks and their respective escapes needed for commands, strings, Bash etc.
The script is designed to be used in a way like the following:
./set_Ubuntu_shortkey.py \
--command="bash -c \"xvkbd -text \$(date \"+%Y-%m-%dT%H%MZ\" --utc) 2>/dev/null\"" \
--name="type_time_UTC" \
--keys="<Control><Shift>d"
This should ideally then set the Ubuntu shortkey such that it has the following characteristics:
name: type_time_UTC
command: bash -c "xvkbd -text $(date "+%Y-%m-%dT%H%MZ" --utc) 2>/dev/null"
key-bindings: Ctrl+Shift+D
#!/usr/bin/env python
"""
################################################################################
# #
# set_Ubuntu_shortkey #
# #
################################################################################
usage:
program [options]
options:
-h, --help display help message
--version display version and exit
--command=TEXT command
--keys=TEXT keys
--name=TEXT name
--debugpassive display commands without executing
"""
import docopt
import logging
import os
import subprocess
import sys
import time
def main(options):
command = options["--command"]
keys = options["--keys"]
name = options["--name"]
debug_passive = options["--debugpassive"]
if None in [command, keys, name]:
print("insufficient options specified")
exit()
print("\nset shortkey:" )
print("- name: " + name )
print("- command: " + command )
print("- keys: " + keys + "\n")
shortkeys_current = subprocess.check_output([
"gsettings",
"get",
"org.gnome.settings-daemon.plugins.media-keys",
"custom-keybindings"
]).decode("utf-8")
if shortkeys_current.strip() == "#as []":
shortkeys_current = "[]"
shortkeys_current = eval(shortkeys_current)
index_shortkey_new = len(shortkeys_current)
address_shortkey_new =\
"/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/" +\
"custom" + str(index_shortkey_new) + "/"
shortkeys_current.append(
address_shortkey_new
)
command_list_1 = [
"gsettings",
"set",
"org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:" +\
address_shortkey_new,
"name",
name
]
command_list_2 = [
"gsettings",
"set",
"org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:" +\
address_shortkey_new,
"command",
command
]
command_list_3 = [
"gsettings",
"set",
"org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:" +\
address_shortkey_new,
"binding",
keys
]
if debug_passive:
print(" ".join(command_list_1))
print(" ".join(command_list_2))
print(" ".join(command_list_3))
else:
subprocess.Popen(command_list_1)
subprocess.Popen(command_list_2)
subprocess.Popen(command_list_3)
if __name__ == "__main__":
options = docopt.docopt(__doc__)
if options["--version"]:
print(version)
exit()
main(options)

Clone repos from Gitolite in one action

Here is my attempt to simplify a process of preparing working environment. Assume your team has many Gitolite hosted repositories and you want to:
get full list of them;
clone some of them as simple and fast as can.
This script allows it. So, the answers for above questions are:
Show gitolite repos:
ssh git#server_ip
And the following code for cloning:
#!/usr/bin/python
import subprocess
import sys
import os
import re
FILTER = None
# check shell arguments
if len(sys.argv) == 2:
FILTER = sys.argv[1]
GIT_PATH = "git#server_ip"
# run system command and retrieve result
p = subprocess.Popen("ssh " + GIT_PATH, stdout=subprocess.PIPE, shell=True)
(output, err) = p.communicate()
output = re.sub(r'^[\S\s]*?(?=web/)', '', output)
output = re.sub(r'R|W', '', output)
output = output.split()
# filter function
def f(line):
if line.find(FILTER) != -1:
return True
return False
if FILTER:
output = filter(f, output)
yes = 'yes'
no = 'no'
for line in output:
print(line)
choice = input("Do you want to clone this repo (yes/no): ")
if choice == yes:
os.system("git clone " + GIT_PATH + ':/' + line.strip())
print('Done!')
print('All work is done! Congrats!')
Syntax: python clone.py [filter]
Filter, when specified, allows to get only repos that contain filter string as substring.
Suggestions for improvement are welcome! Thanks.

Rally APIs: How to copy Test Folder and member Test Cases

This question was asked by a different user earlier:
Copying Test Cases and Test Folder using Rally Python or Ruby API [closed]
but closed by moderators as being an overly broad question. However, given the inability to copy Test Folders and their member Test Cases within the Rally UI, this is a common need for Rally Users.
Thus - I'll re-pose the question, hopefully with enough detail to stand as a valid question. I'll also re-post the answers that I developed for the original question.
Question: As a Rally user and developer in the Rally Python and Ruby REST APIs: how can I leverage the Rally API toolkits to accomplish this task?
Python:
Here is a script that performs this task - it will copy all Test Cases from a Source Test Folder identified by FormattedID, to a Target Test Folder, also identified by FormattedID. It will copy all Test Steps and Attachments as well. The Target Test Folder must exist, i.e. the script will not create a Test Folder for you if the Target is not found.
The script does not associate the new Test Case to original Test Case's Work Product (i.e. Defect, User Story), or copy Discussion items, Test Case Results, or Last Build, Verdict, etc. as it is assumed the new Test Case is desired to be in a "blank" state.
For those needing to install and configure the Rally Python REST Library:
Rally Developer Portal: Pyral Download and Installation
Pyral Documentation
#!/usr/bin/env python
#################################################################################################
#
# copy_test_folder.py -- Copy all Test Cases in Source Test Folder to Target. Includes Test Steps
# and attachments. Target Test Folder must exist (i.e. the script will not
# create a new targeet Test Folder for you)
#
USAGE = """
Usage: copy_test_folder.py
"""
#################################################################################################
# import needed python libs
import sys, os
import re
import string
import base64
from pprint import pprint
# import needed pyral libs
from pyral import Rally, rallySettings, RallyRESTAPIError
errout = sys.stderr.write
my_server = "rally1.rallydev.com"
my_user = "user#company.com"
my_password = "topsecret"
my_workspace = "My Workspace"
my_project = "My Project"
source_test_folder_formatted_id = "TF1"
target_test_folder_formatted_id = "TF4"
rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project)
# rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project, debug=True)
rally.enableLogging('copy_test_folder.log')
# Query for source and target test folders
source_test_folder_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % source_test_folder_formatted_id)
target_test_folder_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % target_test_folder_formatted_id)
# Check to make sure folders exist
if source_test_folder_response.resultCount == 0:
errout('No Source Test Folder Found matching Formatted ID: %s\n' % (source_test_folder_formatted_id))
sys.exit(4)
if target_test_folder_response.resultCount == 0:
errout('No Target Test Folder Found matching Formatted ID: %s\n. Target Test Folder must be created before copying.' % (target_test_folder_formatted_id))
sys.exit(4)
# Get references to source Test Folder and Test Cases, etc.
source_test_folder = source_test_folder_response.next()
source_test_cases = source_test_folder.TestCases
# Get reference to target Test Folder
target_test_folder = target_test_folder_response.next()
for source_test_case in source_test_cases:
# Create update fields for target Test Case
# Does NOT associate new Test Case to original Test Case's work product (i.e. Defect, User Story)
# Does NOT copy Discussion items - as the old Discussions are likely not desired on new Test Case
# Does NOT copy Last Build, Last Run, Last Update Date, Last Verdict as new Test Case will effectively
# be "blank" and not have any results associated to it
if source_test_case.Owner != None:
target_owner = source_test_case.Owner.ref
else:
target_owner = None
target_test_case_fields = {
"Package": source_test_case.Package,
"Description": source_test_case.Description,
"Method": source_test_case.Method,
"Name": source_test_case.Name,
"Objective": source_test_case.Objective,
"Owner": target_owner,
"PostConditions": source_test_case.PostConditions,
"PreConditions": source_test_case.PreConditions,
"Priority": source_test_case.Priority,
"Project": source_test_case.Project.ref,
"Risk": source_test_case.Risk,
"ValidationInput": source_test_case.ValidationInput,
"ValidationExpectedResult": source_test_case.ValidationExpectedResult,
"TestFolder": target_test_folder.ref,
}
# Create the target test case
try:
target_test_case = rally.create("TestCase", target_test_case_fields)
message = "Copied Source Test Case: " + source_test_case.FormattedID + \
" To: " + target_test_folder.FormattedID + ": " + target_test_folder.Name + \
": " + target_test_case.FormattedID
print message
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Test Steps
# Add Test Case Steps
source_test_case_steps = source_test_case.Steps
for source_step in source_test_case_steps:
target_step_fields = {
"TestCase" : target_test_case.ref,
"StepIndex" : source_step.StepIndex,
"Input" : source_step.Input,
"ExpectedResult" : source_step.ExpectedResult
}
target_test_case_step = rally.put('TestCaseStep', target_step_fields)
print "===> Copied TestCaseStep: %s OID: %s" % (target_test_case_step.StepIndex, target_test_case_step.oid)
# Copy Attachments
source_attachments = rally.getAttachments(source_test_case)
for source_attachment in source_attachments:
# First copy the content
source_attachment_content = source_attachment.Content
target_attachment_content_fields = {
"Content": base64.encodestring(source_attachment_content)
}
try:
target_attachment_content = rally.put('AttachmentContent', target_attachment_content_fields)
print "===> Copied AttachmentContent: %s" % target_attachment_content.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Next copy the attachment object
target_attachment_fields = {
"Name": source_attachment.Name,
"Description": source_attachment.Description,
"Content": target_attachment_content.ref,
"ContentType": source_attachment.ContentType,
"Size": source_attachment.Size,
"Artifact": target_test_case.ref,
"User": source_attachment.User.ref
}
try:
target_attachment = rally.put('Attachment', target_attachment_fields)
print "===> Copied Attachment: %s" % target_attachment.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Tags
source_tags = source_test_case.Tags;
target_tags = list()
for source_tag in source_tags:
target_tags.append({"_ref":source_tag.ref})
target_test_case_fields = {
"FormattedID": target_test_case.FormattedID,
"Tags": target_tags
}
try:
update_response = rally.update('TestCase', target_test_case_fields)
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
Ruby:
This Ruby script will copy all Test Cases from a Source Test Folder identified by FormattedID, to a Target Test Folder, also identified by FormattedID. It will copy all Test Steps and Attachments as well. The Target Test Folder must exist, i.e. the script will not create a Test Folder for you if the Target is not found.
The script does not associate the new Test Case to original Test Case's Work Product (i.e. Defect, User Story), or copy Discussion items, Test Case Results, or Last Build, Verdict, etc. as it is assumed the new Test Case is desired to be in a "blank" state.
For those needing to install and configure the Ruby REST Toolkit, links are here:
Developer Portal: Rally REST API for Ruby
Github
# Copyright 2002-2012 Rally Software Development Corp. All Rights Reserved.
require 'rally_api'
$my_base_url = "https://rally1.rallydev.com/slm"
$my_username = "user#company.com"
$my_password = "password"
$my_workspace = "My Workspace"
$my_project = "My Project"
$wsapi_version = "1.37"
# Test Folders
$source_test_folder_formatted_id = "TF4"
$target_test_folder_formatted_id = "TF8"
# Load (and maybe override with) my personal/private variables from a file...
my_vars= File.dirname(__FILE__) + "/my_vars.rb"
if FileTest.exist?( my_vars ) then require my_vars end
#==================== Make a connection to Rally ====================
config = {:base_url => $my_base_url}
config[:username] = $my_username
config[:password] = $my_password
config[:workspace] = $my_workspace
config[:project] = $my_project
config[:version] = $wsapi_version
#rally = RallyAPI::RallyRestJson.new(config)
begin
# Lookup source Test Folder
source_test_folder_query = RallyAPI::RallyQuery.new()
source_test_folder_query.type = :testfolder
source_test_folder_query.fetch = true
source_test_folder_query.query_string = "(FormattedID = \"" + $source_test_folder_formatted_id + "\")"
source_test_folder_result = #rally.find(source_test_folder_query)
# Lookup Target Test Folder
target_test_folder_query = RallyAPI::RallyQuery.new()
target_test_folder_query.type = :testfolder
target_test_folder_query.fetch = true
target_test_folder_query.query_string = "(FormattedID = \"" + $target_test_folder_formatted_id + "\")"
target_test_folder_result = #rally.find(target_test_folder_query)
if source_test_folder_result.total_result_count == 0
puts "Source Test Folder: " + $source_test_folder_formatted_id + "not found. Exiting."
exit
end
if target_test_folder_result.total_result_count == 0
puts "Target Test Folder: " + $target_test_folder_formatted_id + "not found. Target must exist before copying."
exit
end
source_test_folder = source_test_folder_result.first()
target_test_folder = target_test_folder_result.first()
# Populate full object for Target Test Folder
full_target_test_folder = target_test_folder.read
# Get Target Project
target_project = full_target_test_folder["Project"]
# Grab collection of Source Test Cases
source_test_cases = source_test_folder["TestCases"]
# Loop through Source Test Cases and Copy to Target
source_test_cases.each do |source_test_case|
# Get full object for Source Test Case
full_source_test_case = source_test_case.read
# Check if there's an Owner
if !full_source_test_case["Owner"].nil?
source_owner = full_source_test_case["Owner"]
else
source_owner = nil
end
# Populate field data from Source to Target
target_test_case_fields = {}
target_test_case_fields["Package"] = full_source_test_case["Package"]
target_test_case_fields["Description"] = full_source_test_case["Description"]
target_test_case_fields["Method"] = full_source_test_case["Method"]
target_test_case_fields["Name"] = full_source_test_case["Name"]
target_test_case_fields["Objective"] = full_source_test_case["Objective"]
target_test_case_fields["Owner"] = source_owner
target_test_case_fields["PostConditions"] = full_source_test_case["PostConditions"]
target_test_case_fields["PreConditions"] = full_source_test_case["PreConditions"]
target_test_case_fields["Priority"] = full_source_test_case["Priority"]
target_test_case_fields["Project"] = target_project
target_test_case_fields["Risk"] = full_source_test_case["Risk"]
target_test_case_fields["ValidationInput"] = full_source_test_case["ValidationInput"]
target_test_case_fields["ValidationExpectedResult"] = full_source_test_case["ValidationExpectedResult"]
target_test_case_fields["Tags"] = full_source_test_case["Tags"]
target_test_case_fields["TestFolder"] = target_test_folder
# Create the Target Test Case
begin
target_test_case = #rally.create(:testcase, target_test_case_fields)
puts "Test Case: #{full_source_test_case["FormattedID"]} successfully copied to #{full_target_test_folder["FormattedID"]}"
rescue => ex
puts "Test Case: #{full_source_test_case["FormattedID"]} not copied due to error"
puts ex
end
# Now Copy Test Steps
# Add Test Case Steps
source_test_case_steps = full_source_test_case["Steps"]
source_test_case_steps.each do |source_test_case_step|
full_source_step = source_test_case_step.read
target_step_fields = {}
target_step_fields["TestCase"] = target_test_case
target_step_fields["StepIndex"] = full_source_step["StepIndex"]
target_step_fields["Input"] = full_source_step["Input"]
target_step_fields["ExpectedResult"] = full_source_step["ExpectedResult"]
begin
target_test_case_step = #rally.create(:testcasestep, target_step_fields)
puts "===> Copied TestCaseStep: #{target_test_case_step["_ref"]}"
rescue => ex
puts "Test Case Step not copied due to error:"
puts ex
end
end
# Now Copy Attachments
source_attachments = full_source_test_case["Attachments"]
source_attachments.each do |source_attachment|
full_source_attachment = source_attachment.read
source_attachment_content = full_source_attachment["Content"]
full_source_attachment_content = source_attachment_content.read
# Create AttachmentContent Object for Target
target_attachment_content_fields = {}
target_attachment_content_fields["Content"] = full_source_attachment_content["Content"]
begin
target_attachment_content = #rally.create(:attachmentcontent, target_attachment_content_fields)
puts "===> Copied AttachmentContent: #{target_attachment_content["_ref"]}"
rescue => ex
puts "AttachmentContent not copied due to error:"
puts ex
end
# Now Create Attachment Container
target_attachment_fields = {}
target_attachment_fields["Name"] = full_source_attachment["Name"]
target_attachment_fields["Description"] = full_source_attachment["Description"]
target_attachment_fields["Content"] = target_attachment_content
target_attachment_fields["ContentType"] = full_source_attachment["ContentType"]
target_attachment_fields["Size"] = full_source_attachment["Size"]
target_attachment_fields["Artifact"] = target_test_case
target_attachment_fields["User"] = full_source_attachment["User"]
begin
target_attachment = #rally.create(:attachment, target_attachment_fields)
puts "===> Copied Attachment: #{target_attachment["_ref"]}"
rescue => ex
puts "Attachment not copied due to error:"
puts ex
end
end
end
end
An improvement of the same Python script that adds the following features
Test Folders (source and destination) are passed in arguments instead of hard coded
Creation is done recursively (i.e. copy Test Folders and Test Cases in it)
Test Cases are replaced if they already exist (based on the Name)
Usage of API Key to connect rather than password
Try 5 times to connect to Rally before giving up
To use it to copy TF10 to TF99 (which already exists): python copy_test_folder TF10 TF99
#!/usr/bin/env python
#################################################################################################
#
# copy_test_folder.py -- Copy all Test Cases in Source Test Folder to Target. Includes Test Steps
# and attachments. Target Test Folder must exist (i.e. the script will not
# create a new targeet Test Folder for you)
#
USAGE = """
Usage: copy_test_folder.py TF_src TF_dest
"""
#################################################################################################
# import needed python libs
import sys, os
import re
import string
import base64
from pprint import pprint
# import needed pyral libs
from pyral import Rally, rallySettings, RallyRESTAPIError
errout = sys.stderr.write
my_server = "rally1.rallydev.com"
my_user = "name#domain.com"
my_password = "<PASSWORD>"
my_workspace = "<WORKSPACE>"
my_project = "<PROJECT>"
my_key = "<API KEY>"
def connect(nb_attempts=5):
'''Connect to Rally'''
global is_connected
global rally
#Connect
if not is_connected and nb_attempts>0:
print "...Attempting to connect to Rally for project %s..." % my_project
try:
# rally = Rally(my_server, my_user, my_password, workspace=my_workspace, project=my_project)
rally = Rally(my_server, apikey=my_key, workspace=my_workspace, project=my_project)
print "Connected to Rally for project %s (%s)" % (my_project, my_workspace)
is_connected=True
rally.enableLogging('copy_test_folder.log')
#Errors during connection (attempting to connect again)
# except Exception, details:
except AttributeError:
if nb_attempts>1:
connect(nb_attempts-1)
else:
errout('Error during connection to Rally (%s)\n' % details)
exit(4)
else: pass
def copyTF(src_TF, dest_TF):
'''Copy Test Cases from one folder into another (including children Test Folders)'''
# List names and FormattedID of existing Test Cases in destination
dest_TCs = dest_TF.TestCases
existing_dest_TC = {tc.Name:tc.FormattedID for tc in dest_TCs}
# Copy Test Cases to destination folder (replace if Test Case with same name exists)
src_TCs = src_TF.TestCases
for src_TC in src_TCs:
# Create update fields for target Test Case
# Does NOT associate new Test Case to original Test Case's WorkProduct (i.e. Defect, User Story)
# Does NOT copy Discussion items - as the old Discussions are likely not desired on new Test Case
# Does NOT copy Last Build, Last Run, Last Update Date, Last Verdict as new Test Case will effectively
# be "blank" and not have any results associated to it
tcName = src_TC.Name
dest_TC_fields = {
"Package": src_TC.Package,
"Description": src_TC.Description,
"Method": src_TC.Method,
"Name": tcName,
"Objective": src_TC.Objective,
"Owner": getattr(src_TC.Owner, 'ref', None),
"PostConditions": src_TC.PostConditions,
"PreConditions": src_TC.PreConditions,
"Priority": src_TC.Priority,
"Project": src_TC.Project.ref,
"Risk": src_TC.Risk,
"ValidationInput": src_TC.ValidationInput,
"ValidationExpectedResult": src_TC.ValidationExpectedResult,
"TestFolder": dest_TF.ref,
}
# Create/Update the target test case
try:
if existing_dest_TC.has_key(tcName):
operation = "Update"
dest_TC_fields['FormattedID'] = existing_dest_TC[tcName]
dest_TC = rally.update("TestCase", dest_TC_fields)
else:
#Create
operation = "Create"
dest_TC = rally.create("TestCase", dest_TC_fields)
message = operation + "d Source Test Case: " + src_TC.FormattedID + \
" To: " + dest_TF.FormattedID + ": " + dest_TF.Name + \
": " + dest_TC.FormattedID
print message
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Test Steps
#Cleared-up all Test Steps in destination if Test Case is updated
if operation == "Update":
dest_TC_steps = dest_TC.Steps
for dest_step in dest_TC_steps:
rally.delete('TestCaseStep', dest_step.oid)
print "===> Cleared all Test Steps"
# Add Test Case Steps
src_TC_steps = src_TC.Steps
for src_step in src_TC_steps:
target_step_fields = {
"TestCase" : dest_TC.ref,
"StepIndex" : src_step.StepIndex,
"Input" : src_step.Input,
"ExpectedResult" : src_step.ExpectedResult
}
dest_TC_step = rally.put('TestCaseStep', target_step_fields)
print "===> Copied TestCaseStep: %s OID: %s" % (dest_TC_step.StepIndex, dest_TC_step.oid)
# Copy Attachments
source_attachments = rally.getAttachments(src_TC)
for source_attachment in source_attachments:
# First copy the content
source_attachment_content = source_attachment.Content
target_attachment_content_fields = {
"Content": base64.encodestring(source_attachment_content)
}
try:
target_attachment_content = rally.put('AttachmentContent', target_attachment_content_fields)
print "===> Copied AttachmentContent: %s" % target_attachment_content.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Next copy the attachment object
target_attachment_fields = {
"Name": source_attachment.Name,
"Description": source_attachment.Description,
"Content": target_attachment_content.ref,
"ContentType": source_attachment.ContentType,
"Size": source_attachment.Size,
"Artifact": dest_TC.ref,
"User": source_attachment.User.ref
}
try:
target_attachment = rally.put('Attachment', target_attachment_fields)
print "===> Copied Attachment: %s" % target_attachment.ref
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Copy Tags
source_tags = src_TC.Tags;
target_tags = list()
for source_tag in source_tags:
target_tags.append({"_ref":source_tag.ref})
dest_TC_fields = {
"FormattedID": dest_TC.FormattedID,
"Tags": target_tags
}
try:
update_response = rally.update('TestCase', dest_TC_fields)
except RallyRESTAPIError, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(2)
# Recursive call for each child Test Folder (after creating it)
src_children_TFs = src_TF.Children
dest_children_TFs = dest_TF.Children
existing_dest_TF = {tf.Name:tf for tf in dest_children_TFs}
for src_child_TF in src_children_TFs:
tfName = src_child_TF.Name
# Create Test Folder if needed
if existing_dest_TF.has_key(tfName):
dest_child_TF = existing_dest_TF[tfName]
else:
target_TF_fields = {
'Name': tfName,
'Parent': dest_TF.ref
}
dest_child_TF = rally.put('TestFolder', target_TF_fields)
print "Created Test folder %s (%s)" % (tfName, dest_child_TF.FormattedID)
# Copy Test Cases of this folder
copyTF(src_child_TF, dest_child_TF)
if '__main__' in __name__:
# Get source and destination test folders
src_TF_formatted_id = sys.argv[1]
dest_TF_formatted_id = sys.argv[2]
# Connect to Rally
global is_connected
is_connected = False
connect(5)
# Query for source and target test folders
global rally
src_TF_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % src_TF_formatted_id)
dest_TF_response = rally.get('TestFolder', fetch=True, query='FormattedID = %s' % dest_TF_formatted_id)
# Check to make sure folders exist
if src_TF_response.resultCount == 0:
errout('No Source Test Folder Found matching Formatted ID: %s\n' % (src_TF_formatted_id))
sys.exit(4)
if dest_TF_response.resultCount == 0:
errout('No Target Test Folder Found matching Formatted ID: %s\n. Target Test Folder must be created before copying.' % (dest_TF_formatted_id))
sys.exit(4)
# Get Objects of source and target Test Folders
src_TF = src_TF_response.next()
dest_TF = dest_TF_response.next()
# Copy file
copyTF(src_TF, dest_TF)

Categories

Resources