Pour rappel, Ansible est un outil de déploiement qui permet à des playbooks d’automatiser les déploiements d’applications et d’infrastructure. L’avantage clé d’Ansible est sa flexibilité puisqu’il permet de modifier les applications avec souplesse et de les appréhender en tant que service. Toutefois, Ansible possède également quelques faiblesses. Avec une conception volontairement simple, il fonctionne uniquement comme une application de paramètres sans tenir compte de la disponibilité de l’information et des problèmes de sécurité, qui doivent alors être gérés par d’autres systèmes. C’est pourquoi un grand nombre de développeurs préfèrent utiliser Ansible en complémentarité avec un système d’agent actif comme Puppet ou avec un outil de gestion centralisé comme Ansible Tower.
Ansible se concentre sur la configuration au niveau de l’application, les scripts d’approvisionnement pouvant être exécutés sur tout outil de support d’infrastructure (PaaS, conteneurs, bare-metal, Vagrant, etc.). Ansible ne nécessite qu’une connexion SSH et un compte sudo sur le système distant.
Les scripts d’approvisionnement Ansible sont écrits dans un style déclaratif à l’aide de fichiers YAML regroupés en rôles. Les instructions atomiques des rôles sont déclarées à l’aide d’un certain nombre de modules de base fournis par Ansible. Consultez la documentation Ansible pour une introduction en profondeur.
L’un des modèles DevOps pour gérer les mises à jour de configuration consiste à fournir un nouvel environnement à partir de zéro et à éliminer complètement l’ancien (pensez à des images de conteneur). Cela implique une gestion fiable de votre cycle de vie des données. Dans notre cas particulier du Nexus Repository Manager, cela consiste à plusieurs gigaoctets d’artefacts téléchargés / mandatés, de certains journaux d’audit et d’objets OrientDB contenant la configuration. Par conséquent, en fonction des contraintes de l’environnement, il peut être logique de mettre à jour la configuration d’une instance Nexus préalablement approvisionnée. La nature déclarative des instructions de base d’Ansible est répond bien à cette contrainte, mais toute logique personnalisée écrite dans un rôle devrait être idempotente et prendre en compte la logique « créer ou éventuellement mettre à jour ».
Il faut également noter que certaines parties de la configuration Nexus ne peuvent pas être mises à jour. Voici quelques exemples :
Les étapes de base de l’installation sont très simples et peuvent toutes être écrites en utilisant des modules simples de base Ansible :
(Ces étapes sont dans les tâches / nexus_install.yml)
Et puis vient la surprise : la configuration Nexus n’est pas disponible dans un fichier texte simple qui peut être édité à l’aide d’instructions simples Ansible. Elle est stockée dans une base de données OrientDB intégrée qui ne doit pas être modifiée directement. La manière documentée de configurer Nexus est soit par son interface utilisateur Web, soit par son API d’intégration.
La façon dont l’API d’intégration fonctionne est la suivante :
Le module uri d’Ansible envoie des requêtes HTTP, fournissant une automatisation de ces étapes.
La première étape consiste à téléverser le script Groovy sur Nexus. Notez que le script peut déjà être présent. Par conséquent, lors des ré-exécutions du playbook, nous essayons de le supprimer avant d’entreprendre d’autres actions, par le biais de tasks/declare_script_each.yml, comme suit :
--- - name: Removing (potential) previously declared Groovy script {{ item }} uri: url: "http://localhost:8081/service/siesta/rest/v1/script/{{ item }}" user: 'admin' password: "{{ current_nexus_admin_password }}" method: DELETE force_basic_auth: yes status_code: 204,404 - name: Declaring Groovy script {{ item }} uri: url: "http://localhost:8081/service/siesta/rest/v1/script" user: 'admin' password: "{{ current_nexus_admin_password }}" body_format: json method: POST force_basic_auth: yes status_code: 204 body: name: "{{ item }}" type: 'groovy' content: "{{ lookup('template', 'groovy/' + item + '.groovy') }}"
Les requêtes HTTP sont exécutées à partir de l’hôte cible, c’est pourquoi localhost est utilisé ici. force_basic_auth: yes s’assure que le client HTTP n’attende pas une réponse 401 avant de fournir des informations d’identification. Autrement, Nexus répondra immédiatement avec une réponse 403 lorsque aucune authentification n’est passée. status_code est l’état HTTP attendu de la réponse fournie par Nexus. Étant donné que le script Groovy peut ne pas nécessairement exister à ce moment-là, nous devons également accepter le code d’état 404.
L’étape suivante consiste à appeler le script Groovy créé via l’appel HTTP précédent. La plupart des scripts prendront certains paramètres d’entrée (par exemple, créer un utilisateur ), et c’est là que Ansible et Groovy aideront. Tous deux issus de l’âge des principes REST, ils peuvent parler et comprendre le format JSON de manière relativement transparente.
Du côté du script Groovy :
import groovy.json.JsonSlurper parsed_args = new JsonSlurper().parseText(args) security.setAnonymousAccess(Boolean.valueOf(parsed_args.anonymous_access))
Et pour appeler ce script à partir des arguments passés via Ansible :
- include: call_script.yml vars: script_name: setup_anonymous_access args: # this structure will be parsed by the groovy JsonSlurper above anonymous_access: true
Avec call_script.yml :
--- - name: Calling Groovy script {{ script_name }} uri: url: "http://localhost:8081/service/siesta/rest/v1/script/{{ script_name }}/run" user: 'admin' password: "{{ current_nexus_admin_password }}" headers: Content-Type: "text/plain" method: POST status_code: 200,204 force_basic_auth: yes body: "{{ args | to_json }}"
Cela nous permet de passer de manière transparente les paramètres structurés de Ansible aux scripts Groovy, en gardant la structure, les tableaux et les types de base des objets.
Voici quelques conseils qui peuvent aider les développeurs travaillant sur les scripts Groovy.
Comme décrit dans la documentation Nexus, avoir les scripts Nexus dans le classpath de votre IDE peut vraiment vous aider à travailler. Si vous automatisez la configuration de Nexus autant que possible, vous allez inévitablement tomber sur certaines API internes sans documentation. De plus, certaines parties de l’API n’ont aucune source disponible (par exemple, l’API LDAP). Dans de tels cas, un décompilateur peut être utile.
Puisque notre rôle sur Github utilise Maven avec toutes les dépendances nécessaires, vous pouvez simplement l’ouvrir avec IntelliJ et éditer les scripts situés dans « files/groovy ».
Comme documenté, il y a quatre points d’entrée implicites pour accéder aux entrailles de Nexus à partir de votre script:
Ceux-ci sont utiles pour des opérations simples, mais pour tout ce qui est plus compliqué, vous devrez résoudre plus en profondeur les services par:
Certaines parties de Nexus (7,4%, selon Github) sont également écrites à l’aide de Groovy, contenant de nombreux bons exemples de code: CoreApiImpl.groovy.
Créer des requêtes HTTP à partir de l’interface Web de configuration (requêtes AJAX) fournit également des indications sur les structures de données attendues, les paramètres ou les valeurs de certains paramètres.
Enfin, mettre en place un débogueur distant depuis votre IDE vers une instance Nexus peut aider, car il existe de nombreux endroits où une structure de données très générique est utilisée (comme Map <string, object= » »>) et seule l’inspection au moment de l’exécution peut rapidement</string,> indiquer les types réels nécessaires.
Voici quelques exemples commentés de scripts Groovy tirés du rôle Ansible
Les Capabilities de Nexus peuvent être configurées à l’aide d’une interface utilisateur unifiée. Dans notre cas, cela couvre:
Instructions :
import groovy.json.JsonSlurper import org.sonatype.nexus.capability.CapabilityReference import org.sonatype.nexus.capability.CapabilityType import org.sonatype.nexus.internal.capability.DefaultCapabilityReference import org.sonatype.nexus.internal.capability.DefaultCapabilityRegistry // unmarshall the parameters as JSON parsed_args = new JsonSlurper().parseText(args) // Type casts, JSON serialization insists on keeping those as 'boolean' parsed_args.capability_properties['headerEnabled'] = parsed_args.capability_properties['headerEnabled'].toString() parsed_args.capability_properties['footerEnabled'] = parsed_args.capability_properties['footerEnabled'].toString() // Resolve a @Singleton from the container context def capabilityRegistry = container.lookup(DefaultCapabilityRegistry.class.getName()) def capabilityType = CapabilityType.capabilityType(parsed_args.capability_typeId) // Try to find an existing capability to update it DefaultCapabilityReference existing = capabilityRegistry.all.find { CapabilityReference capabilityReference -&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; capabilityReference.context().descriptor().type() == capabilityType } // update if (existing) { log.info(parsed_args.typeId + ' capability updated to: {}', capabilityRegistry.update(existing.id(), existing.active, existing.notes(), parsed_args.capability_properties).toString() ) } else { // or create log.info(parsed_args.typeId + ' capability created as: {}', capabilityRegistry. add(capabilityType, true, 'configured through api', parsed_args.capability_properties).toString() ) }
import groovy.json.JsonSlurper import org.sonatype.nexus.repository.config.Configuration // unmarshall the parameters as JSON parsed_args = new JsonSlurper().parseText(args) // The two following data structures are good examples of things to look for via runtime inspection // either in client Ajax calls or breakpoints in a live server authentication = parsed_args.remote_username == null ? null : [ type: 'username', username: parsed_args.remote_username, password: parsed_args.remote_password ] configuration = new Configuration( repositoryName: parsed_args.name, recipeName: 'maven2-proxy', online: true, attributes: [ maven : [ versionPolicy: parsed_args.version_policy.toUpperCase(), layoutPolicy : parsed_args.layout_policy.toUpperCase() ], proxy : [ remoteUrl: parsed_args.remote_url, contentMaxAge: 1440.0, metadataMaxAge: 1440.0 ], httpclient: [ blocked: false, autoBlock: true, authentication: authentication, connection: [ useTrustStore: false ] ], storage: [ blobStoreName: parsed_args.blob_store, strictContentTypeValidation: Boolean.valueOf(parsed_args.strict_content_validation) ], negativeCache: [ enabled: true, timeToLive: 1440.0 ] ] ) // try to find an existing repository to update def existingRepository = repository.getRepositoryManager().get(parsed_args.name) if (existingRepository != null) { // repositories need to be stopped before any configuration change existingRepository.stop() // the blobStore part cannot be updated, so we keep the existing value configuration.attributes['storage']['blobStoreName'] = existingRepository.configuration.attributes['storage']['blobStoreName'] existingRepository.update(configuration) // re-enable the repo existingRepository.start() } else { repository.getRepositoryManager().create(configuration) }
import groovy.json.JsonSlurper import org.sonatype.nexus.security.user.UserManager import org.sonatype.nexus.security.role.NoSuchRoleException // unmarshall the parameters as JSON parsed_args = new JsonSlurper().parseText(args) // some indirect way to retrieve the service we need authManager = security.getSecuritySystem().getAuthorizationManager(UserManager.DEFAULT_SOURCE) // Try to locate an existing role to update def existingRole = null try { existingRole = authManager.getRole(parsed_args.id) } catch (NoSuchRoleException ignored) { // could not find role } // Collection-type cast in groovy, here from String[] to Set&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;String&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; privileges = (parsed_args.privileges == null ? new HashSet() : parsed_args.privileges.toSet()) roles = (parsed_args.roles == null ? new HashSet() : parsed_args.roles.toSet()) if (existingRole != null) { existingRole.setName(parsed_args.name) existingRole.setDescription(parsed_args.description) existingRole.setPrivileges(privileges) existingRole.setRoles(roles) authManager.updateRole(existingRole) } else { // Another collection-type cast, from Set&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;String&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; to List&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;String&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; security.addRole(parsed_args.id, parsed_args.name, parsed_args.description, privileges.toList(), roles.toList()) }
Le rôle est disponible sur Ansible Galaxy et Github. Il comporte:
La LDAPCon est une conférence internationale autour de la technologie LDAP et des enjeux de gestion des identités, d’authentification et d’habilitation. L’événement qui se déroulera cette année à Bruxelles du 19 au 20 octobre, se tient tous les deux ans dans un pays différent : 2007 à Cologne en Allemagne 2009 à Portland aux États-Unis […]
Nous sommes heureux de vous annoncer la sortie d’une vidéo promotionnelle produite par Microsoft eux-même ! Fruit d’une collaboration autour de la plateforme Azure, cette vidéo souligne la pertinence et l’essor des technologies open source dans l’environnement infonuagique Azure de Microsoft ainsi que notre capacité d’innovation en combinant technologies open source […]
L’authentification unique (en anglais Single Sign On ou SSO) est aujourd’hui bien implantée dans les systèmes d’information, grâce à une large offre de produits et surtout de nombreux standards comme CAS, SAML ou OpenID Connect, pour ne citer que les plus importants. Cependant, ce domaine reste difficile d’accès car chaque nouvelle norme demande un temps […]
La Ville de Villeurbanne mise sur l’Open Source et choisit LemonLDAP::NG pour contrôler les droits d’accès de ses utilisateurs. La Ville de Villeurbanne possédait plusieurs applications Web dont l’authentification était déjà déléguée à un serveur central CAS (Central Authentification Services), modifié pour les besoins de Villeurbanne pour donner accès à la fois aux utilisateurs internes […]
Les fêtes approchent et si votre activité dépend grandement d’Internet et de votre site Web, vous vous posez sans doute la question : « Mon site va-t-il tenir le coup ? ». Nombreux sont les commerçants qui misent beaucoup sur leur site e-commerce et cette période de fête est cruciale, voire vitale. Il ne suffit […]