Skip to content

Configuration Dataservice

1000TurquoisePogs edited this page Aug 3, 2020 · 16 revisions

The Configuration Dataservice is an essential component of the zLUX framework which acts as a JSON or binary resource storage service, and is accessible externally by REST API and internally to the server by Dataservices.

It allows for saving preferences of apps, management of defaults and privileges within a zLUX ecosystem, and bootstrapping configuration of the server's dataservices.

As the fundamental element of extensibility of the zLUX framework is a Plugin, the Configuration Dataservice works with data for Plugins. That is, every resource stored in the Configuration Service is stored for a particular Plugin, and valid resources to be accessed are determined by the definition of each Plugin in how it uses the Configuration Dataservice.

The behavior of the Configuration Dataservice is dependent upon the Resource structure for a zLUX Plugin. Each Plugin lists what resources are valid, and the administrators can set permissions on who can view or modify these resources.

  1. Resource Scope
  2. REST API
    1. REST Query Parameters
    2. REST HTTP Methods
      1. GET
      2. PUT
      3. DELETE
    3. Administrative Access & Group
  3. App API
  4. Internal & Bootstrapping Use
  5. Packaging defaults
  6. Plugin Definition
  7. Aggregation Policies
  8. Relevant Source Code

Resource Scope

Data is stored within the Configuration Dataservice according to the Scope chosen.

The intent of Scope within the Dataservice is to facilitate company-wide administration and privilege management of zLUX data.

When a user requests an resource, the resource is retrieved in one of two ways. If the resource is binary, the resource is returned as a result of the contents that are stored without processing. Otherwise, the resource is treated as JSON and is retrieved is either override or an aggregation of the broader scopes that encompass the scope they are viewing the data from.

When a user stores an resource, the resource is stored within a scope but only if the user has access privilege to update within that scope.

Scope is one of:

  • Plugin: Configuration defaults that come with the plugin. Cannot be modified.
  • Product: Configuration defaults that come with the Product. Cannot be modified.
  • Site: Data that can be used between multiple instances of the zLUX server.
  • Instance: Data within an individual zLUX server.
  • Group: Data shared between multiple users in a group (Pending).
  • User: Data for an individual user (Pending).

Note: While Authorization tuning can allow for settings such as GET from Instance to work without login, User and Group scope queries will be rejected if not logged in due to needing to pull resources from a specific user. Because of this, User and Group scopes will not be functional until the Security Framework is merged into mainline.

Where Plugin is the broadest and User is the narrowest.

When using scope user, the service will manage configuration for your particular username, using the authentication of the session. This way, the User scope is always mapped to your current username.

Consider a case where a user wants to access preferences for their text editor. One way they could do this is to use the REST API to retrieve the settings resource from the Instance scope.

The Instance scope may contain editor defaults that their administrator set. But, if there were no defaults in Instance, then the data in Group and finally User would be checked.

Therefore, the data the user receives would be no broader than what is stored in the Instance scope, but may have only been the settings they had saved within their own User scope if the broader scopes had no data for the resource.

Later, perhaps the user wants to save changes, and they try to save in the Instance scope. Most likely, this is rejected due to preferences by the administrator to disallow changes to the Instance scope by ordinary users.

REST API

When reaching the Configuration Service through a REST API, HTTP methods are used to perform the operation desired. The HTTP URL scheme for the configuration dataservice is:

<Server>/ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin ID>/<Scope>/<resource>/<optional subresources>?<query>

Where the resources are one or more levels deep, using as many layers of subresources as needed.

You can think of a resource as a collection of elements, or a directory. If you need to access just a single element, you must use the query parameter "name="

REST Query Parameters

  • Name (string): Used to get or put a single element rather than a collection.
  • Recursive (boolean): When performing a DELETE, specifies whether to delete subresources too.
  • Listing (boolean): When performing a GET against a resource with content subresources, listing=true will provide the names of the subresources rather than both the names and contents.

REST HTTP Methods

Below is an explanation of each type of REST call currently implemented. Each API call includes an example request and response against a hypothetical App: the "code editor".

GET

GET /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/<scope>/<resource>?name=<element>

  • This returns JSON with the attribute "content" being a JSON resource that is the entire configuration requested.

  • Example: /ZLUX/plugins/org.zowe.configjs/services/data/_current/org.openmainframe.zowe.codeeditor/user/sessions/default?name=tabs

  • The parts of this URL can be broken down as follows:

    • Plugin: org.openmainframe.zowe.codeeditor
    • Scope: user
    • Resource: sessions
    • Subresource: default
    • Element = tabs
  • Response body is a JSON config, if the resource is not binary:

	"_objectType" : "org.zowe.config.resource",
	"_metadataVersion" : "1.1",
	"resourceID" : "org.openmainframe.zowe.codeeditor/USER/sessions/default",
	"contents" : {
		"_metadataVersion" : "1.1",
		"_objectType" : "org.openmainframe.zowe.codeeditor.sessions.tabs",
		"tabs" : [{
				"title" : "TSSPG.REXX.EXEC(ARCTEST2)",
				"filePath" : "TSSPG.REXX.EXEC(ARCTEST2)",
				"isDataset" : true
			}, {
				"title" : ".profile",
				"filePath" : "/u/tsspg/.profile"
			}
		]
	}
}
  • If the resource is binary, a binary body is returned and the Content-Type header states which type was sent.

GET /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/<scope>/<resource>

  • This returns JSON with the attribute "content" being a JSON object that has each attribute being another JSON object which is a single configuration element.
  • NOTE: This mode does not work for binaries, as only 1 binary can be returned per call.

GET /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/<scope>/<resource> when subresources exist

  • This returns a listing of subresources that can in turn be queried.

PUT

PUT /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/<scope>/<resource>?name=<element>

  • Stores a single element (Must be a JSON object {...}) within the requested scope, ignoring aggregation policies, as long as the privilege of the user allows for this.
  • Example: /ZLUX/plugins/org.zowe.configjs/services/data/_current/org.openmainframe.zowe.codeeditor/user/sessions/default?name=tabs
  • Body:
        {
        	"_metadataVersion" : "1.1",
        	"_objectType" : "org.openmainframe.zowe.codeeditor.sessions.tabs",
        	"tabs" : [{
        			"title" : ".profile",
        			"filePath" : "/u/tsspg/.profile"
        		}, {
        			"title" : "TSSPG.REXX.EXEC(ARCTEST2)",
        			"filePath" : "TSSPG.REXX.EXEC(ARCTEST2)",
        			"isDataset" : true
        		}, {
        			"title" : ".emacs",
        			"filePath" : "/u/tsspg/.emacs"
        		}
        	]
        }
  • Response:
        {
        	"_objectType" : "org.zowe.config.resourceUpdate",
        	"_metadataVersion" : "1.1",
        	"resourceID" : "org.openmainframe.zowe.codeeditor/USER/sessions/default",
        	"result" : "Replaced item."
        }

DELETE

DELETE /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/<scope>/<resource>?recursive=true

  • Deletes all files in all leaf resources below the resource specified

DELETE /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/<scope>/<resource>?name=<element>

  • Deletes a single file in a leaf resource

DELETE /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/<scope>/<resource>

  • Deletes all files in a leaf resource
  • Does not delete the folder on disk

Administrative Access & Group

By means not discussed here, but instead handled by the server's authentication and authorization code, a user may be privileged to access or modify items that they do not own.

In the simplest case, this could just mean that the user is able to do a PUT, POST, or DELETE to a level above User, such as Instance.

The more interesting case is accessing another user's contents. In this case, the shape of the URL is different. Compare these two commands:

GET /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/user/<resource>

  • Gets the content for the current user

GET /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/users/<username>/<resource>

  • Gets the content for a specific user if authorized

This is the same structure that is or will be used for the Group scope. When requesting content from the Group scope, the user will be checked to see if they are authorized to make the request for the specific group. For example:

GET /ZLUX/plugins/org.zowe.configjs/services/data/_current/<plugin>/group/<groupname>/<resource>

  • Gets the content for the given group, if the user is authorized for it.

App API

Retrieves and stores configuration information from specific scopes. This should only be used for configuration administration user interfaces. ZLUX.UriBroker.pluginConfigForScopeUri(pluginDefinition: ZLUX.Plugin, scope: string, resourcePath:string, resourceName:string): string;

A shortcut for the preceding method, and the preferred method when you are retrieving configuration information simply to "consume" it. It "asks" for configurations using the "user" scope, and lets the configuration service decide which configuration information to retrieve and how to aggregate it (see below on how the configuration service evaluates what to return for this kind of request). ZLUX.UriBroker.pluginConfigUri(pluginDefinition: ZLUX.Plugin, resourcePath:string, resourceName:string): string;

Internal & Bootstrapping Use

Some Dataservices within Plugins can take configuration that affects their behavior. This configuration is stored within the Configuration Dataservice structure, but is not accessible via the REST API.

Within the instance configuration directory of a zLUX installation, each plugin may optionally have an _internal directory. An example of such a path would be:

~/.zowe/workspace/app-server/ZLUX/pluginStorage/<pluginName>/_internal

Within each _internal folder, the following folders may exist:

  • services/<servicename>: Configuration resources for the specific service
  • plugin: Configuration resources visible to all services in the plugin

The JSON contents within these directories will be provided as Objects to dataservices via the dataservice context Object.

NOTE Internal mode is just for storing JSON, so the binary resource type does not apply here.

Packaging defaults

The best way to provide default settings for a plugin is to include it as part of the plugin's package.
It's easy to distribute to users, requires no configuration steps, and is read-only from the server.
To package, all content must be stored within the /config/storageDefaults directory of your plugin.
Within, non-leaf resources are folders, and leaf resources are files, regardless of JSON or binary.
The _internal folder and content is also permitted.

Plugin Definition

Since the Configuration Dataservices stores data on a per-Plugin basis, each zLUX Plugin must define their resource structure in order to make use of the Configuration Dataservice. This definition is included within the Plugin's pluginDefinition.json file.

For each resource and subresource, you can define an aggregationPolicy control how the data of a broader Scope alters the resource data returned to a user when requesting a resource from a narrower Scope.

Example:

  "configurationData": { //is a direct attribute of the pluginDefinition JSON
    "resources": { //always required
      "theme": {
        "binary": true //When a resource is binary, it has no aggregation policy, and HTTP bodies are raw with Content-Type header being present, rather than a formatted JSON being utilized
      },
      "preferences": {
        "locationType": "relative", //this is the only option for now, but later absolute paths may be accepted
        "aggregationPolicy": "override" //override and none for now, but more in the future
      },
      "sessions": { //the name at this level represents the name used within a URL, such as /plugins/org.zowe.configjs/services/data/_current/org.openmainframe.zowe.codeeditor/user/sessions
        "aggregationPolicy": "none",
        "subResources": {
          "sessionName": {
            "variable": true, //if variable=true is present, the resource must be the only one in that group but the name of the resource is substituted for the name given in the REST request, so it represents more than one
            "aggregationPolicy": "none"
          }
        }
      }
    }
  }

Aggregation Policies

Aggregation Policies determine how the Configuration Dataservice will aggregate JSON objects from different Scopes together when a user requests a resource. If the user requests a resource from the User scope, the data from the User scope may replace, or be merged with the data from a broader scope such as Instance, to make a combined resource object that is returned to the user.

Aggregation Policies are not intended to be user configurable, but rather something defined by a Plugin developer, as it is set in the Plugin's definition for the Configuration Service, as the attribute "aggregationPolicy" within a resource.

The currently implemented Aggregation Policies are:

  • NONE: If the Configuration Dataservice is called for Scope User, only user-saved settings will be sent, unless there are no user-saved settings for the query, in which case the Dataservice will attempt to send data found at a more broad scope.
  • OVERRIDE: The Configuration Dataservice will get data for the resource requested at the broadest level found, and join together the resource's properties from more narrow scopes, overriding broader attributes with narrower ones when found.

Relevant Source Code

Implementation

zlux-server-framework

Example code

zlux-app-manager

VT Terminal App