Adobe Launch

Creating Adobe Launch Extension : Release your Extension

This article is going to be a complement on the one from Adobe public documentation.

On my part, I found the documentation quite restrictive on that part. It seems pretty obvious for the technical writer but it is not for me. Having extra explanation than the ones provided here are always welcome and for that this article come into play.

When you are finish with the development of your extension and it has been uploaded correctly in the launch server, you would need to call the Adobe IO API in order to release your extension.

The documentation provides a a nice example on how this can be done with a curl command :

curl -X PATCH \
  https://reactor.adobe.io/extension_packages/[PACKAGE-ID] \
  -H 'accept: application/vnd.api+json;revision=1' \
  -H 'content-type: application/vnd.api+json' \
  -H 'authorization: Bearer [TOKEN]' \
  -H 'cache-control: no-cache' \
  -H 'x-api-key: Activation-DTM' \
  -d \
'
{
    "data": {
        "id": "[PACKAGE-ID]",
        "type": "extension_packages",
        "meta": {
            "action": "release_private"
        }
    }
}
'

This implies that you have established a connection to Adobe IO before.
In case you didn’t do it, here are some tips or good to know things for that :

  1. Go to adobe.io
  2. Add a new API connection service
  3. Upload the .CRT required for JWT authentication (my article about JWT)
  4. Relax because you are on my blog and I won’t let you alone now

Once you have your information, you request the token directly in the console through the JWT tab. However we are on a JavaScript and Python blog so it would be a shame to not enjoy this opportunity to provide you with a python script to release your extension. 🙂

# -*- coding: utf-8 -*-
"""
Created on Sun Apr 28 14:18:30 2019

@author: piccini
"""

import time as _time
import json as _json
## Non standard libraries
import requests as _requests
import jwt as _jwt
from pathlib import Path

### Set up default values
_org_id, _api_key, _tech_id, _pathToKey, _secret = "","","","","",
_TokenEndpoint = "https://ims-na1.adobelogin.com/ims/exchange/jwt"
_orga_admin ={'_org_admin','_deployment_admin','_support_admin'}
_cwd = Path.as_posix(Path.cwd())
_date_limit = 0
_token = ''
_packageID = ''

def createConfigFile(verbose=False):
    """
    This function will create a 'config_admin.json' file where you can store your access data. 
    """
    json_data = {
        'org_id': '<orgID>',
        'api_key': "<APIkey>",
        'tech_id': "<something>@techacct.adobe.com",
        'secret': "<YourSecret>",
        'pathToKey': '<path/to/your/privatekey.key>',
        'packageID' : '<YourExtensionID>'
    }
    with open('config_admin.json', 'w') as cf:
        cf.write(_json.dumps(json_data, indent=4))
    if verbose:
        print(' file created at this location : '+_cwd + '/config_admin.json')


def importConfigFile(file):
    """
    This function will read the 'config_admin.json' to retrieve the information to be used by this module. 
    """
    global _org_id
    global _api_key
    global _tech_id
    global _pathToKey
    global _secret
    global _packageID
    global _endpoint
    with open(file, 'r') as file:
        f = _json.load(file)
        _org_id = f['org_id']
        _api_key = f['api_key']
        _tech_id = f['tech_id']
        _secret = f['secret']
        _pathToKey = f['pathToKey']
        _packageID = f['packageID']
        _endpoint = _endpoint + _packageID

#### Launch API Endpoint
_endpoint = 'https://reactor.adobe.io/extension_packages/' #+PackageID

    
def retrievingToken(verbose=False):
    """ Retrieve the token by using the information provided by the user during the import importConfigFile function. 
    
    Argument : 
        verbose : OPTIONAL : Default False. If set to True, print information.
    """
    with open(_pathToKey, 'r') as f:
        private_key_unencrypted = f.read()
        header_jwt = {'cache-control':'no-cache','content-type':'application/x-www-form-urlencoded'}
    jwtPayload = {
        "exp": round(24*60*60+ int(_time.time())),###Expiration set to 24 hours
        "iss": _org_id, ###org_id
        "sub": _tech_id,###technical_account_id
        "https://ims-na1.adobelogin.com/s/ent_reactor_admin_sdk":True,
        "aud": "https://ims-na1.adobelogin.com/c/"+_api_key
    }
    encoded_jwt = _jwt.encode(jwtPayload, private_key_unencrypted , algorithm='RS256')##working algorithm
    payload = {
            "client_id":_api_key,
            "client_secret":_secret,
            "jwt_token" : encoded_jwt.decode("utf-8")
            }
    response = _requests.post(_TokenEndpoint, headers=header_jwt, data=payload)
    json_response = response.json()
    token = json_response['access_token']
    expire = json_response['expires_in']
    global _date_limit ## getting the scope right
    _date_limit= _time.time()+ expire/1000 -500 ## end of time for the token
    with open('token.txt','w') as f: ##save the token
        f.write(token)
    if verbose == True:
        print('token valid till : ' + _time.ctime(_time.time()+ expire/1000))
        print('token has been saved here : ' + Path.as_posix(Path.cwd()))
    return token


def releaseExtension():
    """ It will release the extension privately. To be used only once """
    token = retrievingToken()
    header = {'accept':'application/vnd.api+json;revision=1',
              'content-type':'application/vnd.api+json',
              'Authorization':'Bearer '+token,
              'cache-control': 'no-cache',
              'x-api-key': 'Activation-DTM'
              }
    data = {
        "data": {
            "id": _packageID,
            "type": "extension_packages",
            "meta": {
                "action": "release_private"
            }
        }
    }
    res = _requests.patch(_endpoint,headers=header,json=data)
    return res 

createConfigFile() ## create your config file. 
importConfigFile('your_config_file.json') ## Think to do : actually import the config file once you have fill the required info. 
res = releaseExtension() ## save the result in a variable. 
print(res.text) ## just to show something 

The most important part of the token request is that part : “https://ims-na1.adobelogin.com/s/ent_reactor_admin_sdk”:True

This is what is going to link your token to the correct service. If you forget about it, it will not allow you access to the extension creation. So make sure you have this part.

The following part, once you have released your extension, I find it a bit cryptic.

“Once you have released your extension, you can no longer make changes to it and you cannot unrelease it. Once released, bug fixes and feature additions are accomplished by POSTing a new version of your extension package and following the above testing and release steps on that new version.”

You need to make sure that you want to stick with this extension before creating it as it is not possible to unpublish it.
I would imagine that this will be possible in the future.

With the improvement of the Launch API and eco system. It will probably be a necessity at some point in order to clean the extension catalogue.

However, you would need to go through the process of creating a new extension in order to update the one you have released.
Keeping the same name and increasing the versioning is required for the upgrade.

When you are uploading the extension in the production library after your release, you should see a message like this :

“An existing extension package with the name XXX was found on the server, but because its availability is not development, a development version of the extension package will be created.”

This will allow you to test the same way that you did before.

In order to publish it, you will have to publish the extension the same that we did but taking the new ID that has been generated during you last QA process by the @adobe/reactor-uploader method.

I hope this article helped you to understand how to publish and update your extensions and now : “Happy coding!”

On this series, you may want to check those articles :

ArticleContent
Introdcution to Adobe Launch & Extension Creation This posts covers the basic of what you need to know in order to start with this series of articles.
Architecture and dependencies within modules This posts covers the architectures and how the different modules are linked between each others
Global Extension Configuration This article explains how to set up your configuration HTML file. It is the core configuration module of your extension.
Event type module This article will show you how to build an event module. Every module will have a configuration part (HTML) and wrapper(JS).
Condition module
This article will show you how to build a condition module.
Action module This post will be related to the action module and how to build it. We will also see how you can actually import some cool feature of the launch library.
Data Element moduleThis article will be developing the possibilities of the Data Element module
Shared moduleThis article will focus on the shared functionality that you can write and share with other extension developpers to used. We will also used a feature shared from Adobe Analytics to show you how to use it.
Other featuresThe other features that are related to Launch Extension. I will cover additional (and important) information there.
Testing your extensionHow do I debug my Extension ?
Releasing and updating your ExtensionHow do I publish my Extension into Launch ? And after, how do you update it ?

1 Comment

  1. Awesome script !
    I tried several methods (Postman, command line etc…), but your Python script has finally been the working solution for me to release my extension.
    Many thanks for your help !
    Guillaume

Leave a Reply

Your email address will not be published. Required fields are marked *