Python スクリプトの作成

バグ システムの統合のための Python スクリプトは、review_action.pyと呼ばれます。このスクリプトでは次のことを行う必要があります。

  • バグ追跡システムの特定
  • 統合のために行う必要があるアクションの定義
  • バグ トラッカーに送信する指摘情報の指定

オプションで、次のことを行うこともできます。

  • 指摘の詳細ページに表示する [Create a ticket] ボタンのテキストのカスタマイズを行う
  • バグ トラッカーからのコメントの表示に加え、バグ追跡システムのバグ エントリーへの指摘の詳細ページでハイパーリンクの表示を行う
  • スクリプトに診断メッセージを含める

例: Bugzilla への接続を確立する

この例では、ユーザーが Static Code Analysis で [Bugzilla に送信] ボタンをクリックすると、Python スクリプト review_action.py がオープンソースのバグ追跡システム Bugzilla への接続を指定します。review_action.py スクリプトでは一般的ですが、指摘 ID、指摘を見つけたチェッカー、チェッカー メッセージ、指摘の URL など、Bugzilla に送信する必要がある指摘データを定義します(スクリプトの定義済み変数のリストについては、[定義済み変数の使用] を参照してください)。

また、スクリプトには、デフォルト値 (Create a ticket) の代わりにボタンのカスタム名 Send to Bugzilla を定義する定義 #ui.nameも含まれます。set_bug_idメソッドは、指摘の対応する Bugzilla のバグへのハイパーリンクになる Bugzilla へのリンクを指定します。また、success_msgメソッドは、診断メッセージを定義します。

コピー
import sys
import re
import json
import urllib.request
import urllib.error

class InputData:
    """
    Represents the input JSON structure.
    Fields:
    - issue: dict
    - username: str
    """
    def __init__(self, data):
        self.issue = data.get('issue', {})
        self.username = data.get('username', '')

class OutputData:
    """
    Represents the output JSON structure.
    Fields:
    - bugTrackerId: str
    - comment: str
    - status: str
    """
    def __init__(self, output_path):
        self.output_path = output_path
        self.data = {}

    def update(self, key, value):
        self.data[key] = value
        with open(self.output_path, 'w', encoding='utf-8') as f:
            json.dump(self.data, f)

def main():
    input_path = sys.argv[1]
    output_path = sys.argv[2]

    with open(input_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    input_data = InputData(data)
    output_data = OutputData(output_path)

    issue = input_data.issue
    history = issue.get('statusHistory', [])
    username = input_data.username

    def set_bug_id(id):
        output_data.update("bugTrackerId", id)

    def set_comment(comment):
        output_data.update("comment", comment)

    def success(status_msg):
        output_data.update("status", status_msg)

    def fail(s):
        raise RuntimeError(s)

    def bugzillaAPIRequest():
        host = "https://api-dev.bugzilla.mozilla.org/test/latest/bug?username=prezioso@gmail.com&password=klocwork"
        product = "FoodReplicator"
        component = "Salt"
        version = "1.0"
        target = "---"
        summary = "%s | %s | %s | %s | %s | %s | %s" % (
            issue.get("id", ""), issue.get("code", ""), issue.get("severity", ""),
            issue.get("severityCode", ""), issue.get("status", ""), issue.get("url", ""), issue.get("project", ""))
        op_sys = "Linux"
        platform = "All"

        jdata = json.dumps({
            "product": product,
            "component": component,
            "version": version,
            "target": target,
            "summary": summary,
            "opsys": op_sys,
            "platform": platform
        })
        jdata_bytes = jdata.encode('utf-8')

        req = urllib.request.Request(host, jdata_bytes, {'Content-Type': 'application/json', 'Accept': 'application/json'})

        try:
            resp = urllib.request.urlopen(req)
            content = resp.read().decode('utf-8')
            if not content:
                fail("Empty response from server")
            obj = json.loads(content)
            ref = obj.get('ref', '')
            id = obj.get('id', '')
            set_bug_id(id)
            return "%s" % (ref)
        except urllib.error.HTTPError as e:
            print(f"HTTPError: {e.code}", file=sys.stderr)
            if e.code == 201:
                content = e.read().decode('utf-8')
                if not content:
                    fail("Empty response from server")
                obj = json.loads(content)
                ref = obj.get('ref', '')
                id = obj.get('id', '')
                set_bug_id(id)
                return "%s" % (ref)
            else:
                fail("HTTP error: %s" % e)
        except Exception as e:
            fail("Request failed: %s" % e)
        return "fail"

    success_msg = bugzillaAPIRequest()

    if (success_msg == "fail"):
        fail("ERROR - HTTP POST REQUEST")

    success(success_msg)

if __name__ == "__main__":
    main()

注記:

  • 最新の review_action.py スクリプトは Python 3 構文に更新されています。古いスクリプトを更新して、レガシー Python 2 コードを削除してください。互換性とサポートを確保するため、今後の変更は更新された Python 3 構造に基づいて行ってください。

  • 指摘オブジェクトは JSON オブジェクトになりました。指摘フィールドには issue.get("id") を指定してアクセスすることができます。

  • 履歴オブジェクトは、履歴オブジェクトの JSON 配列です。履歴フィールドには history[0]['owner'] を指定してアクセスすることができます。

  • スクリプト内の input_data は以下の形式になります。

コピー
{
"issue": {
"id": 95,
"groupId": 0,
"name": "Buffer Overflow - Array Index Out of Bounds",
"message": "Array \u0027i\u0027 of size 10 may use index value(s) 10",
"file": "C:\\Users\\dkumar\\Downloads\\projs\\c\\c_proj\\testFile.c",
"line": 8,
"code": "ABV.GENERAL",
"severity": "Critical",
"severityCode": 1,
"supportLevel": "Klocwork Certified",
"supportLevelCode": 1,
"bugTrackerId": "12345",
"state": "New",
"status": "Analyze",
"lastUpdateDate": 1753821247663,
"owner": "unowned",
"project": "ff",
"buildName": "build_2",
"url": "http://localhost:8080/review/insight-review.html#issuedetails_goto:problemid\u003d95,project\u003dff,view_id\u003d1",
"statusHistory": [
  {
        "date": 1753821247663,
        "userid": "dkumar",
        "comment": "Bug created successfully.",
    "status": "new",
    "owner":"smith"
  },
  {
        "date": 1753783959710,
        "userid": "dkumar",
        "comment": "Fix",
    "status": "new",
    "owner":"john"
  }
]
},
"username": "john"
 }

スクリプトをインストールすると、指摘ウィンドウは次のように表示されます。BUG ID フィールドは指摘の Bugzilla のエントリーへのハイパーリンクを示しています。

Image:bug_tracker_11.png

例: Rational Team Concert との統合

以下の例は、お使いの Klocwork 環境を Rational Team Concert と統合するために必要なステップを詳細に示したものです。

RTC xml ドキュメントのナビゲート方法

RTC で業務詳細を作成するには、まず、RTC サーバーから一連の xml ドキュメントを要求することにより、関連 URL とプロジェクトキーを特定する必要があります。このステップは次のステップと似ています。

  1. RTC REST API から xml ルートサービスドキュメントをフェッチします: http(s)://yourRTCserver:port/ccm/rootservices
  2. "oslc_cm:cmServiceProviders" 内 "rdf:resource" 属性である Services Catalog URL を抽出します。
            <oslc_cm:cmServiceProviders
                     xmlns:oslc_cm="http://open-services.net/xmlns/cm/1.0/"
                     rdf:resource="http(s):// yourRTCserver:port /ccm/oslc/workitems/catalog" 
            />
  3. プロジェクトのタイトル (element dc:title of element oslc_disc:ServiceProvider) を特定することにより、Service Catalog の中から関連するプロジェクトのサービスプロバイダー URL (要素 "oslc_disc:services" の属性 "rdf:resource" of element) を探します。
    URL には一意のプロジェクトキー (_Day3sOHaEeO1tdkD6QbZUQ など) が含まれています。
  4. サービスプロバイダの URL から、適切なサービスファクトリーの URL ("oslc_cm:url") (要素 “oslc_cm:changeRequests oslc” の要素 “oslc_cm:factory” の<dc:title>Location for creation of change requests</dc:title>) を抽出します
            <oslc_cm:changeRequests oslc_cm:version="1.0">
                            …
                            <oslc_cm:factory>
                                    >dc:title<Location for creation of change requests>/dc:title<
                                    >oslc_cm:url< http(s):// yourRTCserver:port /ccm/oslc/contexts/_Day3sOHaEeO1tdkD6QbZUQ/workitems</oslc_cm:url>
                            </oslc_cm:factory>
                            …
            </oslc_cm:changeRequests>
  5. 業務詳細をファクトリー URL にアップします。たとえば、JSON エンコードの業務詳細は次のようになります。
    {
                    "dc:title":"Klocwork ID 42: Suspicious dereference of pointer in function call before NULL check: cvs\\src\\checkin.c",
                    
                    "dc:description":"This issue was detected by Klocwork static code analysis.
                     \n
                     \t\t\nId: 42    URL: http(s)://yourKlocworkServer:port/review/kw-review.htm#issuedetails_goto:problemid=42,project=CVS,view_id=1
                     \nExported to RTC by: jchapman
                     \n
                     \nFile: cvs\\src\\checkin.c
                     \n
                     \nChecker: RNPD.CALL
                     \nType: Suspicious dereference of pointer in function call before NULL check
                     \nMessage: Suspicious dereference of pointer 'options' by passing argument 1 to function 'strcmp' at line 63 before NULL check at line 76
                     \nSeverity: Critical(1)
                     \nStatus: Analyze
                     \nState: Existing
                     \nOwner: azukich
                     \nLast Update: No Updates
                     \nHistory: \n",
                            
                            "dc:type": "task",
                            
                            "oslc_cm:priority": " http(s):// yourRTCserver:port /ccm/oslc/enumerations/_Day3sOHaEeO1tdkD6QbZUQ/priority/priority.literal.l4"
    }
  6. 動作が正常に行われた場合、業務詳細がアップされた URL は、レスポンスヘッダー内の “Location” に、業務詳細の ID はレスポンス要素 “dc:identifier” に表示されます。

認証の取り扱い方法

初期設定では、RTC サーバーがフォームの記入による認証方法を採用するようにセットアップされています。そのため、RTC サーバーに何かをアップする際には、認証レスポンスリクエストを検索する必要があります。 例:

コピー
import urllib.request
import urllib.parse
import urllib.error

targetUrl = "..."  # your target URL
data = b"..."      # your data as bytes
hostbase = "..."   # your host base
rtc_username = "..."  # your username
rtc_password = "..."  # your password

req = urllib.request.Request(targetUrl, data, {'Content-Type': 'application/json', 'Accept': "application/xml"})
try:
    response = urllib.request.urlopen(req)
    if response.info().get('X-com-ibm-team-repository-web-auth-msg') == 'authrequired':
        login_data = urllib.parse.urlencode({'j_username': str(rtc_username), 'j_password': str(rtc_password)}).encode('utf-8')
        reqLogon = urllib.request.Request(
            hostbase + "/j_security_check",
            login_data,
            {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': "application/json"}
        )
        response = urllib.request.urlopen(reqLogon)
        if response.info().get('X-com-ibm-team-repository-web-auth-msg') == 'authfailed':
            raise RuntimeError("RTC logon failed")
        else:
            if data is not None:
                response = urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
    eData = e.read().decode('utf-8')
    if hasattr(e, 'code'):
        if e.code == 201:
            # process response
            pass
        elif e.code == 400:
            # process error message
            pass

フィールド値の取り扱い方法

フィールド値の列挙子 ("oslc_cm:priority" など) は、プロジェクトごとに定義され、RTC 管理者がカスタマイズすることができます。特定のプロジェクトに対して設定された値を発見するには、お使いのブラウザーとプロジェクトに一意のプロジェクトキーを使ってサーバーにクエリを出します。例:

http(s):// yourRTCserver:port/ccm/oslc/enumerations/_Day3sOHaEeO1tdkD6QbZUQ/priority

ヘルプが必要ですか?静的コード解析プロフェッショナルサービスチームにお問い合わせください。

静的コード解析プロフェッショナルサービスチームは、Bugzilla、JIRA、および IBM Rational Team Concert 向けのベーシックスクリプトを作成してきた長い実績があります。スクリプトはご希望のインストールに合わせてカスタマイズすることができます。ヘルプが必要な場合は、静的コード解析プロフェッショナルサービスチームまでお問い合わせください。

[Create a ticket] ボタンのカスタマイズ

review_action.py スクリプトを projects_root/config ディレクトリに配置すると、Static Code Analysis により、プロジェクトの Static Code Analysis で、指摘の詳細ページに [Create a ticket] ボタンが表示されます。(リンクを表示するにはブラウザーの更新が必要になることがあります)ボタンに別の名前を指定する場合は、スクリプトに次の行を追加します。

#ui.name:<my custom button>

例:

#ui.name:Send to Bugzilla

スクリプトがインストールされると、指摘ウィンドウに新しいボタンが表示されます。

Image:bug_tracker_31.png

ボタン名に日本語を使用するには、review_action.py スクリプトに UTF-8 エンコーディングを含めます。

診断メッセージの組み込み

[Create a ticket] ボタンをクリックしたときにバグレポートが正常に保管されると、Static Code Analysis ウィンドウに、[Ticket created] メッセージが表示されます。失敗すると、[Bug reporting failed] と表示されます。成功時と失敗時のカスタム診断メッセージを指定するには、Python スクリプトで次のメソッドを使用します。

  • success(custom_message)
  • fail(custom_message)

スクリプトで fail() メソッドを呼び出すと、スクリプトの実行がさらに中断されます。

projects_root へのスクリプトの配置

review_action.pyスクリプトが完了したら、プロジェクトのprojects_root/configディレクトリに配置します。

利用可能な変数

review_action.py スクリプトは、JSON 構造で以下の変数を入力として受け取ります。

variable.field 使用方法
username [Create a ticket] ボタンでスクリプトをアクティブにしたユーザーの名前
issue 次のフィールドのスクリプトに関する情報が含まれているクラス Issue の Python オブジェクト
issue.id Static Code Analysis での指摘の識別子
issue.groupId Static Code Analysis で指摘が属するグループの識別子。値がゼロの場合、グループ計算がオフになっていることを示すので、この変数は使用しないでください。
issue.name Static Code Analysis 指摘の詳細ページからの指摘の名前
issue.message Static Code Analysis での指摘のチェッカーメッセージ
issue.file 指摘が出現したファイル
issue.code 指摘を見つけたチェッカーの名前
issue.severity テキスト形式 (数値以外) の指摘の重要度
issue.severityCode 数値形式の指摘の重要度
issue.state 指摘のステート - 既存 (Existing) または修正済み (Fixed)
issue.status 要修正 (Fix) または解析 (Analyze) などの指摘のステータス
issue.lastUpdateDate 最終更新の時間 (ミリ秒単位)
issue.owner 指摘のオーナー
issue.project Klocwork プロジェクトの名前
issue.url Static Code Analysis での指摘の URL
issue.statusHistory 更新履歴イベントを持つ配列。各イベントは、次のフィールドがあるクラス StatusHistoryEvent のオブジェクトです。
  • date - イベントの日付 (ミリ秒単位)
  • userid - イベント作成者のユーザー名
  • owner - 新しい指摘のオーナー
  • status - 新しい指摘ステータス
  • comment - 更新プロセスで作成されたコメント
issue.bugTrackerId Static Code Analysis での指摘のバグトラッカー ID