Software vendors (ISV) periodically add more features to their cloud applications and expose new services, and therefore want to expand their list of products for sale. By following this document, you will continue the development process by adding another function to the APS application to assist the product configuration manager in adding more products for sale.
In this document:
Before continuing with the practical steps, ensure you have completed the development of the integration package using a simple sales model or a model with resellers and your APS application successfully passed the tests as described in the previous steps of this demo project.
The platform is able to identify cloud services by MPN (Manufacturer Product Number ) assigned by the software vendors. After an ISV exposes new services or service combinations with unique MPNs and notifies the service provider about it, the latter initiates a process of expanding the product line to provide the new ISV services. During this process, the platform requests the respective APS connector for certain data used to expand the product line.
ISVs use their own MPN formats. In this project, an MPN must have three fields separated by “/” (slash):
GitHub
- the prefix that identifies the ISV.
A list of GitHub scopes separated by “-” (dash). This field is the main identifier that defines the services available on GitHub.
A number - the suffix that allows the ISV to differentiate products which have the same list of scopes.
For the services considered in the previous steps, we can assign the following MPNs:
GitHub/repo-delete_repo/1001
- a license with the repo
and delete_repo
scopes.
GitHub/repo/1001
- a license with the repo
scope.
To expand the product line, you will use one more scope called gist
in combination with the previously used scopes.
You will use it to add the following MPNs mapped to licenses:
GitHub/gist/1001
- a license for using the gist
scope.
GitHub/repo-gist/1001
- a license for using the repo
and gist
scopes.
GitHub/repo-delete_repo-gist/1001
- a license for using the repo
, delete_repo
, and gist
scopes.
To initiate the product line expansion process, the provider must have a configuration file in the
CSV format that contains the initial data about the new
services. There are
certain requirements
for file format and content. For this project, use the sample file
(download
) with the following content:
A/C/D/U,APS Type,Service Template,MPN,MSRP Recurring Fee,VAR Recurring Fee
ADD,http://aps-standard.org/samples/github/license/2.0,GitHub Integration Demo,GitHub/gist/2001,2.5,2.0
ADD,http://aps-standard.org/samples/github/license/2.0,GitHub Integration Demo,GitHub/repo-gist/2001,4.5,3.5
ADD,http://aps-standard.org/samples/github/license/2.0,GitHub Integration Demo,GitHub/repo-delete_repo-gist/2001,5.5,4.5
The above file is used for two actions in the platform:
To request the APS application for the new product configuration.
To assign prices to the newly created products.
The internal relationship in the APS resource model remains the same as used in the previous basic model or model with resellers. To support the required function, the following items must be changed in APS types:
The license
APS type must declare one more property called mpn
to store an MPN corresponding to the MPN on
the ISV side.
The application root APS type app
must implement the
InitWizardVendorCatalogConfig APS type and, particularly, the
fetchCatalog operation. The platform will call
this operation for the configuration of the new products to sell new licenses.
The steps in this section walk you through the package updating process.
Note
This will be a major update of the APS application. That is why you need to change the version of the APS
application in the APP-Meta.xml
file and the versions of all APS types in the respective *.php
and *.schema
files from 1.0
to 2.0
. Along with that, update the APS links (provisioning logic) and the requests in
JavaScript (presentation logic) to match the new versions.
The APS package must contain the mpn-settings.json
file in its root folder. This file declares an APS property that
the platform must use as the MPN for the products sold through the respective APS application. In this project,
the license
APS type will have the mpn
property to be used as the application MPN
.
For this purpose, create the following content in the file:
{
"http://aps-standard.org/samples/github/license/2.0": {
"mpn": "MPN"
}
}
Note
The “MPN” string must correspond to the “MPN” column header in the Product Configuration File.
As specified in Resource Model and License, the main property of a license is a list
of scopes. Each combination of scopes must correspond to a certain MPN as stated in the MPN section.
In accordance with MPN Mapping, add the following property definition in the scripts/licenses.php
file:
## The property mapped to the MPN (Manufacturer Product Number)
## For each combination of scopes, there must be a unique MPN
/**
* @type(string)
* @title("ManufacturerProductNumber")
* @required
* @description("Manufacturer Product Number unique for this type of service")
*/
public $mpn;
The updated script must look similar to the sample scripts/licenses.php
(download)
file:
<?php
define('APS_DEVELOPMENT_MODE', true);
require "aps/2/runtime.php";
/**
* @type("http://aps-standard.org/samples/github/license/2.0")
* @implements("http://aps-standard.org/types/core/profile/service/1.0")
*/
class license extends \APS\ResourceBase
{
/**
* @link("http://aps-standard.org/samples/github/app/2.0")
* @required
*/
public $app;
/**
* @link("http://aps-standard.org/samples/github/tenant/2.0[]")
*/
public $tenants;
## The license name to represent a product SKU
/**
* @type(string)
* @title("LicenseName")
* @required
* @description("Human readable license name")
*/
public $name;
## Just a human readable description.
/**
* @type(string)
* @title("LicenseDescription")
* @description("Human readable license description")
*/
public $description;
## The only property that allows creating various App reference resources - App licenses.
/**
* @type(string[])
* @title("Scopes")
* @required
* @description("Authorized scopes of resources on the App side for a token generated for a customer")
*/
public $scopes = array("repo");
## The property mapped to the MPN (Manufacturer Product Number)
## For each combination of scopes, there must be a unique MPN
/**
* @type(string)
* @title("ManufacturerProductNumber")
* @required
* @description("Manufacturer Product Number unique for this type of service")
*/
public $mpn;
}
?>
The platform will request the APS application to return product configuration containing arrays with the following elements that the product configuration manager will use to create new products:
apsResources
- a list of service profiles implemented as reference APS resources
resourceTypes
- a list of resource types to be added to a service template
servicePlans
- a list of new service plans
To avoid having the structures of the above elements in the program code, add those structures to an auxiliary file -
scripts/fetch_catalog_template.json
(download
):
{
"apsResource": {
"apsType":"http://aps-standard.org/samples/github/license/2.0",
"type": "http://aps-standard.org/types/core/profile/service/1.0",
"id": "idc2922c137c7a80",
"relations": {
"app": "idglobals"
}
},
"resourceType": {
"name": "GitHub Integration Demo, scopes: %s",
"id": -510002,
"resClass": "rc.saas.service.link",
"required": false
},
"servicePlan": {
"name": "GitHub Integration Demo with scopes: %s",
"id": -20,
"shortDescription": "GitHub Integration, scopes: %s",
"longDescription": "Testing the integration with the GitHub API, scopes: %s",
"planBillingPeriod": 1,
"renewOrderInterval": 0,
"subscrPeriodType": 2,
"subscrPeriod": 1
}
}
The structures in the above template correspond to the structures declared in the InitWizardVendorCatalogConfig APS type. The absent properties will be added by the program code.
In the app
APS type used to create APS application instances, add the required fetchCatalog
operation by
following this process:
In the scripts/app.php
file, declare the implementation of the
InitWizardVendorCatalogConfig APS type:
/**
* @type("http://aps-standard.org/samples/github/app/2.0")
* @implements("http://aps-standard.org/types/core/application/1.0","http://odin.com/init-wizard/config/1.0","http://odin.com/init-wizard-vendor-catalog/config/1.0")
*/
class app extends \APS\ResourceBase
{
# ... Class definition
}
Inside the app
class definition, declare the fetchCatalog
operation:
/**
* @verb(POST)
* @path("/fetchCatalog")
* @param("http://odin.com/init-wizard-vendor-catalog/config/1.0#FetchCatalogRequest",body)
* @access(admin, true)
* @access(owner, true)
* @access(referrer, true)
*/
public function fetchCatalog($request)
{
# The function definition will be added here later.
}
As specified in the InitWizardVendorCatalogConfig APS type and declared above, the input parameter in the fetchCatalog operation must be the FetchCatalogRequest structure.
The following steps will define this function.
In the function definition field, add the following preparation steps:
Validate the request. It must refer to the license
APS type where the mpn
property is declared.
Read the response template from the auxiliary fetch_catalog_template.json
file.
Initialize three arrays to return: apsResources
, resourceTypes
, and servicePlans
.
if($request->apsType != "http://aps-standard.org/samples/github/license/2.0" ||
count($request->offerMpns) == 0)
throw new \Exception("Incorrect APS type, the 'http://aps-standard.org/samples/github/license/2.0' is expected");
$jsondata = file_get_contents("./fetch_catalog_template.json");
$tmpl = json_decode($jsondata);
$apsResources = [];
$resourceTypes = [];
$servicePlans = [];
Recursively process the MPNs in the input parameter to generate an apsResource
, resourceType
,
and servicePlan
for each MPN:
foreach ($request->offerMpns as $mpn) {
$reposArr = explode('-', explode('/', $mpn->mpn)[1]);
$reposStr = implode(', ', $reposArr);
$apsResource = clone $tmpl->apsResource;
$resourceType = clone $tmpl->resourceType;
$servicePlan = clone $tmpl->servicePlan;
# Set an apsResource:
$apsResource->id = 'idc'.(string)rand(1000000000000, 9999999999999);
$apsResource->fields = new stdClass();
$apsResource->fields->profileName = sprintf("Access with scopes: %s", $reposStr);
$apsResource->fields->name = sprintf("Scopes: %s", $reposStr);
$apsResource->fields->scopes = [];
foreach ($reposArr as $repo) {
array_push($apsResource->fields->scopes, $repo);
}
$apsResource->fields->mpn = $mpn->mpn;
# Set a resourceType:
$resourceType->name = sprintf($resourceType->name, $reposStr);
$resourceType->id = rand(-599999, -500000);
$resourceType->actParams = new stdClass();
$resourceType->actParams->resource_uid = $apsResource->id;
# Set a servicePlan:
$servicePlan->name = sprintf($servicePlan->name, $reposStr);
$servicePlan->id = rand(-999, -100);
$servicePlan->shortDescription = sprintf($servicePlan->shortDescription, $reposStr);
$servicePlan->longDescription = sprintf($servicePlan->longDescription, $reposStr);
$servicePlan->resources = [(object)[
'id' => rand(-799999, -700000),
'rtID' => $resourceType->id,
'incl' => 0,
'min' => 1,
'max' => -1
]];
# Push the new objects to the response arrays:
array_push($apsResources, clone $apsResource);
array_push($resourceTypes, $resourceType);
array_push($servicePlans, $servicePlan);
}
Group the required arrays in a JSON object and return the latter:
$response = new stdClass();
$response->apsResources = $apsResources;
$response->resourceTypes = $resourceTypes;
$response->servicePlans = $servicePlans;
return $response;
The updated script must look similar to the sample app.php(download)
file:
<?php
define('APS_DEVELOPMENT_MODE', true);
require "aps/2/runtime.php";
/**
* @type("http://aps-standard.org/samples/github/app/2.0")
* @implements("http://aps-standard.org/types/core/application/1.0","http://odin.com/init-wizard/config/1.0","http://odin.com/init-wizard-vendor-catalog/config/1.0")
*/
class app extends \APS\ResourceBase
{
/**
* @link("http://aps-standard.org/samples/github/tenant/2.0[]")
*/
public $tenants;
/**
* @link("http://aps-standard.org/samples/github/license/2.0[]")
*/
public $licenses;
/**
* @link("http://aps-standard.org/samples/github/reseller/2.0[]")
*/
public $resellers;
/**
* @type(string)
* @title("BaseURL")
* @required
* @description("Base endpoint in the external system. Note: The final slash is required.")
*/
public $baseURL = "https://api.github.com/";
/**
* @verb(GET)
* @path("/getInitWizardConfig")
* @access(admin, true)
* @access(owner, true)
* @access(referrer, true)
*/
public function getInitWizardConfig()
{
$myfile = fopen("./wizard_data.json", "r") or die("Unable to open file!");
$data = fread($myfile,filesize("./wizard_data.json"));
fclose($myfile);
return json_decode($data);
}
/**
* @verb(POST)
* @path("/fetchCatalog")
* @param("http://odin.com/init-wizard-vendor-catalog/config/1.0#FetchCatalogRequest",body)
* @access(admin, true)
* @access(owner, true)
* @access(referrer, true)
*/
public function fetchCatalog($request)
{
$logger = \APS\LoggerRegistry::get(); // Used for troubleshooting
$logger->info("\nFETCH-CATALOG: Entered the function with request: ");
if($request->apsType != "http://aps-standard.org/samples/github/license/2.0" ||
count($request->offerMpns) == 0)
throw new \Exception("Incorrect APS type, the 'http://aps-standard.org/samples/github/license/2.0' is expected");
$jsondata = file_get_contents("./fetch_catalog_template.json");
$tmpl = json_decode($jsondata);
$apsResources = [];
$resourceTypes = [];
$servicePlans = [];
foreach ($request->offerMpns as $mpn) {
$reposArr = explode('-', explode('/', $mpn->mpn)[1]);
$reposStr = implode(', ', $reposArr);
$apsResource = clone $tmpl->apsResource;
$resourceType = clone $tmpl->resourceType;
$servicePlan = clone $tmpl->servicePlan;
# Setting a apsResource:
$apsResource->id = 'idc'.(string)rand(1000000000000, 9999999999999);
$apsResource->fields = new stdClass();
$apsResource->fields->profileName = sprintf("Access with scopes: %s", $reposStr);
$apsResource->fields->name = sprintf("Scopes: %s", $reposStr);
$apsResource->fields->scopes = [];
foreach ($reposArr as $repo) {
array_push($apsResource->fields->scopes, $repo);
}
$apsResource->fields->mpn = $mpn->mpn;
# Setting a resourceType:
$resourceType->name = sprintf($resourceType->name, $reposStr);
$resourceType->id = rand(-599999, -500000);
$resourceType->actParams = new stdClass();
$resourceType->actParams->resource_uid = $apsResource->id;
# Setting a servicePlan:
$servicePlan->name = sprintf($servicePlan->name, $reposStr);
$servicePlan->id = rand(-999, -100);
$servicePlan->shortDescription = sprintf($servicePlan->shortDescription, $reposStr);
$servicePlan->longDescription = sprintf($servicePlan->longDescription, $reposStr);
$servicePlan->resources = [(object)[
'id' => rand(-799999, -700000),
'rtID' => $resourceType->id,
'incl' => 0,
'min' => 1,
'max' => -1
]];
# Push the generated objects to the response arrays:
array_push($apsResources, clone $apsResource);
array_push($resourceTypes, $resourceType);
array_push($servicePlans, $servicePlan);
}
# Response:
$response = new stdClass();
$response->apsResources = $apsResources;
$response->resourceTypes = $resourceTypes;
$response->servicePlans = $servicePlans;
return $response;
}
/**
* @verb(GET)
* @path("/testConnection")
* @param(object,body)
* @access(admin, true)
* @access(owner, true)
* @access(referrer, true)
*/
public function testConnection($body)
{
return "";
}
}
?>
The initial product deployment and provisioning are described in the Deployment and Provisioning documents of this project.
Perform the following operations in the provider control panel (PCP) to test your updated APS application.
In the updated APS application, the property mpn
of the license
APS type must map to the MPN
field
of the product configuration file. To verify this mapping, perform the following steps:
In the OSS PCP, navigate to Services > Applications and open the updated APS application.
On the Service Profiles tab, click MPN Settings.
Ensure that the service profile property mpn
maps to the CSV column name MPN
:
The above configuration reflects the content of the ./mpn-settings.json
file.
Use the prepared product configuration file to request the APS application for the product configuration that enables the provider to sell the services whose MPNs are in that file. The process looks as follows:
In the OSS PCP, navigate to Services > Applications and open the updated APS application.
Click Configure Product, select the Extract vendor’s configuration option, and click Choose File to choose the product configuration file:
Click Next. On the next step, you will find a list of service profiles that represent all licenses to sell (those that exist in the platform and the new ones):
Click Next until you reach the Service Plans step. For each new service plan proposed by the application, assign a proper category. For this purpose, click on the service plan name, select a plan category for it, and click Add:
Click Next to accept the service plan configuration and then, on the final step, click Finish.
After this step, new service plans for selling new licenses provided by the integrated cloud application are added to the platform.
If you want to assign prices for the new products, the recommended way is to import a price configuration file as described in Updating Prices.
For testing purposes, use the same file that you used during the product configuration in the previous section and follow these steps:
Ensure there is a custom attribute to set a country code to be used by the product configurator (InitWizard) when
setting prices for sales vendors. For this purpose, in the BSS PCP, navigate to System > Settings and create
an attribute whose ID and name are InitWizardCountryCode
:
Every sales vendor must have this attribute configured. For this purpose, open the account settings and set the proper value for this custom attribute:
Ensure the name suffix of the CSV file to be used for importing prices is the same as the attribute you installed in the previous step, for example:
$ cp github_config_mpn.csv github_config_mpn_us.csv
Use that CSV file to import prices:
Use the updated product configuration to subscribe customers to the application services.
In this project stage, you integrated your APS application closer with the platform product configuration manager. Now it can assist the provider in expanding the product line when the original cloud application exposes new services.
Your final integration package must look similar to
the GitHub_Integration_Demo
package.