0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DENSOAdvent Calendar 2024

Day 1

Jamfのインベントリプリロード機能を活用してMacを効率的に管理する方法

Last updated at Posted at 2024-11-30

この記事は DENSOアドベントカレンダー2024 の1日目の記事です。

目的

本記事では、Jamf Proを利用して、ゼロタッチデプロイメント前の在庫Macの物理的な場所や状態を効率的に管理する方法を紹介します。この手法により、在庫管理が簡素化され、手動での追跡作業を削減できます。


背景

Jamf Proは、利用者にデバイスを割り当てる機能が豊富ですが、物理的な在庫の追跡や状態の管理には特化していません。例えば、複数のオフィスや拠点にまたがる在庫を管理する場合、外部ツールを併用する運用となることもあります。しかし、Jamf Proのインベントリプリロード機能を活用すれば、CSVファイルを用いて物理的な場所や状態を直接登録し、Jamf Pro内で一元管理することが可能です。


ソリューション概要

インベントリプリロード機能は、CSVファイルを使用してデバイス情報を一括登録・更新できるJamf Proの機能です。この機能を利用すると、以下の情報をJamf Proに登録して管理できます:

  • 物理的な場所(例:本社、倉庫Aなど)
  • デバイスの状態(例:新品、使用中)
  • 固定資産番号などのカスタム情報

公式ドキュメント:Jamf Pro Inventory Preload 機能


手順

ステップ1: CSVファイルの準備

手順

  1. Jamf Proにログインします。
  2. 左側メニューから「Settings」を選択します。
  3. 「Global Management」セクションの「Inventory Preload」をクリックします。
  4. 「Template」をクリックしてCSVテンプレートをダウンロードします。

CSVファイルの項目

CSVファイルには以下の情報を記載できます。必須項目に加え、カスタム項目(Extension Attributes)を追加して組織特有の情報を登録できます。

  • 必須項目:
    • シリアルナンバー(Serial Number)
    • デバイスタイプ(Device Type):"Computer"または"Mobile Device"
  • 推奨項目:
    • IT資産番号(Asset Tag)
    • 物理的な場所(EA Location)
    • デバイスの状態(EA Condition)

CSVファイルの例

以下の例では、デバイスの物理的な場所と状態を登録しています。

Serial Number,Device Type,EA Location,EA Condition,EA Asset Tag
DUMMY12345,Computer,Main Office,New,12345
DUMMY67890,Computer,Warehouse A,Used,67890

ステップ2: CSVファイルのアップロード

  1. Jamf Proにアクセス
    • Jamf Proの「Settings」→「Global Management」→「Inventory Preload」に進みます。
  2. CSVファイルをアップロード
    • 「Upload CSV」をクリックし、準備したCSVファイルをアップロードします。

ステップ3: データの確認と適用

  1. アップロード内容の確認
    • CSVの内容が正しく反映されているかJamf Proの管理画面で確認します。
  2. デバイス情報の適用
    • デバイスがインベントリ収集時に自動的に情報を反映します。

ステップ4: 情報の更新

在庫情報を更新する場合、新しいCSVファイルを作成して再アップロードします。Jamf Pro上で直接編集するのではなく、常にCSVを基に更新を行うことで、ミスを防ぎ管理の一貫性を保てます。


カスタム属性の活用

Jamf Proのカスタム属性(Extension Attributes)を使用することで、組織固有の情報を管理に追加できます。以下のような項目を拡張可能です:

  • IT資産番号(Fixed Asset Tag)
  • 利用用途(Usage)
  • 保管エリア(Location)

CSVファイルのカスタム属性例:

Serial Number,Device Type,EA Location,EA Condition,EA Fixed Asset Tag
DUMMY12345,Computer,Main Office,New,FA12345
DUMMY67890,Computer,Warehouse A,Used,FA67890

注意点

  • 在庫データの更新フローを構築することで、常に最新の情報を反映可能にします。

例えば、GitHub Actionsを使って自動化する場合、以下のようなスクリプトを用意します:

"""
このスクリプトはJamfのインベントリプリロードを自動的に更新します。
OAuthトークンを取得し、既存のインベントリプリロードレコードを削除して、
在庫データを含む新しいCSVファイルをアップロードします。
"""

import logging
import os
import sys

import requests
from dotenv import load_dotenv

logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)

if os.path.exists(".env"):
    load_dotenv()


def validate_env_vars(env_vars):
    """
    必要な環境変数を検証します。
    """
    missing_vars = [var for var in env_vars if not os.getenv(var)]
    if missing_vars:
        logging.error(
            "必要な環境変数が不足しています: %s", ", ".join(missing_vars)
        )
        sys.exit(1)


class JamfRepository:
    """
    Jamf APIと対話するためのリポジトリクラス。
    """

    def __init__(self, base_url):
        self.base_url = base_url

    def get_oauth_token(self, client_id, client_secret):
        """
        Jamf APIからOAuthトークンを取得します。
        """
        token_url = f"{self.base_url}/api/oauth/token"
        token_data = {
            "client_id": client_id,
            "grant_type": "client_credentials",
            "client_secret": client_secret,
        }
        token_headers = {"Content-Type": "application/x-www-form-urlencoded"}

        try:
            response = requests.post(
                token_url, headers=token_headers, data=token_data, timeout=10
            )
            response.raise_for_status()
            logging.info("OAuthトークンを正常に取得しました。")
            return response.json().get("access_token")
        except requests.exceptions.HTTPError as http_err:
            logging.error(
                "HTTPエラーが発生しました: %s - レスポンス: %s", http_err, response.text
            )
            sys.exit(1)
        except requests.exceptions.RequestException as err:
            logging.error(
                "リクエストエラーが発生しました: %s - レスポンス: %s",
                err,
                err.response.text if err.response else "レスポンスなし",
            )
            sys.exit(1)

    def delete_all_inventory_records(self, access_token):
        """
        すべてのインベントリプリロードレコードを削除します。
        """
        delete_url = f"{self.base_url}/api/v2/inventory-preload/records/delete-all"
        delete_headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        }

        try:
            response = requests.post(delete_url, headers=delete_headers, timeout=10)
            response.raise_for_status()
            logging.info("すべてのインベントリプリロードレコードを正常に削除しました。")
        except requests.exceptions.HTTPError as http_err:
            logging.error(
                "HTTPエラーが発生しました: %s - レスポンス: %s", http_err, response.text
            )
            sys.exit(1)
        except requests.exceptions.RequestException as err:
            logging.error(
                "リクエストエラーが発生しました: %s - レスポンス: %s",
                err,
                err.response.text if err.response else "レスポンスなし",
            )
            sys.exit(1)

    def upload_inventory_csv(self, access_token, csv_file_path):
        """
        インベントリプリロード用のCSVファイルをアップロードします。
        """
        upload_url = f"{self.base_url}/api/v2/inventory-preload/csv"
        upload_headers = {"Authorization": f"Bearer {access_token}"}

        try:
            with open(csv_file_path, "rb") as f:
                files = {"file": f}
                response = requests.post(
                    upload_url, headers=upload_headers, files=files, timeout=10
                )
                response.raise_for_status()
                logging.info("インベントリプリロード用CSVを正常にアップロードしました。")
        except requests.exceptions.HTTPError as http_err:
            logging.error(
                "HTTPエラーが発生しました: %s - レスポンス: %s", http_err, response.text
            )
            sys.exit(1)
        except requests.exceptions.RequestException as err:
            logging.error(
                "リクエストエラーが発生しました: %s - レスポンス: %s",
                err,
                err.response.text if err.response else "レスポンスなし",
            )
            sys.exit(1)


def main():
    """
    スクリプトを実行するメイン関数。
    """
    required_env_vars = ["JAMF_CLIENT_ID", "JAMF_CLIENT_SECRET", "CSV_FILE_PATH", "JAMF_BASE_URL"]
    validate_env_vars(required_env_vars)

    client_id = os.getenv("JAMF_CLIENT_ID")
    client_secret = os.getenv("JAMF_CLIENT_SECRET")
    csv_file_path = os.getenv("CSV_FILE_PATH")
    base_url = os.getenv("JAMF_BASE_URL")

    repository = JamfRepository(base_url=base_url)

    access_token = repository.get_oauth_token(client_id, client_secret)
    repository.delete_all_inventory_records(access_token)
    repository.upload_inventory_csv(access_token, csv_file_path)

    logging.info("Jamfのインベントリプリロードを正常に更新しました。")


if __name__ == "__main__":
    main()

このスクリプトを使って、自動化を実現します。

また、GitHub Actionsのワークフロー例は以下の通りです:

name: Jamf Inventory Update

on:
  push:
    branches:
      - main
    paths:
      - '**/*.csv'

jobs:
  update-inventory:
    runs-on: ubuntu-latest

    steps:
      - name: リポジトリをチェックアウト
        uses: actions/checkout@v4

      - name: Pythonをセットアップ
        uses: actions/setup-python@v5
        with:
          python-version: '3.9'

      - name: 依存関係をインストール
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Jamf Inventory Updateを実行
        run: python scripts/jamf_inventory_update.py
        env:
          JAMF_CLIENT_ID: ${{ secrets.JAMF_CLIENT_ID }}
          JAMF_CLIENT_SECRET: ${{ secrets.JAMF_CLIENT_SECRET }}
          CSV_FILE_PATH: ${{ github.workspace }}/inventory.csv
          JAMF_BASE_URL: ${{ secrets.JAMF_BASE_URL }}

追加情報

詳細な使い方はJamf Pro公式ドキュメントをご参照ください:


まとめ

Jamf Proのインベントリプリロード機能を活用することで、ゼロタッチデプロイメント前の在庫Macの管理が効率化されます。物理的な場所や状態を一括登録・更新することで、管理の手間を大幅に削減し、情報の一元化が実現できます。これを機に、よりスマートな在庫管理に取り組んでみてはいかがでしょうか?

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?