Ce guide explique comment écrire un plugin New Ticket Orchestrator qui permet aux utilisateurs de créer ou de mettre à jour des tâches ou des alertes New sur les plateformes. En suivant ce guide, le développeur devrait être en mesure d'écrire un plugin New de manière autonome sans aucun problème technique.
Conditions préalables
- Expérience en programmation Python 3.x (niveau intermédiaire).
- Accès à la plateforme Netskope Cloud Exchange.
- Accès à l'API ou au SDK Python du produit ou de la solution pour lequel vous devez écrire le plugin.
- Un compte avec une autorisation minimale pour le produit.
Module Ticket Orchestrator
La plateforme Cloud Exchange, et son module Ticket Orchestrator, est dotée d'un ensemble riche de caractéristiques et de fonctionnalités qui permettent un haut degré de personnalisation. Nous vous recommandons donc de vous familiariser avec les différents aspects de la plateforme énumérés ci-dessous.
Remarque : ce module permet de partager les données de Netskope avec des outils tiers.
Concepts de Netskope & Terminologie :
- Le moteur principal : Le moteur central de Cloud Exchange gère les plugins tiers et leurs méthodes de cycle de vie. Il dispose de points d'extrémité API permettant d'interagir avec la plateforme pour effectuer diverses tâches.
- Module : Les zones de code fonctionnel qui invoquent des plugins spécifiques aux modules pour accomplir différents flux de travail. Ticket Orchestrator est l'un des modules fonctionnant dans Cloud Exchange.
- Plugin : Les plugins sont des paquets Python dont la logique permet de créer ou de mettre à jour des tâches ou des alertes.
- Configurations de plugin : Les configurations de plugin sont les objets de la classe de plugin qui sont configurés avec les paramètres requis et sont planifiés par le moteur central de Cloud Exchange pour créer ou mettre à jour des tâches ou des alertes.
- Tâches : Il s'agit des tâches qui doivent être créées ou mises à jour dans des plateformes telles que Jira ou ServiceNow. Une classe de tâches contient une classe d'alertes.
- Alertes : Il s'agit de notifications envoyées à la plateforme.
Lignes directrices pour le développement
- Utilisez la structure du répertoire du paquet pour tout le code Python.
- Assurez-vous que toutes les bibliothèques tierces fournies avec le plugin ont été vérifiées pour détecter les vulnérabilités connues.
- Veillez à respecter les conventions de code Python standard (https://peps.python.org/pep-0008/).
- Exécutez et vérifiez que la vérification de Flake8 Lint passe avec la vérification de docstring activée. La longueur maximale d'une ligne doit être de 80.
- Pour les scripts/intégrations écrits en Python, veillez à créer des tests unitaires. Veuillez vous référer à la section ci-dessous " Tests unitaires".
- L'architecture du plugin permet de stocker des états ; toutefois, évitez de stocker des objets volumineux pour la gestion des états.
- Vérifiez que votre code python ne présente pas de vulnérabilités.
- L'icône du plugin doit être inférieure à 10kb. Veillez à utiliser le logo de l'entreprise (et non celui du produit) sur un fond transparent. La taille recommandée pour le logo est de 300×50 ou un rapport d'aspect similaire.
- Utilisez le point de contrôle fourni par le noyau de Cloud Exchange plutôt que d'en mettre un en place par vous-même.
- Suivez les directives relatives à la structure du répertoire des plugins.
- Si la description contient un lien, il doit s'agir d'un lien hypertexte et non d'un texte en clair.
- Convertissez les valeurs de l'horodatage au format lisible par l'homme (de l'époque à l'objet DateTime).
- Utilisez une validation appropriée pour les paramètres transmis à la méthode validate et fournissez le texte d'aide approprié pour tous les paramètres.
- Utilisez un objet notificateur pour déclencher une notification en cas d'échec ou de situation critique (comme la limitation du débit ou le dépassement de la taille de la charge utile) afin d'informer l'utilisateur de l'état du plugin.
- Veillez à mettre en place un mécanisme de journalisation approprié avec l'objet logger transmis par la plateforme Cloud Exchange. Assurez-vous que la journalisation est suffisante pour aider l'équipe d'exploitation à résoudre les problèmes. Assurez-vous que les données sensibles ne sont pas enregistrées ou divulguées dans la notification.
- Assurez-vous que le nom du répertoire du plugin (comme sample_plugin) correspond à celui du fichier manifest.json. Champ ID.
- Veillez à ajouter un mécanisme de relance pour le code de statut 429.
- La pagination doit toujours être prise en compte lors du développement d'une fonctionnalité dans un plugin.
- User Agent should be added to the headers while making any API call. Format for the User Agent is netskope-ce-<ce_version>–<module>–<plugin_name>–<plugin_version>. Refer to the URE Microsoft Azure AD or URE Crowdstrike Identity Protect plugin for more details. Note that the plugin version should be dynamically fetched, and to fetch the netskope-ce-<version> string, use the method defined by core.
- Les champs Tokens API et Password ne doivent pas utiliser strip().
- The log messages should start with <module> <app name> Plugin <configuration_name>. Example: Ticket Orchestrator ServiceNow Plugin <ServiceNow Configuration Name>: <log_message>“. This is a suggestion; you can avoid the configuration name: (logger.info(“<module> <plugin_name> Plugin: <message>”))
- Lors de l'enregistrement d'un journal d'erreurs, vous devez, si possible, ajouter une trace de l'exception. USE : self.logger.error(error, details=traceback.format_exc())
- The Toast message should not contain the <app_name> <module> Plugin: in the message.
- Assurez-vous d'attraper les exceptions et les codes d'état appropriés pendant et après les appels à l'API. Si possible, vous pouvez créer une méthode d'aide à partir de laquelle les demandes seront effectuées, et cette méthode peut être appelée avec les paramètres appropriés lorsque cela est nécessaire.
- Le fichier CHANGELOG.md doit être mis à jour avec les balises appropriées, telles que Added, Changed et Fixed, ainsi qu'un message convivial approprié. Assurez-vous que le nom du fichier correspond exactement à CHANGELOG.md.
- Utilisez un objet notificateur pour déclencher une notification en cas d'échec ou de situation critique (comme la limitation du débit ou le dépassement de la taille de la charge utile) afin d'informer l'utilisateur de l'état du plugin.
- Fournissez le texte d'aide approprié (infobulle) pour tous les paramètres. Si possible, veillez à expliquer la signification du paramètre dans l'infobulle.
- Veillez à donner un nom et une description significatifs aux paramètres de configuration du plugin.
- Veillez à fournir un type de configuration approprié (texte, nombre, mot de passe, choix, multi-choix) aux paramètres.
- Assurez-vous d'utiliser la configuration du proxy et le drapeau de validation du certificat SSL qui est transmis par la plateforme Cloud Exchange lors de toute requête sortante (API/SDK).
- Veillez à collecter la valeur d'un paramètre non obligatoire à l'aide de la fonction .get() et fournir une valeur par défaut lorsque vous utilisez la méthode .get() méthode.
- Veillez à ajouter un mécanisme de relance pour le code de statut 429.
- Si la description du plugin contient un lien, il doit s'agir d'un lien hypertexte redirigeant vers la page de documentation. Pour plus d'informations, reportez-vous au plugin Threat Exchange Trend Micro Vision One.
- Les messages du logger et les messages Toast ne doivent pas contenir les valeurs des champs de type API Token et Password.
- La pagination doit toujours être prise en compte lors du développement d'une fonctionnalité dans un plugin.
- All the logger statement should follow below format: logger.info(“<module> <plugin_name> Plugin: <message>”).
Écrire un plugin
Cette section illustre le processus d'écriture d'un plugin à partir de zéro.
Téléchargez le plugin d’exemple depuis le dépôt public NetskopeOSS sur GitHub ou la base de connaissances Cloud Exchange disponible ici : https://support.netskope.com/hc/en-us/articles/360052128734-Cloud-Threat-Exchange.
Développement de l'installation
Python
Notre système utilise Python3 (v3.7 et plus). Veillez à installer python3 dans votre environnement de développement. Pytest est utilisé pour exécuter des tests unitaires.
Bibliothèques Python incluses
Les bibliothèques Python suivantes sont incluses dans la plateforme Netskope Cloud Exchange.
| Nom de la bibliothèque | Version |
|---|---|
| aiofiles | 22.1.0 |
| amqp | 5.1.1 |
| anyio | 3.6.2 |
| asgiref | 3.6.0 |
| attrs | 22.2.0 |
| azure-core | 1.26.2 |
| azure-storage-blob | 12.14.1 |
| bcrypt | 4.0.1 |
| boto3 | 1.26.51 |
| botocore | 1.29.51 |
| billiard | 3.6.4.0 |
| celery | 5.2.7 |
| cabby | 0.1.23 |
| cachetools | 5.2.1 |
| celerybeat-mongo | 0.2.0 |
| certifi | 2022.12.7 |
| cffi | 1.15.1 |
| chardet | 5.1.0 |
| charset-normalizer | 3.0.1 |
| click | 8.1.3 |
| click-didyoumean | 0.3.0 |
| click-plugins | 1.1.1 |
| click-repl | 0.2.0 |
| colorama | 0.4.6 |
| colorlog | 6.7.0 |
| cryptography | 39.0.0 |
| cybox | 2.1.0.21 |
| defusedxml | 0.7.1 |
| dnspython | 2.3.0 |
| docker | 6.0.1 |
| fastapi | 0.89.1 |
| furl | 2.1.3 |
| google-api-core | 2.11.0 |
| google-auth | 2.16.0 |
| google-cloud-core | 2.3.2 |
| google-cloud-pubsub | 2.13.12 |
| google-cloud-pubsublite | 1.6.0 |
| google-cloud-storage | 2.7.0 |
| google-crc32c | 1.5.0 |
| google-resumable-media | 2.4.0 |
| googleapis-common-protos | 1.58.0 |
| grpc-google-iam-v1 | 0.12.6 |
| grpcio | 1.51.1 |
| grpcio-status | 1.51.1 |
| gunicorn | 20.1.0 |
| h11 | 0.14.0 |
| idna | 3.4 |
| importlib-metadata | 6.0.0 |
| isodate | 0.6.1 |
| jmespath | 1.0.1 |
| jsonpath | 0.82 |
| jsonschema | 4.17.3 |
| kombu | 5.2.4 |
| libcst | 0.3.21 |
| libtaxii | 1.1.119 |
| lxml | 4.9.2 |
| mongoengine | 0.25.0 |
| more-itertools | 9.0.0 |
| MarkupSafe | 2.1.2 |
| memory-profiler | 0.61.0 |
| mixbox | 1.0.5 |
| mongoquery | 1.4.2 |
| msrest | 0.7.1 |
| multidict | 6.0.4 |
| mypy-extensions | 0.4.3 |
| netskopesdk | 0.0.25 |
| numpy | 1.23.5 |
| oauthlib | 3.2.2 |
| onelogin | 3.1.0 |
| ordered-set | 4.1.0 |
| orderedmultidict | 1.0.1 |
| overrides | 6.5.0 |
| pandas | 1.5.0 |
| packaging | 23.0 |
| passlib | 1.7.4 |
| pycparser | 2.21 |
| prompt-toolkit | 3.0.36 |
| proto-plus | 1.22.2 |
| protobuf | 4.21.12 |
| psutil | 5.9.4 |
| pydantic | 1.10.4 |
| pyasn1 | 0.4.8 |
| pyasn1-modules | 0.2.8 |
| PyJWT | 2.6.0 |
| pymongo | 4.3.3 |
| pyparsing | 3.0.9 |
| python-dateutil | 2.8.2 |
| pyrsistent | 0.19.3 |
| python-multipart | 0.0.5 |
| python3-saml | 1.15.0 |
| pytz | 2022.7.1 |
| PyYAML | 6.0 |
| requests | 2.28.2 |
| requests-oauthlib | 1.3.1 |
| rsa | 4.9 |
| six | 1.16.0 |
| starlette | 0.22.0 |
| sniffio | 1.3.0 |
| s3transfer | 0.6.0 |
| stix | 1.2.0.11 |
| taxii2-client | 2.3.0 |
| typing-inspect | 0.8.0 |
| typing-utils | 0.1.0 |
| typing_extensions | 4.4.0 |
| urllib3 | 1.26.14 |
| uvicorn | 0.20.0 |
| vine | 5.0.0 |
| wcwidth | 0.2.6 |
| weakrefmethod | 1.0.3 |
| websocket-client | 1.4.2 |
| Werkzeug | 2.2.2 |
| xmlsec | 1.3.11 |
| zipp | 3.11.0 |
| requests-mock | 1.7.0 |
Inclure des bibliothèques de plugins personnalisés
Netskope conseille de regrouper toutes les bibliothèques python tierces dont votre plugin aura besoin dans le paquetage du plugin lui-même. Utilisez le programme d'installation pip pour réaliser ce regroupement ; il fournit un commutateur qui prend un répertoire en entrée. Si le répertoire est fourni, pip installera les paquets dans ce répertoire.
Par exemple, cette commande installera le paquet "cowsay" dans le répertoire "lib".
> pip install cowsay --target ./lib
Pour la documentation officielle à ce sujet, consultez https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-t.
Lorsque vous importez des modules à partir du dossier lib ci-dessus, vous devez utiliser une importation relative plutôt qu'une importation absolue.
IDE
Les IDE recommandés sont PyCharm ou Visual Studio Code.
Structure du répertoire des plugins
Cette section présente la structure typique des répertoires du plugin Ticket Orchestrator.
/sample_plugin/ ├── /utils/ ├── constants.py ├── __init__.py ├── Changelog.md ├── icon.png ├── main.py └── manifest.json
- __init__.py : Chaque plugin est considéré comme un module python par le code de Ticket Orchestrator. Assurez-vous que chaque paquet de plugins contient le fichier vide "__init__.py" fichier.
- CHANGELOG.md : Ce fichier contient les détails de la mise à jour du plugin et doit être mis à jour avec les balises appropriées telles que Added, Changed, Fixed (Ajouté, Modifié, Corrigé) ainsi qu'un message convivial approprié.
- icon.png : Logo de l'icône du plugin, qui sera visible dans le chiclet du plugin et dans les cartes de configuration de l'interface utilisateur. Le logo doit avoir un fond transparent et une taille recommandée de 300*50 pixels ou un rapport d'aspect similaire.
- main.py : Ce fichier python contient la classe Plugin contenant l'implémentation concrète des méthodes create_tasks, update_tasks, sync_states, validate_steps, get_available_fields, get_default_mappings, et get_queues. get_fields peut également être mis en œuvre en fonction des besoins.
- manifest.json : Fichier manifeste pour le paquet de plugins, contenant des informations sur tous les paramètres configurables et leurs types de données. Ce fichier contient également plus d'informations sur l'intégration du plugin.
- utils/ : Ce répertoire est utilisé pour écrire des fonctions utilitaires et définir des constantes. il contient différents fichiers en fonction des exigences du plugin. Les fichiers d'exemple sont présentés ici.
- constants.py : Ce fichier contient les constantes qui peuvent être utilisées à l'échelle du plugin, comme les mappages de champs, etc.
Les fichiers listés ici sont obligatoires pour toute intégration de plugin, mais vous pouvez ajouter d'autres fichiers en fonction des exigences spécifiques de l'intégration.
Note
Assurez-vous que le nom du répertoire du plugin (par ex. sample_plugin) correspond à l'élément Champ ID.
CHANGELOG.md
Il s'agit d'un fichier qui contient des détails sur les mises à jour du plugin et qui doit être mis à jour avec les balises appropriées telles que Added, Changed, et Fixed ainsi qu'un message convivial approprié.
- Ajouté : utilisez-le lorsque les fonctionnalités de New sont ajoutées
- Corrigé : utilisez-le lorsqu'un bogue/une erreur est corrigé(e).
- Changed : utilisez-le lorsqu'il y a un changement dans l'implémentation existante du plugin.
Exemple de CHANGELOG.md
# 1.0.1 ## Fixed - Fixed pagination while fetching all available fields. # 1.0.0 ## Added - Initial release.
Manifest.json
Il s'agit d'un fichier JSON qui stocke les méta-informations relatives au plugin, qui sont ensuite lues par le module Ticket Orchestrator pour rendre le plugin dans l'interface utilisateur et permettre au module Ticket Orchestrator d'en savoir plus sur le plugin, y compris les paramètres de configuration requis, l'identifiant du plugin, le nom du plugin, etc.
- Chaque plugin doit contenir ce fichier avec les informations nécessaires pour que Ticket Orchestrator puisse instancier l'objet Plugin correctement.
- Les paramètres courants du fichier manifest.json sont les suivants
- name : (string) Nom du plugin. (Obligatoire)
- id : (string) ID du paquet de plugins. Assurez-vous qu'il est unique pour tous les plugins installés dans le Cloud Exchange. L'ID doit correspondre au nom du répertoire du paquet de plugins. (Obligatoire)
- version : (string) Version du plugin. Utilisation d'un MAJOR.MINOR.PATCH (ex. 1.0.1) est encouragé, bien qu'il n'y ait pas de restrictions. (Obligatoire)
- description : (string) Description du plugin. Fournissez une description détaillée qui mentionne les capacités et les instructions d'utilisation du plugin, (ex. Créer des numéros/tickets sur la plateforme). Cette description apparaît sur la carte de configuration du plugin. (obligatoire). Si la description contient un lien, il doit s'agir d'un lien hypertexte et non d'un texte en clair.
- pulling_supported : (booléen) Indiquer vrai s'il est possible de récupérer des alertes. Par exemple, Netskope dispose de cette fonction.
- receiving_supported : (booléen) Mettez true si vous voulez créer des tickets ou envoyer des notifications.
- configuration : (array) Array of JSON objects that contains information about all the parameters required by the plugin - their name, type, id, etc. Les paramètres communs des objets JSON imbriqués sont expliqués ci-dessous.
- label : Nom du paramètre. Ceci sera affiché sur la page de configuration du plugin. (Obligatoire)
- key : Clé unique du paramètre, qui sera utilisée comme clé dans l'objet dict python où la configuration du plugin est utilisée. (Obligatoire)
- type : Type de valeur du paramètre. Les valeurs autorisées sont "texte", "mot de passe", "nombre", "choix" et "multichoix". (Obligatoire) Pour plus de détails, reportez-vous aux types de paramètres de la configuration du plugin ci-dessous.
- default : La valeur par défaut de ce paramètre. Cette valeur apparaîtra sur la page de configuration du plugin dans l'interface utilisateur de Ticket Orchestrator. Les types de données pris en charge sont "texte", "nombre" et "liste" (pour les types à choix multiples). (Obligatoire)
- obligatoire : Booléen, qui indique si ce paramètre est obligatoire ou non. Si un paramètre est obligatoire, l'interface utilisateur de Ticket Orchestrator ne vous permettra pas de lui attribuer une valeur vide. Les valeurs autorisées sont `true` et `false`. (Obligatoire)
- description : Description du paramètre au niveau du texte d'aide, qui peut donner plus de détails sur le paramètre et la valeur attendue. Cette chaîne apparaîtra sur la page de configuration du plugin en tant que texte d'aide. (Obligatoire)
- des choix : Une liste d'objets JSON contenant des clés et des valeurs sous forme de clés JSON. Ce paramètre n'est pris en charge que par le "type" : "choix et multichoix".
Plugin Configuration Parameter types
Assurez-vous que tous les paramètres de configuration du plugin sont listés dans la section de configuration de manifest.json pour le plugin.
Password Parameter
Utilisez ce paramètre pour stocker les secrets/mots de passe pour l'authentification avec les points d'extrémité de l'API. Les paramètres dont le mot de passe est un type auront une zone de texte de mot de passe dans la page de configuration du plugin et seront obscurcis et cryptés par la plateforme.
Exemple de JSON :
"configuration": [
{
"label": "API Token",
"key": "api_token",
"type": "password"
},
]
Vue de la configuration du plugin :

Text Parameter
Utilisez ce paramètre pour stocker des informations sous forme de chaîne, telles que base-url, nom d'utilisateur, etc. Ce paramètre aura une entrée de texte normale sur la page de configuration du plugin.
Exemple de JSON :
"configuration": [
{
"label": "Tenant Name",
"key": "tenant_name",
"type": "text"
},
]
Vue de la configuration du plugin :

Number Parameter
Utilisez ce paramètre pour stocker des valeurs numériques ou flottantes. Ce paramètre aura un champ de saisie numérique sur la page de configuration du plugin. (De CTE)
Exemple de JSON :
"configuration": [
{
"label": "Maximum File hash list size in MB.",
"key": "max_size",
"type": "number"
},
]
Vue de la configuration du plugin :

Choice Parameter
Utilisez ce paramètre pour stocker les valeurs des paramètres d'énumération. Ce paramètre aura une liste déroulante sur la page de configuration du plugin. (De Log Shipper)
Exemple de JSON :
{
"label": "Syslog Protocol",
"key": "syslog_protocol",
"type": "choice",
"choices": [
{
"key": "TLS",
"value": "TLS"
},
{
"key": "UDP",
"value": "UDP"
},
{
"key": "TCP",
"value": "TCP"
}
],
"default": "UDP",
"mandatory": true,
"description": "Protocol to be used while ingesting data."
},
Vue de configuration des plugins :

Après avoir sélectionné l’entrée :

Multichoice Parameter
Utilisez ce paramètre pour stocker des valeurs à choix multiples. Ce paramètre aura une liste déroulante sur la page de configuration du plugin avec la possibilité de sélectionner plusieurs valeurs. (De Ticket Orchestrator)
Exemple de JSON :
"configuration": [
{
"label": "Severity",
"key": "severity",
"type": "multichoice",
"choices": [
{
"key": "Unknown",
"value": "unknown"
},
{
"key": "Low",
"value": "low"
},
{
"key": "Medium",
"value": "medium"
},
{
"key": "High",
"value": "high"
},
{
"key": "Critical",
"value": "critical"
}
],
"default": [
"critical",
"high",
"medium",
"low",
"unknown"
],
"mandatory": false,
"description": "Only indicators with matching severity will be saved."
}
]
Vue de la configuration du plugin :

Toggle Parameter
Ce paramètre stocke une valeur booléenne : « activé » renvoie « Vrai », « désactivé » renvoie « Faux ».
Utiliser le proxy système (« proxy ») : Use system proxy configured in Settings.(Default: False)
Vue de configuration du plugin :
1. Lorsque le commutateur est désactivé

2. Lorsque le commutateur est activé.

Note
Ce paramètre est fourni par Core, et il n'est pas autorisé à être ajouté à partir du fichier manifest.json du plugin.
main.py
Ce fichier python contient l'implémentation de base du plugin.
Importations standard
rom netskope.integrations.itsm.plugin_base import (
PluginBase,
ValidationResult,
MappingField,
)
from netskope.integrations.itsm.models import (
FieldMapping,
Queue,
Task,
TaskStatus,
Alert,
)
Variables de PluginBase
PluginBase donne accès à des variables qui peuvent être utilisées au cours du cycle de vie du plugin Méthodes. Vous trouverez ci-dessous la liste des variables.
| Nom de la variable | Usage | Description |
|---|---|---|
| self.logger | self.logger.error("Message") self.logger.warn("Message") self.logger.info("Message") | Poignée du logger fournie par le noyau. Utilisez cet objet pour enregistrer les événements importants. Les journaux seraient visibles dans les journaux d'audit de CT. Reportez-vous à la documentation de l'objet Logger. |
| self.configuration | self.configuration.get(<nom-clé-attribut>)<attribute-key-name> | Représentation JSON de l'objet de configuration de l'instance de plugin. Utilisez-le pour accéder aux attributs de configuration tels que les informations d'authentification, les détails du serveur, etc. Utilisez le nom de la clé de l'attribut mentionné dans manifest.json. |
| self.last_run_at | Si self.last_run_at : self.last_run_at.timestamp() Utilisez ce format pour convertir la dernière durée d'exécution en format d'époque. | Fournit l'horodatage de la dernière exécution réussie de la méthode d'extraction du plugin. Le noyau de Cloud Exchange maintient l'heure du point de contrôle après chaque exécution réussie de pull(). Pour la première exécution, la valeur sera None. Le type de données de l'objet est datetime. |
| self.storage | Cloud Exchange fournit au plugin un mécanisme pour maintenir l'état. Utilisez cet objet pour conserver l'état requis lors des appels ultérieurs. Le type de données de cet objet est python dict. | |
| self.notifier | self.notifier.info(“message”) self.notifier.warn(“message”) self.notifier.error(“message”) | Cet objet fournit l'identifiant du notificateur du noyau Cloud Exchange. Utilisez cet objet pour envoyer une notification à la plateforme. Les notifications seront visibles dans l'interface utilisateur de Ticket Orchestrator. Veillez à ce que le message contienne des informations résumées pour que l'utilisateur puisse les lire et prendre les mesures nécessaires. Par exemple, le notificateur utilisé dans le plugin Netskope si la méthode push() dépasse la limite de 8MB du produit. |
| self.proxy | requests.get(url=url, proxies=self.proxy) | Gestion des paramètres proxy du système s'ils sont configurés, sinon {}. |
| self.ssl_validation | requests.get(url=url, verify=self.ssl_validation) | Valeur booléenne qui indique si la validation ssl est appliquée pour les appels à l'API REST. |
Classe de plugin
- La classe Plugin doit être héritée de la classe PluginBase. La classe PluginBase est définie dans Netskope.integrations.cto.plugin_base.
- Assurez-vous que la classe Plugin fournit une implémentation pour les méthodes create_tasks, update_tasks, sync_states, validate_steps, get_available_fields, get_default_mappings et get_queues.
- La pagination doit toujours être prise en compte lors du développement d'une fonctionnalité dans un plugin.
- La classe du plugin contiendra tous les paramètres nécessaires pour établir la connexion et l'authentification avec l'API tierce.
- Les constantes telles que PLUGIN_NAME, LIMIT, etc. doivent être déclarées.
"""Sample plugin implementation.
This is a sample implementation of base PluginBase class, which explains the concrete implementation of the base class.
"""
from netskope.integrations.itsm.plugin_base import (
PluginBase,
ValidationResult,
MappingField,
)
from netskope.integrations.itsm.models import (
FieldMapping,
Queue,
Task,
TaskStatus,
Alert,
)
PLUGIN_NAME = "<module> <plugin_name> Plugin"
class Plugin(PluginBase):
"""SamplePlugin class having concrete implementation for creating and updating tasks or alerts.
This class is responsible for implementation of the create_tasks, update_tasks, sync_states, validate_steps, get_available_fields, get_default_mappings, and get_queues methods with proper return types.
Hence it's lifecycle execution can be scheduled by the CTO core engine.
"""
def create_tasks
- Cette méthode met en œuvre la logique permettant de créer des tickets ou d'envoyer des notifications dans la plateforme cible à l'aide des points de terminaison de l'API.
- Lancez les journaux appropriés si une exception se produit ou si le code d'état n'est pas satisfaisant.
- Veillez à renvoyer un objet Tâche avec les détails nécessaires.
- Vérifiez la structure du corps et le schéma de chaque champ et envoyez le corps en conséquence dans l'appel API.
- Traitez toutes les exceptions avec la connexion et le code de réponse HTTP et levez les exceptions avec les messages d'erreur ainsi que les journaux en cas d'erreurs.
- L’Agent utilisateur doit être ajouté aux en-têtes lors de tout appel API. Format de l'agent utilisateur : Netskope-ce-<ce_version>-<module>-<plugin_name>-<plugin_version>.
- Dans cette méthode, la variable "proxy" de l'option "Use System Proxy" doit être utilisée lors d'un appel à l'API.
- Assurez-vous de traiter le code de statut 429 - Trop de demandes, approche suggérée
- Mise en place d'un mécanisme de réessai en cas de code de statut 429 - Trop de demandes
"""Create an issue/ticket on platform."""
def create_task(self, alert: Alert, mappings: Dict, queue: Queue) -> Task:
"""Create an issue/ticket on platform."""
params = self.configuration["auth"]
project_id, issue_type = queue.value.split(":")
# Filter out the mapped attributes based on given project and issue_type
create_meta = self._get_createmeta()
mappings = self._filter_mappings(create_meta, mappings)
body = self._generate_body(create_meta, mappings)
... # Set fields with nested structure
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
}
response = requests.post(
f"{params['url'].strip('/')}/rest/api/3/issue",
json=body,
auth=HTTPBasicAuth(params["email"], params["api_token"]),
headers=add_user_agent(headers),
proxies=self.proxy,
)
if response.status_code == 201:
result = response.json()
# Need result for to create link in Task()
return Task(
id,
status,
link,
)
def update_tasks
- Cette méthode met en œuvre la logique de mise à jour des tâches dans la plateforme cible à l'aide des points d'extrémité de l'API.
- Elle est similaire à create_tasks, sauf que vous modifiez une tâche existante.
- Lancez les journaux appropriés si une exception se produit ou si le code d'état n'est pas satisfaisant.
- Veillez à renvoyer un objet Task contenant les informations nécessaires.
def update_task(
self, task: Task, alert: Alert, mappings: Dict, queue: Queue
) -> Task:
"""Add a comment in existing issue."""
params = self.configuration["auth"]
comment = {
"body": self._get_atlassian_document(
f"New alert received at {str(alert.timestamp)}."
)
}
response = requests.post(
f"{params['url'].strip('/')}/rest/api/3/issue/{task.id}/comment",
headers=add_user_agent(),
json=comment,
auth=HTTPBasicAuth(params["email"], params["api_token"]),
proxies=self.proxy,
)
if response.status_code == 201:
return task
elif response.status_code == 404:
self.logger.info(
f"{PLUGIN_NAME}: Issue with ID {task.id} no longer exists on platform"
f" or the configured user does not have permission to add"
f"comment(s)."
)
return task
else:
raise requests.HTTPError(
f"{PLUGIN_NAME}: Could not add comment. The existing issue on the platform with ID {task.id}."
)
def sync_states
- Si le statut du ticket est modifié dans la plateforme cible, nous récupérons le dernier statut et mettons à jour le statut actuel dans Netskope Cloud Exchange.
- Si le statut du ticket correspond à notre modèle TaskStatus, alors le statut sera reflété dans un ticket particulier, sinon le statut sera Other.
- Veillez à gérer le cas où la taille maximale de la charge utile prise en charge par le point de terminaison de l'API est dépassée. Il peut y avoir plusieurs façons de traiter ce cas.
- Si le point de terminaison de l'API prend en charge plusieurs demandes avec une taille de charge utile fixe, envoyez les données par morceaux.
- Assurez-vous de traiter le code de statut 429 - Trop de demandes, approche suggérée
- Mise en place d'un mécanisme de réessai en cas de code de statut 429 - Trop de demandes
def sync_states(self, tasks: List[Task]) -> List[Task]:
"""Sync all task states."""
params = self.configuration["auth"]
task_ids = [task.id for task in tasks]
task_statuses = {}
body = {
"jql": f"key IN ({','.join(task_ids)})",
"maxResults": 100,
"fields": ["status"], # We only need status of tickets
"startAt": 0,
"validateQuery": "none",
}
while True:
response = requests.post(
f"{params['url'].strip('/')}/rest/api/3/search",
headers=add_user_agent(),
json=body,
auth=HTTPBasicAuth(params["email"], params["api_token"]),
proxies=self.proxy,
)
response.raise_for_status()
if response.status_code == 200:
json_res = response.json()
body["startAt"] += json_res["maxResults"]
if len(json_res["issues"]) == 0:
break
for issue in json_res["issues"]:
task_statuses[issue.get("key")] = (
issue.get("fields", {}).get("status", {}).get("name")
).lower()
for task in tasks:
if task_statuses.get(task.id):
task.status = STATE_MAPPINGS.get(
task_statuses.get(task.id), TaskStatus.OTHER
)
else:
task.status = TaskStatus.DELETED
return tasks
def validate_steps
Il s'agit d'une méthode abstraite de la classe PluginBase.
- Cette méthode valide la configuration du plugin et les paramètres d'authentification transmis lors de la création d'une configuration de plugin.
- Cette méthode n'est appelée que lorsqu'une configuration New est créée ou mise à jour.
- Des validations séparées doivent être effectuées pour la validation des champs vides et la validation des contrôles de type dans la méthode de validation de la configuration du plugin.
- Valide que tous les paramètres obligatoires sont transmis avec le type de données approprié.
- Validez les paramètres d'authentification et le point de terminaison de l'API pour garantir le bon déroulement du cycle de vie du plugin.
- Lors de la validation, utilisez strip() pour les paramètres de configuration tels que l'URL de base, l'email, le nom d'utilisateur, etc. à l'exception des champs API Tokens et Password.
- Renvoyer l'objet ValidationResult (se référer au modèle de données ValidationResult) avec un drapeau de réussite indiquant le succès ou l'échec de la validation et le message de validation contenant la raison de l'échec de la validation.
def validate_step(
self, name: str, configuration: dict
) -> ValidationResult:
"""Validate a given configuration step."""
if name == "auth":
return self._validate_auth(configuration)
elif name == "params":
return self._validate_params(configuration)
else:
return ValidationResult(
success=True, message="Validation successful."
)
def get_available_fields
- Cette méthode doit renvoyer la liste des champs à afficher dans l'interface utilisateur de la file d'attente lorsque la configuration est sélectionnée dans la liste déroulante.
- Cette méthode ne doit renvoyer que les champs pris en charge.
- Veillez à utiliser l'API paginée afin que le plugin n'échoue pas dans le cas d'un grand nombre de champs.
def get_available_fields(self, configuration: dict) -> List[MappingField]:
"""Get list of all the available fields for issues/tickets."""
params = configuration["auth"]
response = requests.get(
f"{params['url'].strip('/')}/rest/api/3/field",
auth=HTTPBasicAuth(params["email"], params["api_token"]),
headers=add_user_agent(),
proxies=self.proxy,
)
response.raise_for_status()
if response.status_code == 200:
return list(
map(
lambda item: MappingField(
label=item.get("name"), value=item.get("key")
),
response.json(),
)
)
else:
raise requests.HTTPError(
f"{PLUGIN_NAME}: Could not fetch available fields from platform."
)
def get_default_mappings
Si l'utilisateur souhaite envoyer un champ par défaut à la plate-forme cible, nous devons implémenter cette méthode ; sinon, nous renvoyons une liste vide.
def get_default_mappings(self, configuration: dict) -> List[FieldMapping]:
"""Get default mappings."""
return [
FieldMapping(
extracted_field="custom_message",
destination_field="summary",
custom_message="Netskope $appCategory alert: $alertName",
),
FieldMapping(
extracted_field="custom_message",
destination_field="description",
custom_message=(
"Alert ID: $id\nApp: $app\nAlert Name: $alertName\n"
"Alert Type: $alertType\nApp Category: $appCategory\nUser: $user"
),
),
]
def get_queues
- Cela permettra de récupérer tous les projets de destination pour lesquels nous devons créer un ticket.
- Lever une exception appropriée si le ticket n'est pas créé, ainsi que les déclarations de journal nécessaires.
- Utilisez la pagination car il peut y avoir de nombreux projets.
def get_queues(self) -> List[Queue]:
"""Get list of projects as queues."""
params = self.configuration["auth"]
start_at, is_last = 0, False
projects = []
issue_types = self.configuration["params"]["issue_type"]
issue_types = list(map(lambda x: x.strip(), issue_types.split(",")))
total_ids = []
while not is_last:
response = requests.get(
f"{params['url'].strip('/')}/rest/api/3/project/search",
params={"startAt": start_at, "maxResults": 50},
headers=add_user_agent(),
auth=HTTPBasicAuth(params["email"], params["api_token"]),
proxies=self.proxy,
)
response.raise_for_status()
if response.status_code == 200:
json_res = response.json()
is_last = json_res["isLast"]
start_at += json_res["maxResults"]
# Create combination of projects and issue types
for project in json_res.get("values"):
total_ids.append(project.get("id"))
# batches of 650 Project ids if we pass more than that
# it will throw 500 server error
if is_last or (start_at % 650) == 0:
total_project_ids = ",".join(total_ids)
meta = self._get_createmeta(
self.configuration, {"projectIds": total_project_ids},
)
projects_list = meta.get("projects")
for project in projects_list:
if not project:
continue
for issue_type in project.get("issuetypes"):
# Issue type is defined as a "key:value" string
# Value of queue is defined as "project_id:issue_type" string
if issue_type.get("name") not in issue_types:
continue
projects.append(
Queue(
label=f"{project.get('name')} - {issue_type.get('name')}",
value=f"{project.get('id')}:{issue_type.get('name')}",
)
)
total_ids = [] # restart the batch ids
else:
raise requests.HTTPError(
f"{PLUGIN_NAME}: Could not fetch projects from platform."
)
return projects
Modèles de données
Cette section énumère les modèles de données et leurs propriétés.
Modèle de données de la file d'attente
- Une file d'attente est un projet dans la plate-forme
- Des tickets peuvent être créés dans cette file d'attente et des notifications peuvent y être envoyées.
| Nom | Type | Description |
|---|---|---|
| label | string | Le nom du projet qui sera affiché dans l'interface utilisateur. |
| value | string | Nous pouvons utiliser la valeur comme clé. |
| default_mapping | Dict | Elle peut être utilisée pour le mappage par défaut et le mappage par défaut de la déduplication. |
from netskope.integrations.itsm.models import Queue
project_id, issue_type = queue.value.split(":")
Modèle de données d'alerte
- L'alerte est une notification qui peut être envoyée à la plateforme tierce.
- Les alertes sont utilisées dans les tâches de création et de mise à jour.
| Nom | Type | Description |
|---|---|---|
| id | string | ID unique de l'objet Alert. |
| configuration | string | Représentation JSON pour accéder aux attributs de configuration, comme les identifiants d'authentification et le serveur. |
| alertName | string | Nom de l'alerte. |
| alertType | string | Type d'alerte. |
| l'appli | string | Nom de l'application. |
| appCategory | string | Catégorie de l'application. |
| user | string | Nom d'utilisateur. |
| type | string | Type d'enregistrement. |
| timeStamp | datetime | Heure exacte à laquelle un événement s'est produit. |
| rawAlert | dict | Champs supplémentaires pour les alertes. |
from netskope.integrations.itsm.models import Alert
comment = {
"body": self._get_atlassian_document(
f"New alert received at {str(alert.timestamp)}."
)
}
Modèle de données des tâches
- Une tâche est un ticket qui est créé ou mis à jour dans une plateforme.
- Une tâche possède un statut (taskStatus) qui détermine son état.
- Les tâches sont utilisées dans les méthodes createTask, updateTask et syncStates.
| Nom | Type | Description |
|---|---|---|
| id | string | ID unique de l'objet Tâche. |
| status | TaskStatus | Statut du ticket. Par exemple : "en cours". |
| dedupeCount | int | Nombre de billets en double. |
| link | str | Lien du billet. |
| deletedAt | datetime | Quand la tâche a été supprimée à. |
| configuration | str | Représentation JSON pour accéder aux attributs de configuration, comme les identifiants d'authentification et le serveur. |
| createdAt | datetime | Date de création de la tâche. |
| businessRule | str | Requête pour filtrer les données. |
| alert | Alerter | Notification. Mentionné ci-dessus (modèle de données d'alerte). |
from netskope.integrations.itsm.models import Task
return Task(
id=result.get("key"),
status=STATE_MAPPINGS.get(issue_status, TaskStatus.OTHER),
link=(
f"{self.configuration['auth']['url'].strip('/')}/browse/"
f"{result.get('key')}"
),
)
Modèle de données pour la cartographie des champs
Ce modèle est utilisé pour mettre en correspondance les champs des alertes avec les champs de la plateforme cible.
| Nom | Type | Description |
|---|---|---|
| extracted_field | string | Champs que nous voulons rendre ou custom_message |
| destination_field | string | Champ de la plate-forme cible |
| custom_message | string | Si vous souhaitez ajouter un message personnalisé. |
from netskope.integrations.itsm.models import FieldMapping
return [
FieldMapping(
extracted_field="custom_message",
destination_field="summary",
custom_message="Netskope $appCategory alert: $alertName",
)
Enregistrement
Ticket Orchestrator fournit une poignée d'un objet logger pour la journalisation.
- Évitez les instructions d'impression dans le code.
- Cet objet s'enregistre dans la base de données centrale de Cloud Exchange avec le champ horodatage. Les niveaux de journalisation pris en charge sont info, warn et error.
- Assurez-vous que le secret d'authentification de l'API ou toute autre information sensible n'est pas exposé dans les messages du journal.
- Veillez à mettre en place un mécanisme de journalisation approprié avec l'objet logger transmis par la plateforme Cloud Exchange.
- Assurez-vous que la journalisation est suffisante pour aider l'équipe d'exploitation à résoudre les problèmes.
- Assurez-vous que les données sensibles ne sont pas enregistrées ou divulguées dans la notification ou les journaux.
self.logger.error(
f"{PLUGIN_NAME}: Error log-message goes here."
)
self.logger.warn(
f"{PLUGIN_NAME}: Warning log-message goes here."
)
self.logger.info(
f"{PLUGIN_NAME}: Info log-message goes here."
)
Notifications
Ticket Orchestrator fournit un objet de notification qui peut être utilisé pour générer des notifications dans l'interface utilisateur de Ticket Orchestrator.
- Cet objet est transmis de la plateforme Cloud Exchange à l'objet Plugin. Chaque intégration de plugin peut utiliser cet objet chaque fois qu'il y a un cas d'échec qui doit être notifié à l'utilisateur immédiatement. L'horodatage de la notification est géré par la plateforme Cloud Exchange.
- Cet objet déclenche une notification dans l'interface utilisateur avec un code couleur correspondant à la gravité de l'échec. Les niveaux de notification pris en charge sont info, avertissement et erreur.
- Assurez-vous que le secret d'authentification de l'API ou toute autre information sensible n'est pas exposé dans les messages de notification.
- Utilisez un objet notificateur pour déclencher une notification en cas d'échec ou de situation critique (comme la limitation du débit ou le dépassement de la taille de la charge utile) afin d'informer l'utilisateur de l'état du plugin.
self.notifier.info(
f"{PLUGIN_NAME}: Info notification-message goes here."
)
self.notifier.error(
f"{PLUGIN_NAME}: Error notification-message goes here."
)
self.notifier.warn(
f"{PLUGIN_NAME}: Warning notification-message goes here."
)
Testing
Linting
Dans le cadre du processus de construction, nous exécutons quelques linters pour détecter les erreurs de programmation courantes, les erreurs stylistiques et les éventuels problèmes de sécurité.
Flake8
C’est un linter basique. Il peut être exécuté sans avoir toutes les dépendances disponibles et détectera les erreurs courantes. Nous utilisons également ce linter pour appliquer le style de formatage standard python pep8. Dans de rares occasions, vous pouvez devoir désactiver une erreur ou un avertissement provenant de ce linter. Faites-le en ajoutant un commentaire en ligne du type sur la ligne que vous souhaitez désactiver :
# noqa: <error-id>
Par exemple :
example = lambda: 'example' # noqa: E731
Lorsque vous ajoutez un commentaire en ligne, incluez toujours aussi le code d’erreur que vous désactivez. Ainsi, s’il y a d’autres erreurs sur la même ligne, elles seront signalées.
Plus d’infos : https://flake8.pycqa.org/en/latest/user/violations.html#in-line-ignoring-errors
Le contrôle docstring de type PEP8 est également activé avec le linter flake8. Assurez-vous donc que chaque fonction/module a un docstring approprié ajouté.
Tests unitaires
Assurer des tests unitaires pour tester de petites unités de code de manière isolée et déterministe. Veillez à ce que les tests unitaires évitent de communiquer avec des API externes et à ce qu'ils utilisent la technique du "mocking". Assurez-vous que les tests unitaires garantissent une couverture du code supérieure à 70%.
Configuration de l'environnement
Pour pouvoir utiliser les tests unitaires, le script d'intégration ou d'automatisation doit être développé dans une structure de répertoire de plugins. Nous utilisons PIP pour installer toutes les dépendances des modules Python nécessaires à l'exécution de l'installation. Avant de lancer les tests, assurez-vous d'installer toutes les dépendances requises mentionnées dans le fichier requirements.txt du dépôt central de Cloud Exchange.
Écrire vos tests unitaires
Assurez-vous que les tests unitaires sont écrits dans un fichier Python séparé nommé : <your_plugin_name>_test.py. Dans le fichier de test unitaire, chaque fonction de test unitaire doit être nommée : test_<your test case>. Plus d’informations sur la rédaction de tests unitaires et leur format sont disponibles sur PyTest Docs.
Mocking
Nous utilisons pytest-mock pour la simulation. pytest-mock est activé par défaut et installé dans l'environnement de base mentionné ci-dessus. Pour utiliser un objet mocker, il suffit de le passer en paramètre à votre fonction de test. Le simulateur peut alors être utilisé pour simuler à la fois l'objet de classe du plugin et les API externes.
Exemple:- du CLS
def test_push(mocker, common_config):
"""To test push method of CLSPlugin."""
cls_plugin = CLS_Plugin(
,
common_config,
None,
None,
logger,
source="",
mappings=,
)
# Initialize required classes
syslogger = logging.getLogger(
"SYSLOG_LOGGER_{}".format(threading.get_ident())
)
mocker.patch(
"netskope.plugins.Default.cls.main.CLSPlugin.init_handler",
return_value=syslogger,
)
try:
cls_plugin.push(data, data_type, subtype)
except Exception as e:
assert False, f"Push raised exception: {e}"
Exécuter vos tests unitaires
$ PYTHONPATH=. pytest
Déploiement de plugins sur Cloud Exchange
Paqueter le plugin
Cloud Exchange attend le plugin développé au format zip et tar.gz.
Exécutez la commande ci-dessous pour zipper le paquet :
zip -r sample_plugin.zip sample_plugin
Exécutez la commande ci-dessous pour générer le paquet tar.gz :
tar -zcvf sample_plugin.tar.gz sample_plugin
Télécharger le plugin
Pour déployer ce zip ou tar.gz sur la plateforme Cloud Exchange :
- Connectez-vous à la plateforme Cloud Exchange.
- Va sur Settings > Plugin.

- Cliquez Add New Plugin.

- Cliquez sur Browse.
- Select votre fichier zip ou tar.gz.

- Cliquez sur Upload.
Add a Repo
Pour déployer votre plugin sur la plateforme Cloud Exchange, vous pouvez ajouter votre repo dans la plateforme Cloud Exchange :
- Connectez-vous à la plateforme Cloud Exchange.
- Va sur Settings > Plugin Repository.

- Cliquez Configure New Repository.

- Saisissez un nom de référentiel, une URL de référentiel, un nom d'utilisateur et un jeton d'accès personnel.
- Cliquez sur Save.

- Allez sur Settings > Plugins.
- Select Repository name dans la liste déroulante Référentiel.

Deliverables
Plugin Guide
- Veillez à mettre à jour le guide du plugin à chaque version.
- Le guide du plugin doit contenir ce contenu :
- Compatibility.
- Notes de mise à jour.
- Description.
- Prerequisites.
- Permissions.
- Autorisation du produit.
- Configuration du plugin ITSM de Netskope.
- Configuration du plugin Ticket Orchestrator que nous avons développé.
- Configuration d'une règle de gestion.
- Configuration d'une file d'attente.
- Validation.
Vidéo de démonstration
Après avoir développé avec succès le plugin Ticket Orchestrator, créez une vidéo de démonstration et présentez le plugin de bout en bout sur le site workflow.

