Microsoft Visual Studio Team System 2008 Team Foundation Server でのポリシーの適用

Visual Studio 2008

Ryan Frazier

Microsoft Corporation

2007 年 12 月

対象 :

Microsoft Visual Studio Team System 2008 Team Foundation Server

Microsoft Visual Studio 2005 Team Foundation Server

概要 : この資料では、ポリシーの配置とバージョン管理のメカニズム (多数の開発ワークステーションが必要なエンタープライズ開発組織向け) について説明し、ポリシーの適用に使用できるサンプル ソリューションの概要を示します。

目次

はじめに

配置の戦略

ポリシー配置の自動化の概要

Team Foundation Server のアプリケーション層でのワークフロー

配置サービス

クライアント

まとめ

はじめに

開発組織で一般的に必要とされる要素は、ソース管理下にある変更をソース管理ストアに統合する関連ワークフローが操作するポリシーを開発者のワークステーションに適用する機能です。そのポリシーによって、コーディング標準、組織的な処理手順への準拠、複雑なルール優先ワークフローなどが適用されます。

Microsoft Visual Studio Team System 2008 Team Foundation Server (および Microsoft Visual Studio 2005 Team Foundation Server) には、カスタム チェックイン ポリシーを使用して、このようなポリシーを適用するメカニズムが用意されています。チェックイン ポリシーを使用すると、開発者は、コードが適用対象の有効なポリシーに準拠していない限り、そのコードをソース管理ツリーにチェックインできなくなります。Team Foundation Server には、一連の基本的なポリシーが含まれていますが、開発者は、ソース管理環境で使用できるカスタム ポリシーを作成して構成することができます。

さらに、開発組織の規模が大きいほど、ポリシー自体の管理を実装に含める必要性が高くなります。懸案事項には、各ポリシーのライフサイクルを管理する方法や、開発者のワークステーションごとにポリシーを適用する方法などがあります。

配置の戦略

カスタム チェックイン ポリシーには、Team Foundation Server のポリシー フレームワークとの接続および通信を確立するための 2 つのインターフェイスが実装されています。1 つ目の IPolicyDefinition インターフェイスにより、ポリシーの構成に必要なメタデータが、Microsoft Visual Studio チーム エクスプローラの [ソース管理の設定] ダイアログ ボックスの [チェックイン ポリシー] タブに表示されます (図 1 参照)。2 つ目の IPolicyEvaluation インターフェイスは、クライアント ワークステーションで実際にポリシーの評価を実行する際に呼び出されます。Team Foundation Server のチーム プロジェクトでポリシーが有効になっていても、クライアント ワークステーションにそのポリシーがインストールされていない場合、そのポリシーはエラーと見なされ、コードをチェックインすることができません。

図 1. チーム エクスプローラの [ソース管理の設定] ダイアログ ボックス

図 1. チーム エクスプローラの [ソース管理の設定] ダイアログ ボックス

そのため、開発に多数の開発ワークステーションが必要になる可能性があるエンタープライズ開発組織では、ポリシーの配置とバージョン管理を実現する堅牢なメカニズムが必要となる場合があります。この資料では、そのようなメカニズムの 1 つについて説明し、ポリシーの適用に使用できるサンプル ソリューションの概要を示します。サンプル ソリューションでは、既存の機能とツールを活用し、カスタム開発と保守作業を最小限に抑えるため、可能な限り Team Foundation Server の既定の機能を使用しています。

ポリシー配置の自動化の概要

ポリシーが適用の強制を伴うことを考慮すると、有効なポリシーを手動で (つまりユーザーが行う) 要求およびインストールすることは、無駄な戦略となる可能性があります。一方、ワークステーションで有効なポリシーを自動的に要求してインストールするメカニズムでは、ポリシーで事実上必須となっている処理 (つまり適用の強制) が実行されます。

図 2 のトポロジは、カスタム ポリシーの検出、インストール、およびバージョン管理を自動で行うクライアントとサーバー間のマルチパートのサンプル ソリューションです。

図 2. サンプル ソリューション トポロジ

図 2. サンプル ソリューション トポロジ

Team Foundation Server のアプリケーション層でのワークフロー

Team Foundation Server の機能の 1 つである作業項目トラッキングでは、(タスクやバグなど) 標準的な作業単位をトラックするための柔軟でカスタマイズ可能なワークフローと、配置およびバージョン管理の作業項目の種類と関連ワークフローを作成するのに必要な拡張機能が提供されます。

また、作業項目の種類と関連ワークフローを特定のチーム プロジェクト合わせてカスタマイズすることができます。チーム プロジェクトの概念に関する広範な議論については、この資料では取り上げません。ここでのチーム プロジェクトは、メジャー リリースのソフトウェア プロジェクトや以前のリリースの特定の機能セットを強化するメンテナンス プロジェクトなど、全体的な作業においてリンクされた要素の論理コンテナとして機能すること、また、別の大枠の作業単位に含まれる、通常は関連のない要素間で論理的な境界としての役割も果たすということだけ言及しておきます。

このサンプル ソリューションでは、ポリシーに関連する成果物の作業項目の種類、ワークフロー、およびセキュリティを一元管理するため、開発チーム プロジェクトに含まれない個別のチーム プロジェクトを使用しています。実例では、アプリケーション ライフサイクル マネジメント (プロジェクト管理、リリース管理、設計、開発、テストなど) に携わるメンバから、単一のチーム プロジェクトを使用して組織の基準や (そうした基準を適用する) 方法を使用して共同作業を行うメンバまで、職務や部門の枠を超えて多様な IT メンバが関係する場合があります。

カスタム作業項目の種類を作成して、個別のワークフローを操作できます。この場合の種類は、"チェックイン ポリシー" です。このカスタム作業項目の種類は、作成されたチーム プロジェクトでのみ有効なものですが、組織でのポリシーの作成、承認、開発、テスト、および配布に関連するカスタム ワークフローと状態がすべて含まれます。

この例では、ポリシーのワークフローに、カスタム チェックイン ポリシーのコンポーネントの開発が含まれます。したがって、関連するチーム プロジェクト内のソース管理ツリーは、コードの論理的な格納場所になります。ただし、ポリシーのリテール バイナリは、開発ワークステーションに配置される必要があります。そこで、作業項目の添付ファイルの登場です。

通常、添付ファイルは、関連ファイルを個々の作業項目にリンクするために使用します。ファイルの種類に制限はなく、あらゆる種類のファイル (文書、画像、実行可能ファイル、MSI パッケージなど) を添付できます。この例では、リテール バイナリ (またはインストールの実行可能ファイルや MSI パッケージ) は、"チェックイン ポリシー" 作業項目を運用環境に配布する準備ができたときに、手動ワークフローの一部として、添付ファイルとして追加されました。リテール バイナリを添付ファイルとして作業項目に追加する処理は、必要に応じてチェックイン処理や自動化されたビルドの一環として自動化することもできます。

この時点で、共同作業を行い、組織で使用するカスタム チェックイン ポリシーを開発する環境が用意できています。また、ポリシーのライフサイクルを追跡するカスタム ワークフローを作成しました。最後に、配置時に Team Foundation Server SDK の API を使用して操作できる場所にポリシーのバイナリを公開しました。

配置サービス

この例では、Microsoft Windows Communication Foundation (WCF) を使用して、Team Foundation Server でアプリケーション サービスを提供します。このサービス インターフェイスによって、Team Foundation Server のアプリケーション層からポリシー (および他の項目) をインストールする機能がクライアント (開発ワークステーションなど) に提供されます。WCF は、.NET、Java など、他のクライアントのコードベースでの複数プラットフォームのサポートを提供するサービスを実装するテクノロジとして採用されました。この資料で後述するクライアントを Team Foundation Server 外で使用する場合は個別の実装が必要になりますが、WCF のサービスの実装は再利用できます。

他のサービス指向リレーションシップと同様に、サービス プロバイダとコンシューマ間には、交換されるデータが抽象的に記述された正式な契約であるデータ コントラクトが必要です。このコントラクトでは、クライアント向けの更新を受信する際にネットワーク経由で転送されるデータが、パラメータと戻り値の型ごとに具体的に定義されています。この例では、さまざまなクライアントに対応するため、プラットフォームに依存しない (つまり、特定のテクノロジに結び付けられない) コントラクトにする必要があります (リスト 1 参照)。

/// <概要>
/// 一対多の更新で構成される、配置可能な個別のパッケージを表します。
/// </概要>
[DataContract]
public class DeploymentPackage
{
    private List<ClientUpdate> _updates;

    public DeploymentPackage() { }

    public DeploymentPackage(ClientUpdate[] updates)
    {
        _updates = new List<ClientUpdate>(updates);
    }

    [DataMember]
    public ClientUpdate[] AvailableUpdates
    {
        get
        {
            if (_updates != null)
            {
                ClientUpdate[] dest = new ClientUpdate[_updates.Count];
                _updates.CopyTo(dest, 0);
                return dest;
            }
            return null;
        }
        set
        {
            _updates = new List<ClientUpdate>(value);
        }
    }

    public void Add(ClientUpdate item)
    {
        if (_updates == null)
            _updates = new List<ClientUpdate>();
        _updates.Add(item);
    }

    public bool Remove(ClientUpdate item)
    {
        if (_updates == null || _updates.Count == 0)
            return false;
        else
            return _updates.Remove(item);
    }
}
/// <概要>
/// 更新のコレクションを表します (全作業項目の一部)。
/// </概要>
[DataContract]
public class ClientUpdate
{
    private List<Update> _updateItems;
    private int _workItemId;

    public ClientUpdate(int workItemId)
    {
        _workItemId = workItemId;
    }

    public ClientUpdate(int workItemId, Update[] items)
        : this(workItemId)
    {
        _updateItems = new List<Update>(items);
    }

    [DataMember]
    public int WorkItemId
    {
        get { return _workItemId; }
        set { _workItemId = value; }
    }

    [DataMember]
    public Update[] Binaries
    {
        get
        {
            if (_updateItems != null)
            {
                Update[] dest = new Update[_updateItems.Count];
                _updateItems.CopyTo(dest, 0);
                return dest;
            }
            return null;
        }
        set
        {
            _updateItems = new List<Update>(value);
        }
    }

    public void Add(Update item)
    {
        if (_updateItems == null)
            _updateItems = new List<Update>();
        _updateItems.Add(item);
    }

    public bool Remove(Update item)
    {
        if (_updateItems == null || _updateItems.Count == 0)
            return false;
        else
            return _updateItems.Remove(item);
    }
}

/// <概要>
/// インストール可能な個別の更新を表します (作業項目の一部)。
/// </概要>
[DataContract]
public class Update
{
    private byte[] _content;
    private string _name;
    private string _fileName;
    private string _extension;

    public Update(string name, string fileName, string extension, byte[] content)
    {
        _name = name;
        _fileName = fileName;
        _content = content;
        _extension = extension;
    }

    [DataMember]
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    [DataMember]
    public string FileName
    {
        get { return _fileName; }
        set { _fileName = value; }
    }

    [DataMember]
    public string Extension
    {
        get { return _extension; }
        set { _extension = value; }
    }

    [DataMember]
    public byte[] Content
    {
        get { return _content; }
        set { _content = value; }
    }
}

リスト 1. 配置パッケージのデータ コントラクト

このサンプルのデータ コントラクトでは、ダウンロード可能な各項目 (ここではポリシー) を、更新パッケージ内で個別に表すことができます。コントラクトを定義したら、次に、サービスの実装と、使用可能な更新をパッケージ化するために各クライアントから要求される情報に焦点を合わせる必要があります。

Team Foundation Server の作業項目トラッキングのフレームワークを使用することで、運用環境に配布されている各ポリシーの一意識別子である作業項目 ID を自動的に取得できます。クライアントの実装については、この後すぐに説明しますが、ここではクライアントがインストールされた最新のポリシーの状態を保持しているという前提で話を進めます。クライアントは、最後に適用した更新パッケージの一意な作業項目 ID を送信できるので、サービスでは、その ID を使用して作業項目ストアに対してクエリを実行し (リスト 2 参照)、更新が必要かどうかを判断できます (運用環境に新しく配布されたポリシーがあるなど)。

[ServiceContract]
public interface IDeploymentService
{
    [OperationContract()]
    DeploymentPackage RequestUpdates(string teamFoundationServer, int lastSuccessfulWorkItemId);
}

public class DeploymentService : IDeploymentService
{
    public DeploymentPackage RequestUpdates(string teamFoundationServer, 
int lastSuccessfulWorkItemId)
    {
        bool addToPackage = false;

        // Team Foundation Server に接続し、作業項目ストアに接続します。
        // TeamFoundationServerFactory の使用により、接続後にインスタンスがキャッシュ
        // され、キャッシュされたインスタンスは、後からの要求に応じて使用できます。
        TeamFoundationServer server = 
TeamFoundationServerFactory.GetServer(teamFoundationServer);
        server.EnsureAuthenticated();
        WorkItemStore workItems = (WorkItemStore)server.GetService(typeof(WorkItemStore));


        // 共同作業プロジェクトから対象のすべての作業項目を取得する
        // クエリを設定します。対象となるのは、その種類が CheckInPolicy で、
        // ダウンロードしても問題ない状態の作業項目です。
        Dictionary<string, object> queryContext = new Dictionary<string, object>();
        queryContext.Add("TeamProject", "PolicyCollaborationProject"); //
        queryContext.Add("WorkItemType", "CheckInPolicy");
        queryContext.Add("LastApplied", lastSuccessfulWorkItemId);
        queryContext.Add("AvailableWorkItemState", "Released");


        // 作業項目クエリを作成して実行します。
        Query workItemQuery = new Query(workItems, "SELECT System.Id, System.Title FROM WorkItems" +
                                                   " WHERE System.TeamProject = @TeamProject" +
                                                   " AND System.WorkItemType = @WorkItemType" +
                                                   " AND System.Id > @LastApplied" +
                                                   " AND System.State = @AvailableWorkItemState" +
                                                   " ORDER BY System.Id ASC", queryContext);
        ICancelableAsyncResult asyncResult = workItemQuery.BeginQuery();
        WorkItemCollection availableUpdates = workItemQuery.EndQuery(asyncResult);

        // 結果を確認し、クライアントへの配布用にパッケージ化します。
        if (availableUpdates != null)
        {
            WebClient downloadUtility = new WebClient();
            downloadUtility.Credentials = server.Credentials;
            DeploymentPackage package = new DeploymentPackage();
            
            // 各作業項目を配置パッケージ内でクライアントの個別の更新としてパッケージ化します。
            foreach (WorkItem item in availableUpdates)
            {
                ClientUpdate update = null;
                addToPackage = false;
                foreach (Attachment file in item.Attachments)
                {
                    // ここで簡単な検証を実行します。
                    if (string.Compare("EXE", file.Extension.TrimStart(new char[] { '.' }), StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        // WebClient を使用してファイルをダウンロードします。
                        if (update == null) update = new ClientUpdate(item.Id);
                        update.Add(new Update(file.Comment, file.Name, file.Extension, downloadUtility.DownloadData(file.Uri)));
                        addToPackage = true;
                    }
                }
                if (addToPackage) package.Add(update);
            }
            return package;
        }
        return null;
    }
}

リスト 2. WCF 配置サービスのコントラクトと実装

この方法では、サービスの実装によって、クライアントのインストールに関する状態は保持されず、このことが問題を引き起こす可能性があります。また、サービスの実装は、ステートレスで、作業の大半を Team Foundation Server API に委譲し、Team Foundation Server のアプリケーション層を使用してスケール アウトできます。

クライアント

このサンプル ソリューションを利用するには、次の処理を行うクライアント コンポーネントが必要です。

  1. 特定のイベントで使用可能な更新を確認する。
  2. 更新のインストールを管理する。
  3. 必要な更新を有効にするためにクライアント環境を再起動する。

Microsoft Visual Studio では、クライアント コンポーネントの作成に使用できる複数の統合オプションが提供されます。このような機能拡張オプションの 1 つに、Visual Studio の起動時に読み込まれるオートメーション アドインを作成する機能があります。ただし、アドインは無効にすることができるので、ポリシー適用の要件が回避されることがあります。そのため、Visual Studio パッケージ (VSPackage) を使用する方法をお勧めします。

VSPackage は、UI 要素、サービス、プロジェクト、エディタ、デザイナなどを提供することで、Visual Studio 統合開発環境 (IDE) を拡張するソフトウェア モジュールです。また、配置、ライセンス、およびセキュリティの単位として使用することもできます。ただし、Visual Studio で VSPackage を読み込むには、有効なパッケージ ロード キー (PLK) が必要です (PLK は Microsoft Visual Studio Industry Partner (VSIP) Web サイトから取得できます)。

どちらの方法を使用する場合でも、開発者のワークステーションへのクライアント コンポーネントのインストールには、Microsoft System Center Configuration Manager 2007 など、ポリシー ベースのインストール メカニズムを使用する必要があります。

統合方法が確定したら、クライアント ロジックに焦点を合わせます。基本的に、クライアント ロジックでは、チーム エクスプローラ クライアントに接続してから、特定の状態の変更が行われたときに配置サービスと通信する必要があります。クライアントをチーム エクスプローラに接続するには、Microsoft.VisualStudio.TeamFoundation 名前空間に含まれる TeamFoundationServerExt クラスを使用します (このオブジェクトのインスタンスを Visual Studio シェル内から取得する方法の例については、Team Foundation Server の SDK を参照してください)。

オブジェクトを取得したら、開発者がチーム エクスプローラでアクティブなチーム プロジェクトのコンテキストを変更したときに作成される通知をサブスクライブできます。その後、接続先の新しい Team Foundation Server とチーム プロジェクトを取得できます。このような通知は、チーム エクスプローラの初回起動時に自動的に作成され、その後も継続して作成されます。そのため、常に最新の情報を入手することができます。アクティブなチーム プロジェクトが変更された場合には、配置サービス プロバイダと通信し、新しいサーバーとチーム プロジェクトに基づいて更新が必要かどうかを検出します。

更新の要求

ソリューションで更新の正確な検出を実行するには、クライアントは、なんらかの形に残る状態でサーバーと通信する必要があります。前述のとおり、この状態は、最後に正常にインストールされた更新パッケージの作業項目 ID です (最初の接続試行の場合には作業項目 ID はありません)。組織の要件に応じて、追加情報を使用し、クライアントの状態を正確かつ一意に識別するコンテキスト (つまり、チーム プロジェクト) を指定できます。たとえば、各開発組織には個別の Team Foundation Server アプリケーション層があり、ポリシーの役割も異なる可能性があります。ID は、クライアント ファイル システム上の一定の場所に格納され、さらに難読化が必要な場合にはディスク上でセキュリティが確保される (つまり暗号化される) 場合があります。

この状態は、更新の検出が行われたときに配置サービスに通知されます。更新がクライアントで受信されると、クライアントは、その更新をローカル ワークステーションにインストールし、後でトラブルシューティングを行う場合に使用できるように、失敗したインストールの監査情報のログを記録する必要があります。

これで、インストールの管理責任はクライアントが持つようになります。ただし、実装については状況が異なる可能性があります。更新は、新しいバイナリまたは既存のバイナリの新しいバージョンで構成されている可能性があります。前述の "チェックイン ポリシー" ワークフローとクライアント インストール ロジックの両方において、インストールの検証処理への関与とその役割の共有 (または、どちらか一方) を行うことをお勧めします。たとえば、クライアント コンポーネントで、インストールを実行する実行可能ファイルの起動に System.Diagnostics.Process オブジェクトを使用する場合、無効なファイルの種類が添付ファイルの一覧に含まれるときに、(前述の) サーバー側のワークフローで、作業項目の状態が "配布" に移行しないようにすると役立つ場合があります。または、配置サービスへのクライアント要求の一環として更新を検出する作業項目クエリを、クライアント インストーラの種類に関連付け (ここでも、そのクライアント状態の一部として)、特定のインストーラに対して有効な添付ファイルのみを選択することができます。

Listing 3 shows an example of client-side validation that uses a System.Diagnostics.Process object to kick-off an executable installer.

private static void OnErrorDataReceived(object sender, DataReceivedEventArgs args)
{
    // 失敗した更新によって生成されたエラー情報を監査します。
}

public void CheckForUpdates(string teamFoundationServer, int lastSuccessfulWorkItemId)
{
    IDeploymentService client = null;
    int currentWorkItemId = lastSuccessfulWorkItemId;

    try
    {
        bool updateApplied = false;

        // WCF クライアント プロキシ チャネルを作成します。
        ChannelFactory<IDeploymentService> factory = new ChannelFactory<IDeploymentService>();
        client = factory.CreateChannel();

        // 更新が利用できるかどうかを確認します。
        DeploymentPackage package = client.RequestUpdates(teamFoundationServer, lastSuccessfulWorkItemId);

        // 配置サービスから更新を受信した場合は、更新を適用します。
        if (package != null && package.AvailableUpdates != null && package.AvailableUpdates.Length > 0)
        {
            foreach (ClientUpdate update in package.AvailableUpdates)
            {
                updateApplied = false;
                foreach (Update item in update.Binaries)
                {
                    // ...
                    // Update クラスのプロパティを使用してファイルをディスクにコピーします (コードは省略)。
                    // ...

                    // パッケージを実行し、有効な完了コードを待機します。
                    using (Process process = new Process())
                    {
                        process.StartInfo.FileName = Path.GetTempPath() + @"\" + item.FileName; // これは、ファイルのコピーが、処理の一時パスに作成されたことを前提としています。
                        process.StartInfo.CreateNoWindow = true;
                        process.StartInfo.UseShellExecute = false;
                        process.StartInfo.RedirectStandardError = true;
                        process.ErrorDataReceived += new DataReceivedEventHandler(OnErrorDataReceived);
                        process.Start();
                        process.BeginErrorReadLine();
                        process.WaitForExit();
                        process.Refresh();
                        updateApplied = process.ExitCode == 0;
                    }

                    // この部分の更新が正常に適用されなかった場合は、この更新を中断します。
                    if (!updateApplied)
                        break;

                }
                if (updateApplied)
                {
                    currentWorkItemId = update.WorkItemId;
                }
            }
        }

        // 配置サービスの接続を閉じます。
        ((IClientChannel)client).Close();
    }
    catch (TimeoutException timeEx)
    {
        // 監査を行い、中止します。

        ((IClientChannel)client).Abort();
    }
    catch (FaultException unknownFaultEx)
    {
        // 監査を行い、中止します。

        ((IClientChannel)client).Abort();
    }
    catch (CommunicationException commEx)
    {
        // 監査を行い、中止します。

        ((IClientChannel)client).Abort();
    }
    catch (Exception ex)
    {
        // 監査を行い、中止します。

        ((IClientChannel)client).Abort();
    }
    finally
    {
        if (currentWorkItemId != lastSuccessfulWorkItemId)
        {
         // 更新が正常に適用されたら、クライアントのローカルの状態を更新し、
            // 最後に正常に適用された更新の新しい作業項目 ID を反映する必要があります。
        }
    }
}

リスト 3. クライアント側のインストール

次の処理手順では、インストールされた更新に基づいてクライアントのローカルの状態を更新するか、更新が失敗した場合には開発者に視覚的な情報を提供します。更新のインストールが失敗した場合は、状態を変更しないようにする必要があります。つまり、クライアントは、次回の要求 (おそらく、インストールの失敗の原因となった問題が解決した後) で同じ更新を受信します。

最後に、更新が正常にインストールされたら、Visual Studio の再起動を要求することをお勧めします。

まとめ

Microsoft Visual Studio Team System 2008 Team Foundation Server とカスタム チェックイン ポリシーを使用すると、ポリシーを開発ワークステーションに適用する柔軟かつ拡張可能なメカニズムが提供されます。大規模な組織では、実際のポリシーのライフサイクルを管理するために、構造化された処理と管理が必要となる場合がありますが、Team Foundation Server では、拡張可能な作業項目トラッキングのフレームワークと Visual Studio IDE との統合により、ソリューションが提供されます。

コミュニティの追加

追加
表示: