npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sap/xsenv

v5.4.0

Published

Utility for easy setup and access of SAP HANA XS Advanced environment variables

Downloads

913,186

Readme

@sap/xsenv

Utility for easily reading application configurations for bound services and certificates in the SAP Cloud Platform Cloud Foundry environment, SAP XS advanced model and Kubernetes (K8S).

Install

npm install --save @sap/xsenv

Usage

var xsenv = require('@sap/xsenv');

// Read the configuration for all bound service instances
var services = xsenv.readServices();
console.log(services.serviceInstance); // prints { credentials: { user: ..., pass:... }, name: 'serviceInstance', tags: [...], label: ...

// Read the credentials for all bound service instances matching a given service query
var services = xsenv.filterServices({ label: 'hana' });
console.log(services); // prints [ { credentials: { ... } }, { credentials: { ... } } ]

// Read only the credentials portion of the configuration for a service instance matching a given service query
var svc = xsenv.serviceCredentials({ tag: 'hana' });
console.log(svc); // prints { host: '...', port: '...', user: '...', password: '...', ... }

// Read configuration for a service instance matching a given service query
var services = xsenv.getServices({ hana: { name: 'hanaInstance' }}); // returns { hana: { host: '...', port: '...', user: '...', password: '...', ... } }
var hanaInstanceCredentials = services.hana;

For specifics in the usage in different environments, read below.

Usage in Cloud Foundry and SAP XS Advanced

Cloud Foundry and SAP XS advanced both provide application configurations via environment variables. The properties of the bound services are in VCAP_SERVICES environment variable in both cases.

Service Lookup

Normally in Cloud Foundry you bind a service instance to your application with a command like this one:

cf bind-service my-app aservice

Here is how you can get this service configuration in your Node.js application if you don't know the instance name in advance:

var xsenv = require('@sap/xsenv');

var services = xsenv.readServices();
var svc = services[process.env.SERVICE_NAME];

You can look up services based on their metadata:

var svc = xsenv.serviceCredentials({ tag: 'hdb' });
console.log(svc); // prints { host: '...', port: '...', user: '...', password: '...', ... }

This example finds a service binding with hdb in the tags. See Service Query below for description of the supported query values.

You can also look up multiple services in a single call:

var xsenv = require('@sap/xsenv');

var services = xsenv.getServices({
  hana: { tag: 'hdb' },
  scheduler: { label: 'jobs' }
});

var hanaCredentials = services.hana;
var schedulerCredentials = services.scheduler;

This example finds two services - one with tag hdb and the other with label jobs. getServices function provides additional convenience that default service configuration can be provided in a JSON file.

To test the above example locally, create a file called default-services.json in the working directory of your application. This file should contain something like this:

{
  "hana": {
    "host": "localhost",
    "port": "30015",
    "user": "SYSTEM",
    "password": "secret"
  },
  "scheduler": {
    "host": "localhost",
    "port": "4242",
    "user": "my_user",
    "password": "secret"
  }
}

Note: The result property names (hana and scheduler) are the same as those in the query object and also those in default-services.json.

Local environment setup describes an alternative approach to provide service configurations for local testing.

User-Provided Service Instances

While this package can look up any kind of bound service instances, you should be aware that User-Provided Service Instances have less properties than managed service instances. Here is an example:

  "VCAP_SERVICES": {
    "user-provided": [
      {
        "name": "pubsub",
        "label": "user-provided",
        "tags": [],
        "credentials": {
          "binary": "pubsub.rb",
          "host": "pubsub01.example.com",
          "password": "p@29w0rd",
          "port": "1234",
          "username": "pubsubuser"
        },
        "syslog_drain_url": ""
	  }
    ]
  }

As you can see the only usable property is the name. In particular, there are no tags for a user-provided services.

Usage in Kubernetes

Kubernetes offers several ways of handling application configurations for bound services and certificates. @sap/xsenv expects that such configurations are handled as Kubernetes Secrets and mounted as files to the pod at a specific path. This path can be provided by the application developer, but the default is /etc/secrets/sapcp. From there, @sap/xsenv assumes that the directory structure is the following /etc/secrets/sapcp/<service-name>/<instance-name>. Here <service-name> and <instance-name> are both directories and the latter contains the credentials/configurations for the service instance as files, where the file name is the name of the configuration/credential and the content is respectively the value.

For example, the following folder structure:


/etc/
    /secrets/
            /sapcp/
                 /hana/
                 |    /hanaInst1/
                 |    |          /user1
                 |    |          /pass1
                 |    /hanaInst2/
                 |               /user2
                 |               /pass2
                 /xsuaa/
                       /xsuaaInst/
                                  /user
                                  /pass

resembles two instances of service hana - hanaInst1 and hanaInst2 each with their own credentials/configurations and one instance of service xsuaa called xsuaaInst with its credentials.

In Kubernetes you can create and bind to a service instance in the following way using the Service Catalog:

svcat provision xsuaaInst --class xsuaa --plan application
svcat bind xsuaaInst --name xsuaaBind

Upon creation of the binding, the Service Catalog will create a Kubernetes secret (by default with the same name as the binding) containing credentials, configurations and certificates. This secret can then be mounted to the pod as a volume.

The following deployment.yml file would generate the file structure above, assuming we have bindings hanaBind1, hanaBind2 and xsuaaBind for service instances hanaInst1, hanaInst2 and xsuaaInst created with Service Catalog:

...
     containers:
      - name: app
        image: app-image:1.0.0
        ports:
          - appPort: 8080
        volumeMounts:
        - name: hana-volume-1
          mountPath: "/etc/secrets/sapcp/hana/hanaInst1"
          readOnly: true
        - name: hana-volume-2
          mountPath: "/etc/secrets/sapcp/hana/hanaInst2"
          readOnly: true
        - name: xsuaa-volume
          mountPath: "/etc/secrets/sapcp/xsuaa/xsuaaInst"
          readOnly: true
      volumes:
      - name: hana-volume-1
        secret:
          secretName: hanaBind1
      - name: hana-volume-2
        secret:
          secretName: hanaBind2
      - name: xsuaa-volume
        secret:
          secretName: xsuaaBind

Of course, you can also create Kubernetes secrets directly with kubectl and mount them to the pod. As long as the mount path follows the <root-path>/<service-name>/<instance-name> pattern, @sap/xsenv will be able to read and filter the bound services configurations.

Note: The library attempts to parse property values which represent valid JSON objects. Property values representing arrays are not being parsed.

The following service credentials:

/etc/
    /secrets/
            /sapcp/
                 /some-service/
                       /some-instance/
                                  /url   - containing https://some-service
                                  /uaa   - containing { "url": "https://uaa", "clientid": "client", "clientsecret": "secret" }
                                  /other - containing [1, "two"]

Will be available to the application as:

{
  url: 'https://some-service',
  uaa: {
    url: 'https://uaa',
    clientid: 'client',
    clientsecret: 'secret'
  },
  other: '[1, "two"]'
}

Service Lookup

Service look up in the Kubernetes environment looks the same way as it does in the Cloud Foundry one.

Looking at the above example of bound services here is how you can get the service configuration of hanaInst1 in your node application:

var xsenv = require('@sap/xsenv');

var services = xsenv.readServices();
console.log(services.hanaInst1.credentials); // prints { user1: '...', pass1: '...', ... }

Here is how to lookup the service based on its metadata in Kubernetes:

var svc = xsenv.serviceCredentials({ label: 'hana' });
console.log(svc); // prints { host: '...', port: '...', user: '...', passwrod: '...', ... }

This example finds a service binding with hana as a label. Note that for Kubernetes lookup based on metadata is limited. See Service Query below for description of the supported query values in Cloud Foundry and Kubernetes.

If you have mounted your secrets to a different path, you can pass it to @sap/xsenv like so:

var xsenv = require('@sap/xsenv');

var services = xsenv.getServices('/some/user/path', {
  hana: { name: 'hanaInst1' },
  xsuaa: { label: 'xsuaa' }
});

var hanaCredentials = services.hana;
var schedulerCredentials = services.xsuaa;

Local Usage

For local testing you can provide configurations by yourself. This package allows you to provide default configurations in a separate configuration file.

  • This reduces clutter by removing configuration data from the app code.
  • You don't have to set env vars manually each time you start your app.
  • Different developers can use their own configurations for their local tests without changing files under source control. Just add this configuration file to .gitignore and .cfignore.

You can provide default configurations on two levels:

  • For bound services via getServices() and default-services.json
  • For any environment variable via loadEnv() and default-env.json

Service Query

Both getServices and filterServices use the same service query values. Due to specifics of the environment the queries in Cloud Foundry can be richer - see property table below.

Query value | Description ------------|------------ {string} | Matches the service with the same service instance name (name property). Same as { name: '<string>' }. {object} | All properties of the given object should match corresponding service instance properties as they appear in VCAP_SERVICES or the Kubernetes secret. See below what is supported on each platform. {function} | A function that takes a service object as argument and returns true, only if it is considered a match.

If an object is given as a query value, it may have the following properties:

Property | CF | K8S | Description ---------|-----|-----|------------ name | yes | yes |Service instance name - the name you use to bind the service label | yes | yes |Service name - the name shown by cf marketplace tag | yes | no |Should match any of the service tags plan | yes | no |Service instance plan - the plan you use in cf create-service

If multiple properties are given, all of them must match.

Note: Do not confuse the instance name (name property) with the service name (label property). Since you can have multiple instances of the same service bound to your app, instance name is unique while service name is not.

Here are some examples.

Find a service instance by name:

xsenv.serviceCredentials('hana');

Look up a service by tag:

xsenv.serviceCredentials({ tag: 'relational' });

Match several properties:

xsenv.serviceCredentials({ label: 'hana', plan: 'shared' });

Pass a custom filter function:

xsenv.serviceCredentials(function(service) {
  return /shared/.test(service.plan) && /hdi/.test(service.label);
});

Notice that the filter function is called with the full service object as it appears in VCAP_SERVICES, but serviceCredentials returns only the credentials property of the matching service. The behaviour is the same in Kubernetes - the function will return only the contents of the credentials portion of the mounted secret.

API

getServices([path], query, [servicesFile])

Looks up bound service instances matching the given query. If a service is not found - returns default service configuration loaded from a JSON file. The order of lookup is VCAP_SERVICES -> mounted secrets path in K8S -> default service configuration.

  • path - (optional) A string containing the mount path where the secrets are located in Kubernetes. By default is "/etc/secrets/sapcp". For example, by default the credentials for an instance "inst-name" of service "service-name" would be located under "/etc/secrets/sapcp/service-name/inst-name".
  • query - An object describing requested services. Each property value is a filter as described in Service Query
  • servicesFile - (optional) path to JSON file to load default service configuration (default is default-services.json). If null, do not load default service configuration.
  • returns - An object with the same properties as in query argument where the value of each property is the respective service credentials object
  • throws - An error, if for some of the requested services no or multiple instances are found

serviceCredentials([path], filter)

Looks up a bound service instance matching the given filter works for both the Kubernetes and Cloud Foundry environments.

Note: This function does not load default service configuration from default-services.json.

  • path - (optional) A string containing the mount path where the secrets are located in Kubernetes. By default is "/etc/secrets/sapcp".
  • filter - Service lookup criteria as described in Service Query
  • returns - Credentials object of found service
  • throws - An error in case no or multiple matching services are found

filterServices([path], filter)

Returns all bound services that match the given criteria. Works in Cloud Foundry and Kubernetes.

  • path - (optional) A string containing the mount path where the secrets are located in Kubernetes. By default is "/etc/secrets/sapcp".
  • filter - Service lookup criteria as described in Service Query
  • returns - An array of credentials objects of matching services. Empty array, if no matches found.

readServices([path], [options])

  • path - (optional) A string containing the mount path where the secrets are located in Kubernetes. By default is "/etc/secrets/sapcp".
  • options - (optional) An object with options to customize behavior. Only supports field disableCache to disable K8s secrets caching.
  • returns Returns an object where each service instance is mapped to its name. Works in Kubernetes and Cloud Foundry.

For example, given this VCAP_SERVICES:

  {
    "hana" : [ {
      "credentials" : {
        ...
      },
      "label" : "hana",
      "name" : "hana1",
      "plan" : "shared",
      "tags" : [ "hana", "relational" ]
    },
    {
      "credentials" : {
        ...
      },
      "label" : "hana",
      "name" : "hana2",
      "plan" : "shared",
      "tags" : [ "hana", "relational", "SP09" ]
    } ]
  }

readServices would return:

{
  hana1: {
    "credentials" : {
      ...
    },
    "label" : "hana",
    "name" : "hana1",
    "plan" : "shared",
    "tags" : [ "hana", "relational" ]
  },
  hana2: {
    "credentials" : {
      ...
    },
    "label" : "hana",
    "name" : "hana2",
    "plan" : "shared",
    "tags" : [ "hana", "relational", "SP09" ]
  }
}

cfServiceCredentials(filter)

Same as serviceCredentials(filter) but works only in Cloud Foundry. It is recommended to use the generic function.

filterCFServices(filter)

Same as filterServices(filter) but works only in Cloud Foundry. It is recommended to use the generic function.

readCFServices()

Same as readServices() but works only in Cloud Foundry. It is recommended to use the generic function.

Local environment setup

To test your application locally you often need to setup its environment so that resembles the environment in Cloud Foundry or Kubernetes. You can do this easily by defining the necessary environment variables in a JSON file.

For example you can create file default-env.json with the following content in the working directory of the application :

{
  "PORT": 3000,
  "VCAP_SERVICES": {
    "hana": [
      {
        "credentials": {
          "host": "myhana",
          "port": "30015",
          "user": "SYSTEM",
          "password": "secret"
        },
        "label": "hana",
        "name": "hana-R90",
        "tags": [
          "hana",
          "database",
          "relational"
        ]
      }
    ],
    "scheduler": [
      {
        "credentials": {
          "host": "localhost",
          "port": "4242",
          "user": "jobuser",
          "password": "jobpassword"
        },
        "label": "scheduler",
        "name": "jobscheduler",
        "tags": [
          "scheduler"
        ]
      }
    ]
  }
}

Then load it in your application:

xsenv.loadEnv();
console.log(process.env.PORT); // prints 3000
console.log(xsenv.cfServiceCredentials('hana-R90')); // prints { host: 'myhana, port: '30015', user: 'SYSTEM', password: 'secret' }

This way you don't need in your code conditional logic if it is running in Cloud Foundry or locally.

You can also use a different file name:

xsenv.loadEnv('myenv.json');

loadEnv([file])

Loads the environment from a JSON file. This function converts each top-level property to a string and sets it in the respective environment variable, unless it is already set. This function does not change existing environment variables. So the file content acts like default values for environment variables.

This function does not complain if the file does not exist.

  • file - optional name of JSON file to load, 'default-env.json' by default. Does nothing if the file does not exist.

Loading SSL Certificates

If SSL is configured in XS advanced On-Premise Runtime, it will provide one or more trusted CA certificates that applications can use to make SSL connections. If present, the file paths of these certificates are listed in XS_CACERT_PATH environment variable separated by path.delimiter (: on LINUX and ; on Windows).

loadCertificates([certPath])

Loads the certificates listed in the given path. If this argument is not provided, it uses XS_CACERT_PATH environment variable instead. If that is not set either, the function returns undefined. The function returns an array even if a single certificate is provided. This function is synchronous.

  • certPath - optional string with certificate files to load. The file names are separated by path.delimiter. Default is process.env.XS_CACERT_PATH.
  • returns - an array of loaded certificates or undefined if certPath argument is not provided
  • throws - an error, if some of the files could not be loaded

For example, this code loads the trusted CA certificates so they are used for all subsequent outgoing HTTPS connections:

var https = require('https');
var xsenv = require('@sap/xsenv');

https.globalAgent.options.ca = xsenv.loadCertificates();

This function can be used also to load SSL certificates for HANA like this:

var hdb = require('hdb');
var xsenv = require('@sap/xsenv');

var client = hdb.createClient({
  host : 'hostname',
  port : 30015,
  ca   : xsenv.loadCertificates(),
  ...
});

Debugging

Set DEBUG=xsenv in the environment to enable debug traces. See debug package for details.