Adding attachments to TestCaseResults using pyral 0.9.3 - python

I'm trying to add an attachment to a testcaseresult using pyral like this:
testCaseResult = rally.create('TestCaseResult', {'TestCase': tc.ref , 'Build': revision,
'Verdict': verdict[test.result], 'Date': resultdate, 'Notes': note,
'Tester': tester, 'Duration': runtime })
res = rally.addAttachment(testCaseResult.oid, file);
The TestaseResult is successfully created but res is False.
What am I doing wrong? Should I not be using the oid? I've tried passing testCaseResult, testCaseResult.oid and "TestCaseResult/" + testCaseResult.oid, and none seem to work...
UPDATED:
Based on Mark's answer below (pyral does not directly support adding attachments to testcaseresults), I wrote the following subroutine:
def addTestCaseResultAttachment(testCaseResult, filename, contentType='text/plain'):
if not os.path.exists(filename):
raise Exception('Named attachment filename: %s not found' % filename)
if not os.path.isfile(filename):
raise Exception('Named attachment filename: %s is not a regular file' % filename)
attachment_file_name = os.path.basename(filename)
attachment_file_size = os.path.getsize(filename)
if attachment_file_size == 0:
raise Exception('Cannot attach zero length file')
if attachment_file_size > 5000000:
raise Exception('Attachment file size too large, unable to attach to Rally Artifact')
contents = ''
with open(filename, 'r') as af:
contents = base64.encodestring(af.read())
# create an AttachmentContent item
ac = rally.create('AttachmentContent', {"Content" : contents}, project=None)
if not ac:
raise RallyRESTAPIError('Unable to create AttachmentContent for %s' % attachment_file_name)
attachment_info = { "Name" : attachment_file_name,
"Content" : ac.ref, # ref to AttachmentContent
"ContentType" : contentType,
"Size" : attachment_file_size, # must be size before encoding!!
"User" : 'user/%s' % me.oid,
"TestCaseResult" : testCaseResult.ref
}
# and finally, create the Attachment
attachment = rally.create('Attachment', attachment_info, project=None)
if not attachment:
raise RallyRESTAPIError('Unable to create Attachment for %s' % attachment_file_name)

The issue is that the addAttachment method for pyral's restapi, sets a ref to the "Artifact" attribute of the Attachment object, as shown following (lines 1241 thru 1251 of restapi):
attachment_info = { "Name" : attachment_file_name,
"Content" : ac.ref, # ref to AttachmentContent
"ContentType" : mime_type,
"Size" : attachment_file_size, # must be size before encoding!!
"User" : 'user/%s' % self.contextHelper.user_oid,
#"Artifact" : artifact.ref # (Artifact is an 'optional' field)
}
# While it's actually possible to have an Attachment not linked to an Artifact,
# in most cases, it'll be far more useful to have the linkage to an Artifact than not.
if artifact:
attachment_info["Artifact"] = artifact.ref
Thus, the addAttachment method will actually only work for objects that inherit from Artifact, i.e. Stories, Defects, Tasks, TestCases, etc. As seen in WSAPI Docs, to associate an Attachment to a TestCaseResult, the needed attribute is actually "TestCaseResult". This syntax was chosen since a TestCaseResult is actually a WorkspaceDomainObject, and not an Artifact.
Here's an example that creates a new TestCaseResult and adds an Attachment
#!/usr/bin/env python
#################################################################################################
#
# create_tcr_and_attachment.py -- Create a New TestCaseResult and attach a file to it
#
USAGE = """
Usage: py create_tcr_and_attachment.py <TestCaseFormatedID> <filename>
"""
#################################################################################################
import sys, os
import re
import string
import base64
from pyral import Rally, rallySettings
#################################################################################################
errout = sys.stderr.write
ATTACHMENT_ATTRIBUTES = ['oid', 'ObjectID', '_type', '_ref', '_CreatedAt', 'Name',
'CreationDate', 'Description',
'Content', 'ContentType', 'Size',
'Subscription',
'Workspace',
'Artifact',
'User'
]
ATTACHMENT_IMPORTANT_ATTRS = """
Subscription ref (supplied at creation)
Workspace ref (supplied at creation)
Name STRING Required (name of the file, like foo.txt or ThePlan.doc)
User ref to User Required Settable (User who added the object)
Content ref to AttachmentContent
Size INTEGER Required
ContentType STRING Required
Artifact ref to Artifact (optional field)
Description TEXT Optional
"""
#################################################################################################
def main(args):
options = [opt for opt in args if opt.startswith('--')]
args = [arg for arg in args if arg not in options]
server = "rally1.rallydev.com"
user = "user#company.com"
password = "topsecret"
workspace = "My Workspace"
project = "My Project"
print " ".join(["|%s|" % item for item in [server, user, '********', workspace, project]])
rally = Rally(server, user, password, workspace=workspace, version="1.43") # specify the Rally server and credentials
rally.enableLogging('rally.hist.create_tcr_and_attachment') # name of file you want logging to go to
if len(args) != 2:
errout('ERROR: You must supply a Test Case FormattedID and an attachment file name')
errout(USAGE)
sys.exit(1)
targetTCID, filename = args
me = rally.getUserInfo(username=user).pop(0)
print "%s user oid: %s" % (user, me.oid)
target_project = rally.getProject()
target_tc = rally.get('TestCase', query='FormattedID = %s' % targetTCID, instance=True)
datestring = "2014-05-01"
tcr_info = {
"TestCase" : target_tc.ref,
"Build" : "master-91321",
"Date" : datestring,
"Verdict" : "Pass",
"Notes" : "Honeycomb harvest project."
}
print "Creating Test Case Result ..."
tcr = rally.put('TestCaseResult', tcr_info)
print "Created TCR: %s" % (tcr.oid)
print "Creating AttachmentContent"
if not os.path.exists(filename):
raise Exception('Named attachment filename: %s not found' % filename)
if not os.path.isfile(filename):
raise Exception('Named attachment filename: %s is not a regular file' % filename)
attachment_file_name = os.path.basename(filename)
attachment_file_size = os.path.getsize(filename)
if attachment_file_size > 5000000:
raise Exception('Attachment file size too large, unable to attach to Rally Artifact')
contents = ''
with open(filename, 'r') as af:
contents = base64.encodestring(af.read())
# create an AttachmentContent item
ac = rally.create('AttachmentContent', {"Content" : contents}, project=None)
if not ac:
raise RallyRESTAPIError('Unable to create AttachmentContent for %s' % attachment_file_name)
attachment_info = { "Name" : attachment_file_name,
"Content" : ac.ref, # ref to AttachmentContent
"ContentType" : "image/jpeg",
"Size" : attachment_file_size, # must be size before encoding!!
"User" : 'user/%s' % me.oid,
"TestCaseResult" : tcr.ref
}
# and finally, create the Attachment
attachment = rally.create('Attachment', attachment_info, project=None)
if not attachment:
raise RallyRESTAPIError('Unable to create Attachment for %s' % attachment_file_name)
#################################################################################################
#################################################################################################
if __name__ == '__main__':
main(sys.argv[1:])

The function addAttachment in Rally API has been updated to support Test Case result:
# While it's actually possible to have an Attachment not linked to an Artifact,
# in most cases, it'll be far more useful to have the linkage to an Artifact than not.
# A special case is where the "Artifact" is actually a TestCaseResult, which is not a
# subclass of Artifact in the Rally data model, but the WSAPI has been adjusted to permit
# us to associate an Attachment with a TestCaseResult instance.
if artifact:
attachment_info["Artifact"] = artifact.ref
if artifact._type == 'TestCaseResult':
del attachment_info["Artifact"]
attachment_info["TestCaseResult"] = artifact.ref
The function call rally.addAttachment(testCaseResult.oid, file); should now work.

Related

label all sql instances in gcp that matches specific label

I am using the instances.list method from cloud sql admin-api to retrieve the information about instances in project. I am using the example code provided by google and it provides information about the instance. I am retrieving the name of the instance and then I need to update the instance labels if the label is matching the provided 'RC_PlatformCode'. This needs to be done to all sql instances in project matching the specific label. How can this be achieved as my code is not working. Or is there an easier way to do this in Python?
from config import Config, log, get_secret
from botocore.exceptions import ClientError
from typing import Dict, Iterable
from pprint import pprint
from googleapiclient import discovery
import json
import os
config = Config()
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="creds.json"
def updateSqlLabels(data, account):
log.info("-----")
log.info("updating Cloud Storage labels")
RC_PlatformCode = data['NewImage']['PlatformCode']['S']
platformcode_gcp = 'rc_platformcode'
tagKey = data['NewImage']['Key']['S']
tagValue = data['NewImage']['Value']['S']
char_to_replace = {
'#': '_at_',
'.': '_'
}
tagKey = tagKey.lower()
for key, value in char_to_replace.items():
tagValue = tagValue.replace(key, value)
service = discovery.build('sqladmin', 'v1beta4')
project = account # TODO: Update placeholder value.
request = service.instances().list(project=project)
while request is not None:
response = request.execute()
for database_instance in response['items']:
# TODO: Change code below to process each `database_instance` resource:
log.info("db_name = " + database_instance['name'])
update_tag = False
try:
labels = database_instance['settings']['userLabels']
log.info("tags -> " + str(labels))
except ClientError:
continue
for label in labels:
if labels["rc_platformcode"] == RC_PlatformCode:
log.info(
f"RC_PlatformCode [{RC_PlatformCode}] present for instance [{database_instance['name']}]")
update_tag = True
break
if update_tag:
create_tag = True
log.info("processing instance -> " + database_instance['name'])
log.info("setting tag Key -> " + tagKey)
log.info("setting tag Value -> " + tagValue)
for label in labels:
log.info("checking tag -> " + label)
labels[tagKey] = tagValue
instance_labels = labels
database_instance_body = {
'settings': {
'userLabels': instance_labels
}
}
log.info("project = " + project)
log.info("instance = " + database_instance['name'])
log.info("labels = " + str(database_instance_body))
request = service.instances().patch(project=project, instance=database_instance['name'], body=database_instance_body)
response = request.execute
break
I am receiving following error :
Response
{
"errorMessage": "'items'",
"errorType": "KeyError",
"stackTrace": [
" File \"/var/task/handler.py\", line 63, in handler\n updateSqlLabels(data, account)\n",
" File \"/var/task/cloudSql.py\", line 32, in updateSqlLabels\n for database_instance in response['items']:\n"
]
}
Any tips and help would be appreciated

"errorMessage": "string argument without an encoding", [duplicate]

This question already has answers here:
TypeError: string argument without an encoding
(3 answers)
Closed 3 years ago.
I'm trying to save password string encrypted in DynamoDb, I get this error.
Response:
{
"errorMessage": "string argument without an encoding",
"errorType": "TypeError",
"stackTrace": [
" File \"/var/task/lambda_function.py\", line 25, in lambda_handler\n encrypted_password = encrypt(session, plain_text_password, key_alias)\n",
" File \"/var/task/lambda_function.py\", line 11, in encrypt\n Plaintext=bytes(secret)\n"
]
}
This is the code I'm trying to work with.
import boto3
import base64
from botocore.exceptions import ClientError
def encrypt(session, secret, alias):
client = session.client('kms')
ciphertext = client.encrypt(
KeyId=alias,
Plaintext=bytes(secret)
)
return base64.b64encode(ciphertext["CiphertextBlob"])
def lambda_handler(event, context):
plain_text_password = event['password']
username = event['username']
key_alias = 'alias/ProjectKey'
table_name = 'Authentication'
session = boto3.session.Session()
table = boto3.resource('dynamodb').Table(table_name)
encrypted_password = encrypt(session, plain_text_password, key_alias)
print('ENCRYPTED STRING: ' + encrypted_password)
item = {
'username':username,
'password':encrypted_password
}
#check if item with the username already exists; if so, update password; else create new item
entry = table.get_item(TableName=table_name, Key={'username':username})
# if an entry with that username already exists, then update its corresponding password
if 'Item' in entry:
print('Item found. Updating password.')
print("entry['Item']" + str(entry['Item']))
response = table.update_item(
Key={
'username': username
},
UpdateExpression="set password = :p",
ExpressionAttributeValues={
':p': encrypted_password
},
ReturnValues="UPDATED_NEW"
)
else:
#if an entry with that username doesn't already exist, then create it
print('Adding new item to table.')
table.put_item(Item=item)
new_entry = table.get_item(TableName=table_name, Key={'username':username})
if 'Item' in new_entry:
print('A new item was inserted in the table.')
else:
print('Failed to insert new item in table')
return 'Function succeeded!'
I tried to run in python 2.7 and python 3 but no go.
I have added Lambda full access and dynamodb full access roles for Lambda and DB respectively and for KMS I have given the same accessess to administrate and key usage.
could you provide more informations (type, ...) on ciphertext["CiphertextBlob"] ?
maybe you just need to convert to bytes, e.g
base64.b64encode(bytes("yourstring", 'utf-8'))
or another way
base64.b64encode(ciphertext["CiphertextBlob"].encode('utf-8'))

Add tag to a Rally Defect using python pyral

I am trying to create Rally defect using pyral python package. Need to add a tag "#TestTag2".
Is there a way to add Tag at the time defect is created?
I am trying to add tag after the defect is created. But getting following error -
info = {"Workspace": "/workspace/123",
"Project": "/project/123",
"Name": "Test Defect",
"Description": "Test Defect details",
"Owner": "/user/123",
"ScheduleState": "Defined",
}
try:
defect = rally.create('Defect', info )
print ("Rally Defect Opened - {0} \n").format(defect.FormattedID)
adds = rally.addCollectionItems(defect, 'Tag',"#TestTag")
rally.addCollectionItems(defect,)
print(adds)
except Exception, details:
sys.stderr.write('ERROR: %s \n' % details)
sys.exit(1)
Getting following ERROR -
Rally Defect Opened - DE1234
ERROR: addCollectionItems() takes exactly 3 arguments (4 given)
Please help here, how to add a tag to a defect. Thanks in advance.
You got this error because of the signature of the method is the following:
def addCollectionItems(self, target, items)
You need to adjust your code to pass the list of tags:
tag_req = rally.get('Tag', fetch=True, query='Name = "TAG NAME"')
tag = tag_req.next()
adds = rally.addCollectionItems(defect, [tag])
Or you can use directly during Defect creation without any additional API calls:
from pyral import Rally
SERVER = 'SERVER URL'
USER = 'USER'
PASSWORD = 'PASSWORD'
WORKSPACE = 'WORKSPACE'
TAG = 'TAG NAME'
OWNER_EMAIL = 'bla#bla.com'
rally = Rally(SERVER, USER, PASSWORD, workspace=WORKSPACE)
target_project = rally.getProject()
user_req = rally.get('User', fetch=True, query='EmailAddress = "%s"' % (OWNER_EMAIL))
user = user_req.next()
tag_req = rally.get('Tag', fetch=True, query='Name = "%s"' % (TAG))
tag = tag_req.next()
defect_info ={"Project": target_project.ref,
"Name": "Test Defect",
"Description": "Test Defect details",
"ScheduleState": "Defined",
"Owner": user.ref,
"TAGS": [tag],
}
try:
defect = rally.create('Defect', defect_info )
print ("Rally Defect Opened - {0} \n").format(defect.FormattedID)
except Exception, details:
sys.stderr.write('ERROR: %s \n' % details)

Python Error not reading config.cfg?

Traceback (most recent call last):
File "/Users/jondevereux/Desktop/Data reporting/kpex_code/1PD/api_python_publisher_1PD.py", line 40, in <module>
username = parser.get('api_samples', 'username')
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ConfigParser.py", line 607, in get
raise NoSectionError(section)
ConfigParser.NoSectionError: No section: 'api_samples'
The config file is in the correct directory (same as .py and has the appropriate section api_samples:
[api_samples]
authentication_url = https://crowdcontrol.lotame.com/auth/v1/tickets
api_url = https://api.lotame.com/2/
username = xxx
password = xxx
Script works on co-workers PC not on mine? I had to use pip to install requests - i'm wondering I i'm missing something else?
Code is as follows:
# Set up the libs we need
import requests
import sys
import csv
import json
from ConfigParser import SafeConfigParser # used to get information from a config file
reload(sys)
sys.setdefaultencoding('utf-8')
'''
Now let's get what we need from our config file, including the username and password
We are assuming we have a config file called config.config in the same directory
where this python script is run, where the config file looks like:
[api_samples]
authentication_url = https://crowdcontrol.lotame.com/auth/v1/tickets
api_url = https://api.lotame.com/2/
username = USERNAME_FOR_API_CALLS
password = PASSWORD
'''
# Set up our Parser and get the values - usernames and password should never be in code!
parser = SafeConfigParser()
parser.read('config.cfg')
username = parser.get('api_samples', 'username')
password = parser.get('api_samples', 'password')
authentication_url = parser.get('api_samples', 'authentication_url')
base_api_url = parser.get('api_samples', 'api_url')
# OK, all set with our parameters, let's get ready to make our call to get a Ticket Granting Ticket
# Add the username and password to the payload (requests encodes for us, no need to urlencode)
payload = {'username': username,
'password': password}
# We want to set some headers since we are going to post some url encoded params.
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain", "User-Agent":"python" }
# Now, let's make our Ticket Granting Ticket request. We get the location from the response header
tg_ticket_location = requests.post(authentication_url, data=payload).headers['location']
# Let's take a look at what a Ticket Granting Ticket looks like:
# print ('Ticket Granting Ticket - %s \n') % (tg_ticket_location[tg_ticket_location.rfind('/') + 1:])
# Now we have our Ticket Granting Ticket, we can get a service ticket for the service we want to call
# The first service call will be to get information on behavior id 5990.
# service_call = base_api_url + 'behaviors/5990'
# Add the service call to the payload and get the ticket
#payload = {'service': service_call}
#service_ticket = requests.post( tg_ticket_location, data=payload ).text
# Let's take a look at the service ticket
#print ('Here is our Service Ticket - %s \n') % ( service_ticket )
'''
Now let's make our call to the service ... remember we need to be quick about it because
we only have 10 seconds to do it before the Service Ticket expires.
A couple of things to note:
JSON is the default response, and it is what we want, so we don't need to specify
like {'Accept':'application/json'}, but we will anyway because it is a good practice.
We don't need to pass any parameters to this call, so we just add the parameter
notation and then 'ticket=[The Service Ticet]'
'''
headers = {'Accept':'application/json'}
#behavior_info = requests.get( ('%s?ticket=%s') % (service_call, service_ticket), headers=headers)
# Let's print out our JSON to see what it looks like
# requests support JSON on it's own, so not other package needed for this
# print ('Behavior Information: \n %s \n') % (behavior_info.json() )
'''
Now let's get the names and IDs of some audiences
We can reuse our Ticket Granting Ticket for a 3 hour period ( we haven't passed that yet),
so let's use it to get a service ticket for the audiences service call.
Note that here we do have a parameter that is part of the call. That needs to be included
in the Service Ticket request.
We plan to make a call to the audience service to get the first 10 audiences in the system
ascending by audience id. We don't need to pass the sort order, because it defaults to ascending
'''
# Set up our call and get our new Service Ticket, we plan to sort by id
# Please insert audiences ID below:
audienceids = ['243733','243736','241134','242480','240678','242473','242483','241119','243732','242492','243784','242497','242485','243785','242486','242487','245166','245167','245168','245169','245170','245171','240860']
f = open("publisher_report_1PD.csv", 'w+')
title_str = ['1PD % Contribution','audienceId','publisherName','audienceName']
print >> f,(title_str)
for audience_id in audienceids:
service_call = base_api_url + 'reports/audiences/' + audience_id + '/publisher?stat_interval=LAST_MONTH&page_count=100&page_num=1&sort_attr=audienceName&inc_network=false&sort_order=ASC'
payload = {'service': service_call}
# Let's get the new Service Ticket, we can print it again to see it is a new ticket
service_ticket = requests.post( tg_ticket_location, data=payload ).text
#print ('Here is our new Service Ticket - %s \n') % ( service_ticket )
# Use the new ticket to query the service, remember we did have a parameter this time,
# so we need to & 'ticket=[The Service Ticket]' to the parameter list
audience_list = requests.get( ('%s&ticket=%s') % (service_call, service_ticket)).json()
#print audience_list
# create an array to hold the audiences, pull ou the details we want, and print it out
audiences = []
for ln in audience_list['stats']:
audiences.append({ 'audienceId': ln['audienceId'], 'audienceName': ln['audienceName'], 'publisherName': ln['publisherName'], '1PD % Contribution': ln['percentOfAudience']})
for ii in range( 0, len(audiences) ):
data = audiences[ii]
data_str = json.dumps(data)
result = data_str.replace("\"","")
result1 = result.replace("{1PD % Contribution:","")
result2 = result1.replace("publisherName: ","")
result3 = result2.replace("audienceName: ","")
result4 = result3.replace("audienceId: ","")
result5 = result4.replace("}","")
print >> f,(result5)
# Once we are done with the Ticket Granting Ticket we should clean it up'
remove_tgt = requests.delete( tg_ticket_location )
print ( 'Status for closing TGT - %s') % (remove_tgt.status_code)
i = input('YAY! Gotcha!!')
I see only one reason for your problem: you run script from different folder and then script is looking for config.cfg in different folder.
You can get full path to folder with script
import os
script_folder = os.path.dirname(os.path.realpath(__file__))
and create full path to config.cfg
parser.read( os.path.join(script_folder, 'config.cfg') )

Base64 encode binary uploaded data on the AppEngine

I've been trying to Base64 encode image data from the user (in this case a trusted admin) in order to skip as many calls to the BlobStore as I possibly can. Every time I attempt to encode it, I recieve an error saying:
Error uploading image: 'ascii' codec can't decode byte 0x89 in position 0: ordinal not in range(128)
I've googled the error (the less significant parts) and found that it may have something to do with Unicode (?). The template portion is just a basic upload form, while the handler contains the following code:
def post(self,id):
logging.info("ImagestoreHandler#post %s", self.request.path)
fileupload = self.request.POST.get("file",None)
if fileupload is None : return self.error(400)
content_type = fileupload.type or getContentType( fileupload.filename )
if content_type is None:
self.error(400)
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write( "Unsupported image type: " + fileupload.filename )
return
logging.debug( "File upload: %s, mime type: %s", fileupload.filename, content_type )
try:
(img_name, img_url) = self._store_image(
fileupload.filename, fileupload.file, content_type )
self.response.headers['Location'] = img_url
ex=None
except Exception, err:
logging.exception( "Error while storing image" )
self.error(400)
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write("Error uploading image: " + str(err))
return
#self.redirect(urlBase % img.key() ) #dummy redirect is acceptable for non-AJAX clients,
# location header should be acceptable for true REST clients, however AJAX requests
# might not be able to access the location header so we'll write a 200 response with
# the new URL in the response body:
acceptType = self.request.accept.best_match( listRenderers.keys() )
out = self.response.out
if acceptType == 'application/json':
self.response.headers['Content-Type'] = 'application/json'
out.write( '{"name":"%s","href":"%s"}' % ( img_name, img_url ) )
elif re.search( 'html|xml', acceptType ):
self.response.headers['Content-Type'] = 'text/html'
out.write( '%s' % ( img_url, img_name) )
def _store_image(self, name, file, content_type):
"""POST handler delegates to this method for actual image storage; as
a result, alternate implementation may easily override the storage
mechanism without rewriting the same content-type handling.
This method returns a tuple of file name and image URL."""
img_enc = base64.b64encode(file.read())
img_enc_struct = "data:%s;base64,%s" % (content_type, img_enc)
img = Image( name=name, data=img_enc_struct )
img.put()
logging.info("Saved image to key %s", img.key() )
return ( str(img.name), img.key() )
My image model:
from google.appengine.ext import db
class Image(db.Model):
name = db.StringProperty(required=True)
data = db.TextProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)
owner = db.UserProperty(auto_current_user_add=True)
Any help is greatly appreciated. This code, minus my image encoding in _store_image, comes from the blooger branch of gvdent here.
your store image code could be like this....
img = Image( name=name, data=file.read() )
img.put()
return ( str(img.name), img.key() )
doing base64encode of binary data may increase the size of data itself and increase the cpu encoding and decoding time.
and Blobstore uses the same storage sturcuture as datastore, so it is just making easier to
use file upload store download.

Categories