diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fcfe92..51bac79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 2.0.0 +**Maintainer**: Raffael Sahli \ +**Date**: Thur 14 Feb 2019 14:28:20 PM ET + +* [FIX] Fixes "(node:25116) UnhandledPromiseRejectionWarning: TypeError: result is not iterable" due error response from icinga +* [FIX] Better error handling and catching various uncaught promises +* [FIX] Docs fix regaring how to create icinga2 api user +* [CHANGE] Upgrade to winston ^3.0.0 stable +* [CHANGE] Log format is now in json and includes a timestamp +* [FIX] Service protocol is now lower cases which fixes issues like check_command=TCP insteadof check_command=tcp +* [FIX] Fixes icinga api username is now correctly read from ICINGA_API_USERNAME as documented (instead ICINGA_API_USER) +* [FEATURE] Add support for service/host definitions in kubernetes annotations #1 +* [BREAKER!] Removed service.portNameAsCommand option, use kubernetes annotations +* [FEATURE] Discover persistent volumes and create icinga services for those #4 +* [FIX] Fixed typo Definiton => Definition in default config example +* [CHANGE] Added various new tests +* [FIX] kube workes are in no namespace, remove icinga groups +* [FIX] fixed Error: Invalid attribute specified: host_name\n for kube nodes +* [FIX] fixed duplicate ingress objects (different path, same ingress names in different namespaces, ...) +* [CHANGE] Added hostName setting, by default all resources (except nodes) get attached to single host object of their type (can be configured differently) +* [CHANGE] Implemented workaround for icinga issue https://github.com/Icinga/icinga2/issues/6012, restart icinga service after adding new objects +* [FIX] Changing boolean values in config.json now reflects as configured (Set to false was not possible in v1.x) + + ## 1.0.1 **Maintainer**: Raffael Sahli \ **Date**: Fri 01 Jun 2018 01:18:20 PM CEST @@ -6,6 +30,7 @@ * [CHANGE] config logger.level => log.level * [FIX] catch error objects from kube watchers + ## 1.0.0 **Maintainer**: Raffael Sahli \ **Date**: Thu May 24 14:52:11 CEST 2018 diff --git a/Dockerfile b/Dockerfile index f3e5b77..4b15753 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8.10.0-alpine +FROM node:10.15.1-alpine RUN apk update && apk add git diff --git a/README.md b/README.md index 85a178c..b5906e9 100644 --- a/README.md +++ b/README.md @@ -4,51 +4,104 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitHub release](https://img.shields.io/github/release/gyselroth/kube-icinga.svg)](https://github.com/gyselroth/kube-icinga/releases) -kube-icinga will automatically deploy icinga objects to monitor your kubernetes services. +kube-icinga automatically deploys icinga objects out of your kubernetes resources. It has built-in autodiscovery and will work out of the box. However you can change various default configurations and deploy custom icinga objects or disable/enable kubernetes objects to monitor. -## How does it work? +## Features -Multiple watchers are bootstraped and listen for any kubernetes changes. Those changes will reflect immediately on icinga2. +* Autodiscovery +* Icinga servicegroup support +* Create services for kubernetes nodes, services, ingresses and persistent volumes +* Completely customizable per resource or per resource type -* Kubernetes namespaces will result in icinga service groups and host groups -* Nodes will result in host objects -* Ingresses will result in dummy icinga host objects (hostname is taken from metadata.name) whereas ingress paths will get deployed as icinga services related to the ingress host object ->**Note**: You may change this behaviour by attaching the services (ingress paths) to all kubernetes worker nodes by setting `kubernetes.ingresses.attachToNodes` to `true`. (This will result in many more services and checks depending on the size of your cluster!) -* Services (ClusterIP, NodePort, LoadBalanacer) will also result in icinga host objects and service ports are icinga services. ->**Note**: NodePort services will always be attached to each kubernetes worker node. See [NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport) services for more information. +## How does it work? -Since there is no such thing as hosts in the world of moving and containers. The host object (kubernetes metadata.name) on icinga may just be a [dummy](https://www.icinga.com/docs/icinga2/latest/doc/10-icinga-template-library/#plugin-check-command-dummy) and will not get checked -if the service can not be attached to kubernetes nodes. +Multiple watchers are bootstraped and listen for any kubernetes changes. Those changes will reflect immediately on your icinga environment. -You may wonder where your pods are in this setup. Well it does not make any sense to monitor single pods. Pods are mortal and (always) moving. The important thing is to monitor your services as an entity. +* Kubernetes namespaces will result in icinga service groups +* Nodes will result in host objects +* Ingresses will result in icinga services (and or host objects) +* Services (ClusterIP, NodePort [2], LoadBalanacer) will result in icinga services (and/or host objects) +* Persistent volumes will result in icinga services (and/or host objects) ## Table of Contents -* [Description](#description) +* [Features](#features) * [How does it work?](#how-does-it-work) +* [Resource types](#resource-types) + * [Namespaces](#namespaces) + * [Nodes](#nodes) + * [Ingresses](#ingresses) + * [Services](#services) + * [Volumes](#volumes) * [Requirements](#requirements) * [Setup icinga2 api user](#setup-icinga2-api-user) * [Deployment](#deployment) -* [Configuration](#configuration) +* [Resource visibility](#resource-visibility) * [Advanced topics](#advanced-topics) - * [ClusterIP services](#clusterip-services) * [Using icinga2 apply rules](#using-icinga2-apply-rules) - * [Overwite specific icinga object definition](#overwite-specific-icinga-object-definition) + * [Globally overwrite icinga object definitions](#globally-overwrite-icinga-object-definitions) + * [Overwrite icinga object definitions directly in kubernetes resources](overwrite-icinga-object-definitions-directly-in-kubernetes-resources) +* [Configuration](#configuration) + +## Resource types + +### Namespaces +Namespaces will result in icinga servicegroups. +Note that the servicegroup gets only created if one of the following resources exist in a given namespace. + +### Nodes +Kubernetes nodes get mappend 1:1 to icinga host objects. There is no other magic. +However you may change the icinga host object by configuring the global config `kubernetes.nodes` or +change annotations values in single node objects. + +### Ingresses +Each kubernetes ingress hostname/path will result in a single icinga service. By default all those services get +attached to a single icinga host named `kubernetes-ingresses` and each service also gets attached to a servicegroup which is taken from the kubernetes namespace. +You may change the hostname by configuring `kubernetes.ingresses.hostName` (or set a custom hostname in an annotation) or even set `kubernetes.ingresses.hostName` to `null`. +If hostName is `null`, host objects get dynamically created named `ingress-${namespace}-${name}`. This will result in more host objects but the services still get created and get attached to their servicegroup (namespace). +If you choose to create dynamic host objects you may want to disable service provisioning, you may set `kubernetes.ingresses.applyServices` to `false` and create services manually with icinga apply rules (see [advanced](#using-icinga2-apply-rules)). +Besides that you can still configure advanced options for those host objects and/or service objects (See configuration). +For instance you may attach ingress services directly to all kubernetes worker nodes by setting `kubernetes.ingresses.attachToNodes` to `true`. (This will result in many more services and checks depending on the size of your cluster!) + +By default ingresses get checked with the `http` check_command. Usually you do not want to change this but you can by using annotations or changing the global ingress settings. + +### Services +Each service port will result in a single icinga service (like ingress paths). By default all those services get +attached to a single host named `kubernetes-${serviceType}-services`. Supported service types are either ClusterIP, NodePort or LoadBalancer. +Like ingresses you may change the hostname or configure host provisioning by setting hostName to `false` (`kubernetes.services.${serviceType}.hostName`). +Dynamic service host objects get named `service-${namespace}-${name}`. You may also disable service provisioning by setting `kubernetes.services.${serviceType}.applyServices` to `false` if you prefer icinga apply rules. +ClusterIP provisioning is disabled by default since ClusterIP services are only visible internally in kubernetes. You need either an icinga deployment within the kubernetes cluster or optionally +an icinga [satelite](https://www.icinga.com/docs/icinga2/latest/doc/06-distributed-monitoring) if you decide to enable ClusterIP provisioning. +NodePort services get attached by default directly to the kubernetes icinga node objects. + +By default services get checked with the service protocol which usually result in check_tcp. However you may set custom settings using annotations (or global service settings). + +### Volumes +Each persistent volume will result in a single icinga service. By default all those services get attached to a single host named `kubernetes-volumes`. +Like for ingresses and services you may change the same advanced settings for volumes. +Since a persistent volume is not directly attached to namespace the namespace for the service group gets taken from the persistent volume claim. + +>**Note** Volume services are created by default with the dummy check_command. Since there is no way for kube-icinga to specify the correct command automatically. You need to either overwrite the check_command in annotations, set the command +in the serviceDefinition for volumes or disable service provisioning, enable host provisioning (set hostName to `null`) and create icinga apply rule based services manually. + +You may wonder where your pods are in this setup. Well it does not make any sense to monitor single pods. Pods are mortal and (always) moving. The important thing is to monitor your services as an entity. + ## Requirements -* A running kubernetes cluster (or minikube) +* A running kubernetes cluster * Icinga2 server with enabled API module ## Setup icinga2 api user -kube-icina requires an [icinga api user](https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/#creating-apiusers) which first must be created. -You can either create it manually or using the icinga2 command utility. +kube-icina requires an [icinga api user](https://icinga.com/docs/icinga2/latest/doc/12-icinga2-api/#authentication) which first must be created. - ```sh -icinga2 api user --user kube-icinga --password kube-icinga +/etc/icinga2/conf.d/api-users.conf: +``` +object ApiUser "kube-icinga" { + password = "kube-icinga" + permissions = ["*"] +} ``` -Use another password! - ## Deployment The recommended (and logical) way to deploy kube-icinga is deplyoing kube-icinga on kubernetes itself using the [gyselroth/kube-icinga](https://hub.docker.com/r/gyselroth/kube-icinga/) docker image. @@ -59,6 +112,77 @@ kubectl -f https://raw.githubusercontent.com/gyselroth/kube-icinga/master/kube-i ``` (Change the secret password and ICINGA_ADDRESS value accordingly) +>**Note**: kube-icinga will be created as single pod deployment in the kubernetes kube-system namespace. You may changes this behaviour. + +### Resource visibility +The resource yaml also contains a new cluster role `kube-icinga`. kube-icinga will create icinga objects for all visible namespaces which are by default all namespaces since it is a kubernetes cluster role. +You may specify resources visibile to kube-icinga with custom RBAC rules. + +## Advanced topics + +### Using icinga2 apply rules +You certainly can use icinga2 apply rules. You may disable auto service deployments via `applyServices` for ingresses, services and volumes and define your own services via [apply rules](https://www.icinga.com/docs/icinga2/latest/doc/03-monitoring-basics/#using-apply). +Of course you can also setup a mixed deployment. Automatically let kube-icinga deploy services and apply additional services via apply rules. + +>**Note**: Since icinga apply rules are [not triggered](https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/#modifying-objects) if an object gets updated kube-icinga will delete and recreate those objects. + +All icinga host objects are created with all kubernetes data available packed in the variable `vars.kubernetes`. Therefore you may apply rules with this data. + +### Globally overwrite icinga object definitions +It is possible to set custom options for the icinga objects during creation. You might set custom values via `kubernetes.ingresses.hostTemplates` or kubernetes.ingresses.serviceTemplate`. The same can also be done for services. For example it might be crucial to set a custom zone for ClusterIP services since they are only reachable within the cluster and shall be monitored by an icinga satelite node. Any valid setting for a specific icinga object can be set. + +>**Note** This will set the custom options for all objects from the same type (services.ClusterIP in the follwing example), this may not what you want. Using kubernetes annotations +you may configure custom incinga attributes directly in the kubernetes resource definition. + +```json +{ + "kubernetes": { + "services": { + "ClusterIP": + "discover": true, + "serviceDefiniton": { + "zone": "my_custom_icinga_zone" + "vars.a_custom_icinga_variable": ["foo", "bar"] + }, + } + } + } +} +``` + +### Overwrite icinga object definitions directly in kubernetes resources + +kube-icinga is able to parse kubernetes annotations and merge those with its default settings. + +>**Note**: With annotations you may set specific settings for each kubernetes resources while configure options for kube-icinga set settings on a resource type basis. + +You may use the following annotations: + +| Name | Description | +|-------|------------| +| `kube-icinga/check_command` | Use a custom icinga check command. | +| `kube-icinga/host` | Use a custom hostname (to which icinga host a service gets bound to). | +| `kube-icinga/template` | Use a custom icinga template. | +| `kube-icinga/definition` | JSON encoded icinga definiton which may contain advanced icinga options and gets merged with the defaults. | + + +```yaml +kind: PersistentVolume +apiVersion: v1 +metadata: + name: generic-storage-24afe01f-2300-11e9-94e3-0050568fe3c2, + selfLink":"/api/v1/persistentvolumes/generic-storage-24afe01f-2300-11e9-94e3-0050568fe3c2 + uid":"2a66425e-2300-11e9-94e3-0050568fe3c2 + resourceVersion: 65603642 + creationTimestamp: 2019-01-28T13:25:22Z + annotations: + kube-icinga/check_command: "check_storage_lun" + kube-icinga/hostname: "storagesystem.example.lan" + hpe.com/docker-volume-name": "generic-storage-24afe01f-2300-11e9-94e3-0050568fe3c2" + pv.kubernetes.io/provisioned-by: "example.com/storage" + volume.beta.kubernetes.io/storage-class: "generic-storage" +``` + ## Configuration kube-icinga itself can be configured via environment variables however you may also deploy and change the `config.json` file instead of environment variables. @@ -76,62 +200,39 @@ List of configurable values: |`kubernetes.nodes.hostDefinition`|You may overwrite specific attributes of the icinga [host definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#host).|`ICINGA_NODES_HOST_DEFINITION`|`{}`| |`kubernetes.nodes.hostTemplates`|Specify a list of host templates|`ICINGA_NODES_HOST_TEMPLATES`|`['generic-host']`| |`kubernetes.ingresses.discover`|Deploy kubernetes ingress objects|`KUBERNETES_INGRESSES_DISCOVER`|`true`| +|`kubernetes.ingresses.hostName`|The name of the icinga host object to attach services to (May also be null to enabled host object provisioning)|`KUBERNETES_INGRESSES_HOSTNAME`|`kubernetes-ingresses`| |`kubernetes.ingresses.serviceDefinition`|You may overwrite specific attributes of the icinga [service definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#service). |`ICINGA_INGRESSES_SERVICE_DEFINITION`|'{}'| |`kubernetes.ingresses.hostDefinition`|You may overwrite specific attributes of the icinga [host definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#host).|`ICINGA_INGRESSES_SERVICE_DEFINITION`|`{}`| |`kubernetes.ingresses.serviceTemplates`|Specify a list of icinga service templates|`ICINGA_INGRESSES_SERVICE_TEMPLATES`|`['generic-service']`| |`kubernetes.ingresses.hostTemplates`|Specify a list of host templates|`ICINGA_INGRESSES_HOST_TEMPLATES`|`['generic-host']`| |`kubernetes.ingresses.applyServices`|Apply ingress paths as icinga services attached to the icinga ingress host|`KUBERNETES_INGRESSES_APPLYSERVICES`|`true`| |`kubernetes.ingresses.attachToNodes`|If `true` instead attaching port services to a dummy host object `metadata.name` all services get attached to each kubernetes worker node!|`KUBERNETES_INGRESSES_ATTACHTONODES`|`false`| -|`kubernetes.services.ClusterIP.discover`|Deploy kubernetes service objects|`KUBERNETES_SERVICES_TYPE_DISCOVER`|`false`| -|`kubernetes.services.ClusterIP.serviceDefinition`|You may overwrite specific attributes of the icinga [service definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#service). |`ICINGA_SERVICES_SERVICE_DEFINITION`|`{}`| +|`kubernetes.volumes.discover`|Deploy kubernetes ingress objects|`KUBERNETES_VOLUMES_DISCOVER`|`true`| +|`kubernetes.volumes.hostName`|The name of the icinga host object to attach services to (May also be null to enabled host object provisioning)|`KUBERNETES_VOLUMES_HOSTNAME`|`kubernetes-volumes`| +|`kubernetes.volumes.serviceDefinition`|You may overwrite specific attributes of the icinga [service definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#service). |`ICINGA_VOLUMES_SERVICE_DEFINITION`|'{}'| +|`kubernetes.volumes.hostDefinition`|You may overwrite specific attributes of the icinga [host definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#host).|`ICINGA_VOLUMES_SERVICE_DEFINITION`|`{}`| +|`kubernetes.volumes.serviceTemplates`|Specify a list of icinga service templates|`ICINGA_VOLUMES_SERVICE_TEMPLATES`|`['generic-service']`| +|`kubernetes.volumes.hostTemplates`|Specify a list of host templates|`ICINGA_VOLUMES_HOST_TEMPLATES`|`['generic-host']`| +|`kubernetes.volumes.applyServices`|Apply volumes as icinga services attached to a icinga host object|`KUBERNETES_VOLUMES_APPLYSERVICES`|`true`| +|`kubernetes.volumes.attachToNodes`|If `true` instead attaching port services to a dummy host object `metadata.name` all services get attached to each kubernetes worker node!|`KUBERNETES_VOLUMES_ATTACHTONODES`|`false`| +|`kubernetes.services.ClusterIP.discover`|Deploy kubernetes service objects|`KUBERNETES_SERVICES_CLUSTERIP_DISCOVER`|`false`| +|`kubernetes.services.ClusterIP.hostName`|The name of the icinga host object to attach services to (May also be null to enabled host object provisioning)|`KUBERNETES_SERVICES_CLUSTERIP_HOSTNAME`|`kubernetes-clusterip-services`| +|`kubernetes.services.ClusterIP.serviceDefinition`|You may overwrite specific attributes of the icinga [service definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#service). |`ICINGA_SERVICES_CLUSTERIP_SERVICE_DEFINITION`|`{}`| |`kubernetes.services.ClusterIP.hostDefinition`|You may overwrite specific attributes of the icinga [host definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#host).|`ICINGA_SERVICES_CLUSTERIP_HOST_DEFINITION`|`{}`| |`kubernetes.services.ClusterIP.serviceTemplates`|Specify a list of icinga service templates|`ICINGA_SERVICES_CLUSTERIP_SERVICE_TEMPLATES`|`['generic-service']`| |`kubernetes.services.ClusterIP.hostTemplates`|Specify a list of host templates|`ICINGA_SERVICES_CLUSTERIP_HOST_TEMPLATES`|`['generic-host']`| |`kubernetes.services.ClusterIP.applyServices`|URI of LDAP server|`KUBERNETES_SERVICES_CLUSTERIP_APPLYSERVICES`|`true`| -|`kubernetes.services.ClusterIP.portNameAsCommand`|Creates an icinga service with the port name as check_command. Example: If the port name is `mongodb` then the service gets checked via check_mongodb. If `false` the port protocl gets used which is usually TCP or UDP. >**Note**: It will fallback to the port protocol if the icinga plugin was not found as specified in the name. |`KUBERNETES_SERVICES_CLUSTERIP_PORTNAMEASCOMMAND`|`true`| -|`kubernetes.services.NodePort.discover`|Deploy kubernetes service objects|`KUBERNETES_SERVICES_TYPE_DISCOVER`|`true`| +|`kubernetes.services.NodePort.discover`|Deploy kubernetes service objects|`KUBERNETES_SERVICES_NODEPORT_DISCOVER`|`true`| |`kubernetes.services.NodePort.serviceDefinition`|You may overwrite specific attributes of the icinga [service definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#service). |`ICINGA_SERVICES_SERVICE_DEFINITION`|`{}`| |`kubernetes.services.NodePort.hostDefinition`|You may overwrite specific attributes of the icinga [host definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#host).|`ICINGA_SERVICES_NODEPORT_HOST_DEFINITION`|`{}`| |`kubernetes.services.NodePort.serviceTemplates`|Specify a list of icinga service templates|`ICINGA_SERVICES_NODEPORT_SERVICE_TEMPLATES`|`['generic-service']`| |`kubernetes.services.NodePort.hostTemplates`|Specify a list of host templates|`ICINGA_SERVICES_NODEPORT_HOST_TEMPLATES`|`['generic-host']`| |`kubernetes.services.NodePort.applyServices`|URI of LDAP server|`KUBERNETES_SERVICES_NODEPORT_APPLYSERVICES`|`true`| -|`kubernetes.services.NodePort.portNameAsCommand`|Creates an icinga service with the port name as check_command. Example: If the port name is `mongodb` then the service gets checked via check_mongodb. If `false` the port protocl gets used which is usually TCP or UDP. >**Note**: It will fallback to the port protocol if the icinga plugin was not found as specified in the name. |`KUBERNETES_SERVICES_NODEPORT_PORTNAMEASCOMMAND`|`true`| -|`kubernetes.services.LoadBalancer.discover`|Deploy kubernetes service objects|`KUBERNETES_SERVICES_TYPE_DISCOVER`|`true`| -|`kubernetes.services.LoadBalancer.serviceDefinition`|You may overwrite specific attributes of the icinga [service definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#service). |`ICINGA_SERVICES_SERVICE_DEFINITION`|`{}`| +|`kubernetes.services.NodePort.hostName`|The name of the icinga host object to attach services to (May also be null to enabled host object provisioning)|`KUBERNETES_SERVICES_NODEPORT_HOSTNAME`|`kubernetes-nodeport-services`| +|`kubernetes.services.LoadBalancer.discover`|Deploy kubernetes service objects|`KUBERNETES_SERVICES_LOADBALANCER_DISCOVER`|`true`| +|`kubernetes.services.LoadBalancer.hostName`|The name of the icinga host object to attach services to (May also be null to enabled host object provisioning)|`KUBERNETES_SERVICES_LOADBALANCER_HOSTNAME`|`kubernetes-loadbalancer-services`| +|`kubernetes.services.LoadBalancer.serviceDefinition`|You may overwrite specific attributes of the icinga [service definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#service). |`ICINGA_SERVICES_LOADBALANCER_SERVICE_DEFINITION`|`{}`| |`kubernetes.services.LoadBalancer.hostDefinition`|You may overwrite specific attributes of the icinga [host definiton](https://www.icinga.com/docs/icinga2/latest/doc/09-object-types/#host).|`ICINGA_SERVICES_LOADBALANCER_HOST_DEFINITION`|`{}`| |`kubernetes.services.LoadBalancer.serviceTemplates`|Specify a list of icinga service templates|`ICINGA_SERVICES_LOADBALANCER_SERVICE_TEMPLATES`|`['generic-service']`| |`kubernetes.services.LoadBalancer.hostTemplates`|Specify a list of host templates|`ICINGA_SERVICES_LOADBALANCER_HOST_TEMPLATES`|`['generic-host']`| |`kubernetes.services.LoadBalancer.applyServices`|URI of LDAP server|`KUBERNETES_SERVICES_LOADBALANCER_APPLYSERVICES`|`true`| -|`kubernetes.services.LoadBalancer.portNameAsCommand`|Creates an icinga service with the port name as check_command. Example: If the port name is `mongodb` then the service gets checked via check_mongodb. If `false` the port protocl gets used which is usually TCP or UDP. >**Note**: It will fallback to the port protocol if the icinga plugin was not found as specified in the name. |`KUBERNETES_SERVICES_LOADBALANCER_PORTNAMEASCOMMAND`|`true`| - -## Advanced topics - -### ClusterIP services -Ingresses, NodePort and Load Balancer services will expose your kubernetes services to the public but not ClusterIP services. ClusterIP services (Which is the default) are only internal kubernetes services and not available from outside the cluster. If your icinga2 setup lives outside the cluster you only have to options, either disable deployment for those objects or setup an icinga [satelite](https://www.icinga.com/docs/icinga2/latest/doc/06-distributed-monitoring) container which will live in kubernetes. Of course you may also deploy an entire icinga2 setup on kubernetes if you do not have an existing one. - -### Using icinga2 apply rules -You certainly can use icinga2 apply rules. You may disable auto service deployments via `applyServices` for ingresses and services and define your own services via [apply rules](https://www.icinga.com/docs/icinga2/latest/doc/03-monitoring-basics/#using-apply). -Of course you can also setup a mixed deployment. Automatically let kube-icinga deploy services and apply additional services via apply rules. - ->**Note**: Since icinga apply rules are [not triggered](https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/#modifying-objects) if an object gets updated kube-icinga will delete and recreate those objects. - -All icinga host object are created with all kubernetes data available packed in the variable `vars.kubernetes`. Therefore you can apply rules with this data, for example this will create an icinga service for all objects with a kubernetes label foo=bar. - -### Overwite specific icinga object definition -It is possible to set custom options for the icinga objects during creation. You might set custom values via `kubernetes.ingresses.hostTemplates` or kubernetes.ingresses.serviceTemplate`. The same can also be done for services. For example it might be crucial to set a custom zone for ClusterIP services since they are only reachable within the cluster and shall be monitored by an icinga satelite node. Any valid setting for a specific icinga object can be set. - -```json -{ - "kubernetes": { - "services": { - "ClusterIP": - "discover": true, - "serviceDefiniton": { - "zone": "my_custom_icinga_zone" - "vars.a_custom_icinga_variable": ["foo", "bar"] - }, - } - } - } -} -``` diff --git a/config.json b/config.json index 79ffc6c..8e4e781 100644 --- a/config.json +++ b/config.json @@ -4,10 +4,10 @@ }, "cleanup": true, "icinga": { - "address": "", + "address": "127.0.0.1", "port": 5665, - "apiUser": "", - "apiPassword": "" + "apiUser": "kube-icinga", + "apiPassword": "kube-icinga" }, "kubernetes": { "nodes": { @@ -17,7 +17,18 @@ }, "ingresses": { "discover": true, - "serviceDefiniton": {}, + "hostName": "kubernetes-ingresses", + "serviceDefinition": {}, + "hostDefinition": {}, + "serviceTemplates": ["generic-service"], + "hostTemplates": ["generic-host"], + "applyServices": true, + "attachToNodes": false + }, + "volumes": { + "discover": true, + "hostName": "kubernetes-volumes", + "serviceDefinition": {}, "hostDefinition": {}, "serviceTemplates": ["generic-service"], "hostTemplates": ["generic-host"], @@ -27,30 +38,31 @@ "services": { "ClusterIP": { "discover": false, + "hostName": "kubernetes-clusterip-services", + "serviceDefinition": {}, "hostDefinition": {}, "serviceTemplates": ["generic-service"], "hostTemplates": ["generic-host"], - "applyServices": false, - "portNameAsCommand": true + "applyServices": true }, "NodePort": { "discover": true, - "serviceDefiniton": {}, + "hostName": "kubernetes-nodeport-services", + "serviceDefinition": {}, "hostDefinition": {}, "serviceTemplates": ["generic-service"], "hostTemplates": ["generic-host"], - "applyServices": false, - "portNameAsCommand": true, + "applyServices": true, "attachToNodes": false }, "LoadBalancer": { "discover": true, - "serviceDefiniton": {}, + "hostName": "kubernetes-loadbalancer-services", + "serviceDefinition": {}, "hostDefinition": {}, "serviceTemplates": ["generic-service"], "hostTemplates": ["generic-host"], - "applyServices": false, - "portNameAsCommand": true + "applyServices": true } } } diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 0000000..08e59ac --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,62 @@ +version: '2' +services: + icinga2: + image: jordan/icinga2 + restart: on-failure:5 + # Set your hostname to the FQDN under which your + # sattelites will reach this container + hostname: icinga2 + environment: + - MYSQL_ROOT_PASSWORD= + - ICINGA2_FEATURE_GRAPHITE=1 + # Important: + # keep the hostname graphite the same as + # the name of the graphite docker-container + - ICINGA2_FEATURE_GRAPHITE_HOST=graphite + - ICINGA2_FEATURE_GRAPHITE_PORT=2003 + - ICINGA2_FEATURE_GRAPHITE_URL=http://graphite + #- ICINGAWEB2_ADMIN_USER=icingaadmin + #- ICINGAWEB2_ADMIN_PASS=icinga + #- ICINGA2_USER_FULLNAME=Icinga2 Docker Monitoring Instance + - DEFAULT_MYSQL_HOST=mysql + volumes: + - ./data/icinga/cache:/var/cache/icinga2 + - ./data/icinga/certs:/etc/apache2/ssl + - ./data/icinga/etc/icinga2:/etc/icinga2 + - ./data/icinga/etc/icingaweb2:/etc/icingaweb2 + - ./data/icinga/lib/icinga:/var/lib/icinga2 + - ./data/icinga/lib/php/sessions:/var/lib/php/sessions + - ./data/icinga/log/apache2:/var/log/apache2 + - ./data/icinga/log/icinga2:/var/log/icinga2 + - ./data/icinga/log/icingaweb2:/var/log/icingaweb2 + - ./data/icinga/log/mysql:/var/log/mysql + - ./data/icinga/spool:/var/spool/icinga2 + # If you want to enable outbound e-mail, create the files + # - ./ssmtp/ssmtp.conf + # - ./ssmtp/revaliases + # and configure to your corresponding mail setup. + # See: https://github.com/jjethwa/icinga2#sending-notification-mails + #- ./ssmtp/revaliases:/etc/ssmtp/revaliases:ro + #- ./ssmtp/ssmtp.conf:/etc/ssmtp/ssmtp.conf:ro + ports: + - "80:80" + - "443:443" + - "5665:5665" + graphite: + image: graphiteapp/graphite-statsd:latest + container_name: graphite + restart: on-failure:5 + hostname: graphite + volumes: + - ./data/graphite/conf:/opt/graphite/conf + - ./data/graphite/storage:/opt/graphite/storage + - ./data/graphite/log/graphite:/var/log/graphite + - ./data/graphite/log/carbon:/var/log/carbon + mysql: + image: mariadb:10.1 + environment: + - MYSQL_ROOT_PASSWORD= + volumes: + - ./data/mysql/data:/var/lib/mysql + # If you have previously used the container's internal DB use: + #- ./data/icinga/lib/mysql:/var/lib/mysql diff --git a/package-lock.json b/package-lock.json index d320678..997fc61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,23 +5,31 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.49.tgz", - "integrity": "sha1-vs2AVIJzREDJ0TfkbXc0DmTX9Rs=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "@babel/highlight": "7.0.0-beta.49" + "@babel/highlight": "7.0.0" } }, "@babel/highlight": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.49.tgz", - "integrity": "sha1-lr3GtD4TSCASumaRsQGEktOWIsw=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { "chalk": "2.4.1", "esutils": "2.0.2", - "js-tokens": "3.0.2" + "js-tokens": "4.0.0" + }, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } } }, "@sindresorhus/is": { @@ -42,9 +50,9 @@ "dev": true }, "abab": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", - "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", "dev": true }, "acorn": { @@ -54,12 +62,21 @@ "dev": true }, "acorn-globals": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", - "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", + "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", "dev": true, "requires": { - "acorn": "5.5.3" + "acorn": "6.1.0", + "acorn-walk": "6.1.1" + }, + "dependencies": { + "acorn": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", + "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "dev": true + } } }, "acorn-jsx": { @@ -79,6 +96,12 @@ } } }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -96,23 +119,6 @@ "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", "dev": true }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", @@ -141,6 +147,262 @@ "normalize-path": "2.1.1" }, "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -161,12 +423,18 @@ "extglob": "2.0.4", "fragment-cache": "0.2.1", "kind-of": "6.0.2", - "nanomatch": "1.2.9", + "nanomatch": "1.2.13", "object.pick": "1.3.0", "regex-not": "1.0.2", "snapdragon": "0.8.2", "to-regex": "3.0.2" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -188,10 +456,13 @@ } }, "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } }, "arr-flatten": { "version": "1.1.0", @@ -245,9 +516,9 @@ "dev": true }, "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", "dev": true }, "arrify": { @@ -304,9 +575,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "aws-sign2": { @@ -375,7 +646,7 @@ "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", - "convert-source-map": "1.5.1", + "convert-source-map": "1.6.0", "debug": "2.6.9", "json5": "0.5.1", "lodash": "4.17.10", @@ -400,12 +671,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, @@ -423,14 +688,6 @@ "lodash": "4.17.10", "source-map": "0.5.7", "trim-right": "1.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "babel-helpers": { @@ -444,13 +701,13 @@ } }, "babel-jest": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.0.1.tgz", - "integrity": "sha1-u6079SP7IC2gXtCmVAtIyE7tE6Y=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", + "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", "dev": true, "requires": { "babel-plugin-istanbul": "4.1.6", - "babel-preset-jest": "23.0.1" + "babel-preset-jest": "23.2.0" } }, "babel-messages": { @@ -470,14 +727,14 @@ "requires": { "babel-plugin-syntax-object-rest-spread": "6.13.0", "find-up": "2.1.0", - "istanbul-lib-instrument": "1.10.1", - "test-exclude": "4.2.1" + "istanbul-lib-instrument": "1.10.2", + "test-exclude": "4.2.3" } }, "babel-plugin-jest-hoist": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.0.1.tgz", - "integrity": "sha1-6qEclkVjrqnCG+zvK994U/fzwUg=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", + "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", "dev": true }, "babel-plugin-syntax-object-rest-spread": { @@ -509,12 +766,12 @@ } }, "babel-preset-jest": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.0.1.tgz", - "integrity": "sha1-YxzFRcbPAhlDATvK8i9F2H/mIZg=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", + "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", "dev": true, "requires": { - "babel-plugin-jest-hoist": "23.0.1", + "babel-plugin-jest-hoist": "23.2.0", "babel-plugin-syntax-object-rest-spread": "6.13.0" } }, @@ -526,28 +783,11 @@ "requires": { "babel-core": "6.26.3", "babel-runtime": "6.26.0", - "core-js": "2.5.7", + "core-js": "2.6.4", "home-or-tmp": "2.0.0", "lodash": "4.17.10", "mkdirp": "0.5.1", "source-map-support": "0.4.18" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - } } }, "babel-runtime": { @@ -556,7 +796,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.7", + "core-js": "2.6.4", "regenerator-runtime": "0.11.1" } }, @@ -690,6 +930,12 @@ "kind-of": "6.0.2" } }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -718,9 +964,9 @@ } }, "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz", + "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", "dev": true }, "boom": { @@ -742,47 +988,37 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.3" } }, "browser-process-hrtime": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", - "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", "dev": true }, "browser-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", "dev": true, "requires": { "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } } }, "bser": { @@ -800,12 +1036,6 @@ "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", "dev": true }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -821,6 +1051,14 @@ "to-object-path": "0.3.0", "union-value": "1.0.0", "unset-value": "1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "cacheable-request": { @@ -860,11 +1098,10 @@ "dev": true }, "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true }, "capture-exit": { "version": "1.2.0", @@ -880,17 +1117,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - } - }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -951,7 +1177,7 @@ "is-binary-path": "1.0.1", "is-glob": "2.0.1", "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" + "readdirp": "2.2.1" }, "dependencies": { "anymatch": { @@ -967,9 +1193,9 @@ } }, "ci-info": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", - "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true }, "circular-json": { @@ -998,9 +1224,15 @@ "requires": { "is-descriptor": "0.1.6" } - } - } - }, + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -1017,24 +1249,14 @@ "dev": true }, "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, - "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true - } + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "clone-response": { @@ -1067,18 +1289,21 @@ } }, "color": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/color/-/color-0.8.0.tgz", - "integrity": "sha1-iQwHw/1OZJU3Y4kRz2keVFi2/KU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", "requires": { - "color-convert": "0.5.3", - "color-string": "0.3.0" + "color-convert": "1.9.3", + "color-string": "1.5.3" } }, "color-convert": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", - "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } }, "color-name": { "version": "1.1.3", @@ -1086,30 +1311,31 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", "requires": { - "color-name": "1.1.3" + "color-name": "1.1.3", + "simple-swizzle": "0.2.2" } }, "colornames": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-0.0.2.tgz", - "integrity": "sha1-2BH9bIT1kClJmorEQ2ICk1uSvjE=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" }, "colors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", - "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" }, "colorspace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.0.1.tgz", - "integrity": "sha1-yZx5btMRKLmHalLh7l7gOkpxl0k=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.1.tgz", + "integrity": "sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw==", "requires": { - "color": "0.8.0", - "text-hex": "0.0.0" + "color": "3.0.0", + "text-hex": "1.0.0" } }, "combined-stream": { @@ -1120,11 +1346,12 @@ "delayed-stream": "1.0.0" } }, - "compare-versions": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.2.1.tgz", - "integrity": "sha512-2y2nHcopMG/NAyk6vWXlLs86XeM9sik4jmx1tKIgzMi9/RQ2eo758RGpxQO3ErihHmg0RlQITPqgz73y6s7quA==", - "dev": true + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true }, "component-emitter": { "version": "1.2.1", @@ -1151,10 +1378,13 @@ } }, "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } }, "copy-descriptor": { "version": "0.1.1", @@ -1163,9 +1393,9 @@ "dev": true }, "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.4.tgz", + "integrity": "sha512-05qQ5hXShcqGkPZpXEFLIpxayZscVD2kuMBZewxiIPPEagukO4mqgPA9CWhUvFBJfy3ODdK2p9xyHh7FTU9/7A==", "dev": true }, "core-util-is": { @@ -1207,7 +1437,7 @@ "glob2base": "0.0.12", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "resolve": "1.1.7", + "resolve": "1.10.0", "safe-buffer": "5.1.2", "shell-quote": "1.6.1", "subarg": "1.0.0" @@ -1243,18 +1473,18 @@ } }, "cssom": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", "dev": true }, "cssstyle": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz", - "integrity": "sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", + "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", "dev": true, "requires": { - "cssom": "0.3.2" + "cssom": "0.3.6" } }, "dashdash": { @@ -1266,14 +1496,27 @@ } }, "data-urls": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.0.tgz", - "integrity": "sha512-ai40PPQR0Fn1lD2PPie79CibnlMN2AYiDhwFX/rZHVsxbs5kNJSjegqXIprhouGXlRdEnfybva7kqRGnB6mypA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", "dev": true, "requires": { - "abab": "1.0.4", - "whatwg-mimetype": "2.1.0", - "whatwg-url": "6.4.1" + "abab": "2.0.0", + "whatwg-mimetype": "2.3.0", + "whatwg-url": "7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" + } + } } }, "debug": { @@ -1328,13 +1571,12 @@ } }, "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" + "object-keys": "1.1.0" } }, "define-property": { @@ -1376,6 +1618,12 @@ "kind-of": "6.0.2" } }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -1428,13 +1676,13 @@ "dev": true }, "diagnostics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.0.tgz", - "integrity": "sha1-4QkJALSVI+hSe+IPCBJ1IF8q42o=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", "requires": { - "colorspace": "1.0.1", + "colorspace": "1.1.1", "enabled": "1.0.2", - "kuler": "0.0.0" + "kuler": "1.0.1" } }, "diff": { @@ -1486,45 +1734,46 @@ "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", "requires": { - "env-variable": "0.0.4" + "env-variable": "0.0.5" } }, "env-variable": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.4.tgz", - "integrity": "sha512-+jpGxSWG4vr6gVxUHOc4p+ilPnql7NzZxOZBxNldsKGjCF+97df3CbuX7XMaDa5oAVkKQj4rKp38rYdC4VcpDg==" + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "0.2.1" } }, "es-abstract": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", - "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", "dev": true, "requires": { - "es-to-primitive": "1.1.1", + "es-to-primitive": "1.2.0", "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" + "has": "1.0.3", + "is-callable": "1.1.4", + "is-regex": "1.0.4", + "object-keys": "1.1.0" } }, "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { - "is-callable": "1.1.3", + "is-callable": "1.1.4", "is-date-object": "1.0.1", - "is-symbol": "1.0.1" + "is-symbol": "1.0.2" } }, "es6-promise": { @@ -1539,9 +1788,9 @@ "dev": true }, "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", "dev": true, "requires": { "esprima": "3.1.3", @@ -1556,6 +1805,13 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true } } }, @@ -1673,12 +1929,12 @@ "dev": true }, "exec-sh": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.1.tgz", - "integrity": "sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", + "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", "dev": true, "requires": { - "merge": "1.2.0" + "merge": "1.2.1" } }, "execa": { @@ -1703,53 +1959,12 @@ "dev": true }, "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "is-posix-bracket": "0.1.1" } }, "expand-range": { @@ -1759,53 +1974,20 @@ "dev": true, "requires": { "fill-range": "2.2.4" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "3.0.0", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } } }, "expect": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.0.1.tgz", - "integrity": "sha1-mRMfL9kRVZX4zDaXQB5/BzTUX+8=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", + "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", "dev": true, "requires": { "ansi-styles": "3.2.1", - "jest-diff": "23.0.1", + "jest-diff": "23.6.0", "jest-get-type": "22.4.3", - "jest-matcher-utils": "23.0.1", - "jest-message-util": "23.0.0", - "jest-regex-util": "23.0.0" + "jest-matcher-utils": "23.6.0", + "jest-message-util": "23.4.0", + "jest-regex-util": "23.3.0" }, "dependencies": { "ansi-styles": { @@ -1814,16 +1996,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.1" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "1.1.3" + "color-convert": "1.9.3" } } } @@ -1866,74 +2039,12 @@ } }, "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "is-extglob": "1.0.0" } }, "extsprintf": { @@ -1957,6 +2068,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + }, "fb-watchman": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", @@ -2007,26 +2123,16 @@ } }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.1.1", + "repeat-element": "1.1.3", + "repeat-string": "1.6.1" } }, "find-index": { @@ -2071,12 +2177,6 @@ "for-in": "1.0.2" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -2118,7 +2218,7 @@ "requires": { "graceful-fs": "4.1.11", "jsonfile": "4.0.0", - "universalify": "0.1.1" + "universalify": "0.1.2" } }, "fs.realpath": { @@ -2669,9 +2769,9 @@ "dev": true }, "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, "get-stream": { @@ -2800,31 +2900,22 @@ "dev": true }, "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", "dev": true, "requires": { - "async": "1.5.2", + "async": "2.6.0", "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "source-map": "0.6.1", + "uglify-js": "3.4.9" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -2843,9 +2934,9 @@ } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "1.1.1" @@ -2871,6 +2962,12 @@ "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "has-to-string-tag-x": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", @@ -2888,6 +2985,14 @@ "get-value": "2.0.6", "has-values": "1.0.0", "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "has-values": { @@ -2900,10 +3005,30 @@ "kind-of": "4.0.0" }, "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { "is-buffer": "1.1.6" @@ -2938,9 +3063,9 @@ } }, "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "html-encoding-sniffer": { @@ -2949,7 +3074,7 @@ "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", "dev": true, "requires": { - "whatwg-encoding": "1.0.3" + "whatwg-encoding": "1.0.5" } }, "http-cache-semantics": { @@ -3053,7 +3178,7 @@ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { - "loose-envify": "1.3.1" + "loose-envify": "1.4.0" } }, "invert-kv": { @@ -3083,7 +3208,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.11.0" + "binary-extensions": "1.13.0" } }, "is-buffer": { @@ -3092,28 +3217,19 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "1.1.1" - } - }, "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-ci": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", - "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "1.1.3" + "ci-info": "1.6.0" } }, "is-data-descriptor": { @@ -3208,9 +3324,9 @@ } }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { "kind-of": "3.2.2" @@ -3221,23 +3337,6 @@ "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -3274,6 +3373,14 @@ "dev": true, "requires": { "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "is-posix-bracket": { @@ -3300,7 +3407,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.1" + "has": "1.0.3" } }, "is-resolvable": { @@ -3320,10 +3427,13 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "1.0.0" + } }, "is-typedarray": { "version": "1.0.0", @@ -3342,6 +3452,12 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3354,10 +3470,13 @@ "dev": true }, "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } }, "isstream": { "version": "0.1.2", @@ -3365,44 +3484,43 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-api": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.1.tgz", - "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", + "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", "dev": true, "requires": { "async": "2.6.0", - "compare-versions": "3.2.1", "fileset": "2.0.3", - "istanbul-lib-coverage": "1.2.0", - "istanbul-lib-hook": "1.2.0", - "istanbul-lib-instrument": "1.10.1", - "istanbul-lib-report": "1.1.4", - "istanbul-lib-source-maps": "1.2.4", - "istanbul-reports": "1.3.0", + "istanbul-lib-coverage": "1.2.1", + "istanbul-lib-hook": "1.2.2", + "istanbul-lib-instrument": "1.10.2", + "istanbul-lib-report": "1.1.5", + "istanbul-lib-source-maps": "1.2.6", + "istanbul-reports": "1.5.1", "js-yaml": "3.11.0", "mkdirp": "0.5.1", "once": "1.4.0" } }, "istanbul-lib-coverage": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", - "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", "dev": true }, "istanbul-lib-hook": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz", - "integrity": "sha512-p3En6/oGkFQV55Up8ZPC2oLxvgSxD8CzA0yBrhRZSh3pfv3OFj9aSGVC0yoerAi/O4u7jUVnOGVX1eVFM+0tmQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", + "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", "dev": true, "requires": { "append-transform": "0.4.0" } }, "istanbul-lib-instrument": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", - "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", "dev": true, "requires": { "babel-generator": "6.26.1", @@ -3410,19 +3528,19 @@ "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", - "istanbul-lib-coverage": "1.2.0", + "istanbul-lib-coverage": "1.2.1", "semver": "5.5.0" } }, "istanbul-lib-report": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz", - "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", "dev": true, "requires": { - "istanbul-lib-coverage": "1.2.0", + "istanbul-lib-coverage": "1.2.1", "mkdirp": "0.5.1", - "path-parse": "1.0.5", + "path-parse": "1.0.6", "supports-color": "3.2.3" }, "dependencies": { @@ -3444,33 +3562,25 @@ } }, "istanbul-lib-source-maps": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz", - "integrity": "sha512-UzuK0g1wyQijiaYQxj/CdNycFhAd2TLtO2obKQMTZrZ1jzEMRY3rvpASEKkaxbRR6brvdovfA03znPa/pXcejg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", "dev": true, "requires": { "debug": "3.1.0", - "istanbul-lib-coverage": "1.2.0", + "istanbul-lib-coverage": "1.2.1", "mkdirp": "0.5.1", "rimraf": "2.6.2", "source-map": "0.5.7" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "istanbul-reports": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", - "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", "dev": true, "requires": { - "handlebars": "4.0.11" + "handlebars": "4.1.0" } }, "isurl": { @@ -3483,19 +3593,19 @@ } }, "jest": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.0.1.tgz", - "integrity": "sha1-DQgykO5BEs7Pt4Dfb/gTMu03MgE=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", + "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", "dev": true, "requires": { "import-local": "1.0.0", - "jest-cli": "23.0.1" + "jest-cli": "23.6.0" }, "dependencies": { "jest-cli": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.0.1.tgz", - "integrity": "sha1-NRpbpRzyjs8gM22XowuXDR9TClY=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", + "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", "dev": true, "requires": { "ansi-escapes": "3.1.0", @@ -3504,118 +3614,121 @@ "glob": "7.1.2", "graceful-fs": "4.1.11", "import-local": "1.0.0", - "is-ci": "1.1.0", - "istanbul-api": "1.3.1", - "istanbul-lib-coverage": "1.2.0", - "istanbul-lib-instrument": "1.10.1", - "istanbul-lib-source-maps": "1.2.4", - "jest-changed-files": "23.0.1", - "jest-config": "23.0.1", - "jest-environment-jsdom": "23.0.1", + "is-ci": "1.2.1", + "istanbul-api": "1.3.7", + "istanbul-lib-coverage": "1.2.1", + "istanbul-lib-instrument": "1.10.2", + "istanbul-lib-source-maps": "1.2.6", + "jest-changed-files": "23.4.2", + "jest-config": "23.6.0", + "jest-environment-jsdom": "23.4.0", "jest-get-type": "22.4.3", - "jest-haste-map": "23.0.1", - "jest-message-util": "23.0.0", - "jest-regex-util": "23.0.0", - "jest-resolve-dependencies": "23.0.1", - "jest-runner": "23.0.1", - "jest-runtime": "23.0.1", - "jest-snapshot": "23.0.1", - "jest-util": "23.0.1", - "jest-validate": "23.0.1", - "jest-worker": "23.0.1", + "jest-haste-map": "23.6.0", + "jest-message-util": "23.4.0", + "jest-regex-util": "23.3.0", + "jest-resolve-dependencies": "23.6.0", + "jest-runner": "23.6.0", + "jest-runtime": "23.6.0", + "jest-snapshot": "23.6.0", + "jest-util": "23.4.0", + "jest-validate": "23.6.0", + "jest-watcher": "23.4.0", + "jest-worker": "23.2.0", "micromatch": "2.3.11", - "node-notifier": "5.2.1", - "realpath-native": "1.0.0", + "node-notifier": "5.4.0", + "prompts": "0.1.14", + "realpath-native": "1.1.0", "rimraf": "2.6.2", "slash": "1.0.0", "string-length": "2.0.0", "strip-ansi": "4.0.0", "which": "1.3.0", - "yargs": "11.0.0" + "yargs": "11.1.0" } } } }, "jest-changed-files": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.0.1.tgz", - "integrity": "sha1-95Vy0HIIROpd+EwqRI6GLCJU9gw=", + "version": "23.4.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", + "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", "dev": true, "requires": { "throat": "4.1.0" } }, "jest-config": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.0.1.tgz", - "integrity": "sha1-Z5i/8SR8ejkLEycZMwUAFYL8WPo=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", + "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", "dev": true, "requires": { "babel-core": "6.26.3", - "babel-jest": "23.0.1", + "babel-jest": "23.6.0", "chalk": "2.4.1", "glob": "7.1.2", - "jest-environment-jsdom": "23.0.1", - "jest-environment-node": "23.0.1", + "jest-environment-jsdom": "23.4.0", + "jest-environment-node": "23.4.0", "jest-get-type": "22.4.3", - "jest-jasmine2": "23.0.1", - "jest-regex-util": "23.0.0", - "jest-resolve": "23.0.1", - "jest-util": "23.0.1", - "jest-validate": "23.0.1", - "pretty-format": "23.0.1" + "jest-jasmine2": "23.6.0", + "jest-regex-util": "23.3.0", + "jest-resolve": "23.6.0", + "jest-util": "23.4.0", + "jest-validate": "23.6.0", + "micromatch": "2.3.11", + "pretty-format": "23.6.0" } }, "jest-diff": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.0.1.tgz", - "integrity": "sha1-PUkTfO4SwyCktNK0pvpugtSRoWo=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", + "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", "dev": true, "requires": { "chalk": "2.4.1", "diff": "3.5.0", "jest-get-type": "22.4.3", - "pretty-format": "23.0.1" + "pretty-format": "23.6.0" } }, "jest-docblock": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.0.1.tgz", - "integrity": "sha1-3t3RgzO+XcJBUmCgTvP86SdrVyU=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", + "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", "dev": true, "requires": { "detect-newline": "2.1.0" } }, "jest-each": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.0.1.tgz", - "integrity": "sha1-puXb9TCvxr+ddHkt3mnY23D4RwY=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", + "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", "dev": true, "requires": { "chalk": "2.4.1", - "pretty-format": "23.0.1" + "pretty-format": "23.6.0" } }, "jest-environment-jsdom": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.0.1.tgz", - "integrity": "sha1-2mieuTWNwW5XCKuyCPTrJqQ5V1w=", + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", + "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", "dev": true, "requires": { - "jest-mock": "23.0.1", - "jest-util": "23.0.1", - "jsdom": "11.11.0" + "jest-mock": "23.2.0", + "jest-util": "23.4.0", + "jsdom": "11.12.0" } }, "jest-environment-node": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.0.1.tgz", - "integrity": "sha1-Z2t0DiBfHyvnckGWnngSvoJO55U=", + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", + "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", "dev": true, "requires": { - "jest-mock": "23.0.1", - "jest-util": "23.0.1" + "jest-mock": "23.2.0", + "jest-util": "23.4.0" } }, "jest-get-type": { @@ -3625,153 +3738,173 @@ "dev": true }, "jest-haste-map": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.0.1.tgz", - "integrity": "sha1-zYkFKr/Iy6AfVgu+wJ1PNq7CXU8=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", + "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", "dev": true, "requires": { "fb-watchman": "2.0.0", "graceful-fs": "4.1.11", - "jest-docblock": "23.0.1", + "invariant": "2.2.4", + "jest-docblock": "23.2.0", "jest-serializer": "23.0.1", - "jest-worker": "23.0.1", + "jest-worker": "23.2.0", "micromatch": "2.3.11", "sane": "2.5.2" } }, "jest-jasmine2": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.0.1.tgz", - "integrity": "sha1-Fth1NW5jYIcrukhCb30x/cGwvOo=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", + "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", "dev": true, "requires": { + "babel-traverse": "6.26.0", "chalk": "2.4.1", "co": "4.6.0", - "expect": "23.0.1", + "expect": "23.6.0", "is-generator-fn": "1.0.0", - "jest-diff": "23.0.1", - "jest-each": "23.0.1", - "jest-matcher-utils": "23.0.1", - "jest-message-util": "23.0.0", - "jest-snapshot": "23.0.1", - "jest-util": "23.0.1", - "pretty-format": "23.0.1" + "jest-diff": "23.6.0", + "jest-each": "23.6.0", + "jest-matcher-utils": "23.6.0", + "jest-message-util": "23.4.0", + "jest-snapshot": "23.6.0", + "jest-util": "23.4.0", + "pretty-format": "23.6.0" } }, "jest-leak-detector": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.0.1.tgz", - "integrity": "sha1-nboHUFrDSVw50+wJrB5WRZnoYaA=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", + "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", "dev": true, "requires": { - "pretty-format": "23.0.1" + "pretty-format": "23.6.0" } }, "jest-matcher-utils": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.0.1.tgz", - "integrity": "sha1-DGwNrt+YM8Kn82I2Bp7+y0w/bl8=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", + "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", "dev": true, "requires": { "chalk": "2.4.1", "jest-get-type": "22.4.3", - "pretty-format": "23.0.1" + "pretty-format": "23.6.0" } }, "jest-message-util": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.0.0.tgz", - "integrity": "sha1-Bz89dscB98cYpLmvHrfxOHksR5Y=", + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", + "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.49", + "@babel/code-frame": "7.0.0", "chalk": "2.4.1", "micromatch": "2.3.11", "slash": "1.0.0", - "stack-utils": "1.0.1" + "stack-utils": "1.0.2" } }, "jest-mock": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.0.1.tgz", - "integrity": "sha1-FWn0d5aMZo/HKCc6F8N2d3O0Y1c=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", + "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", "dev": true }, "jest-regex-util": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.0.0.tgz", - "integrity": "sha1-3Vwf3gxG9DcTFM8Q96dRoj9Oj3Y=", + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", + "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", "dev": true }, "jest-resolve": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.0.1.tgz", - "integrity": "sha1-P4QDRisQo0wt8dR6q1V0xJNbzSQ=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", + "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", "dev": true, "requires": { - "browser-resolve": "1.11.2", + "browser-resolve": "1.11.3", "chalk": "2.4.1", - "realpath-native": "1.0.0" + "realpath-native": "1.1.0" } }, "jest-resolve-dependencies": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.0.1.tgz", - "integrity": "sha1-0BoQ3a2RUsTOzfXqwriFccS2pk0=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", + "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", "dev": true, "requires": { - "jest-regex-util": "23.0.0", - "jest-snapshot": "23.0.1" + "jest-regex-util": "23.3.0", + "jest-snapshot": "23.6.0" } }, "jest-runner": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.0.1.tgz", - "integrity": "sha1-sXauPs+eGUqkuEp/z3DRuNsjGqc=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", + "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", "dev": true, "requires": { "exit": "0.1.2", "graceful-fs": "4.1.11", - "jest-config": "23.0.1", - "jest-docblock": "23.0.1", - "jest-haste-map": "23.0.1", - "jest-jasmine2": "23.0.1", - "jest-leak-detector": "23.0.1", - "jest-message-util": "23.0.0", - "jest-runtime": "23.0.1", - "jest-util": "23.0.1", - "jest-worker": "23.0.1", - "source-map-support": "0.5.6", + "jest-config": "23.6.0", + "jest-docblock": "23.2.0", + "jest-haste-map": "23.6.0", + "jest-jasmine2": "23.6.0", + "jest-leak-detector": "23.6.0", + "jest-message-util": "23.4.0", + "jest-runtime": "23.6.0", + "jest-util": "23.4.0", + "jest-worker": "23.2.0", + "source-map-support": "0.5.10", "throat": "4.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "1.0.0", + "source-map": "0.6.1" + } + } } }, "jest-runtime": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.0.1.tgz", - "integrity": "sha1-sddl+wP7bUBDgFrycGdqaT9QTVc=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", + "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", "dev": true, "requires": { "babel-core": "6.26.3", "babel-plugin-istanbul": "4.1.6", "chalk": "2.4.1", - "convert-source-map": "1.5.1", + "convert-source-map": "1.6.0", "exit": "0.1.2", "fast-json-stable-stringify": "2.0.0", "graceful-fs": "4.1.11", - "jest-config": "23.0.1", - "jest-haste-map": "23.0.1", - "jest-message-util": "23.0.0", - "jest-regex-util": "23.0.0", - "jest-resolve": "23.0.1", - "jest-snapshot": "23.0.1", - "jest-util": "23.0.1", - "jest-validate": "23.0.1", + "jest-config": "23.6.0", + "jest-haste-map": "23.6.0", + "jest-message-util": "23.4.0", + "jest-regex-util": "23.3.0", + "jest-resolve": "23.6.0", + "jest-snapshot": "23.6.0", + "jest-util": "23.4.0", + "jest-validate": "23.6.0", "micromatch": "2.3.11", - "realpath-native": "1.0.0", + "realpath-native": "1.1.0", "slash": "1.0.0", "strip-bom": "3.0.0", - "write-file-atomic": "2.3.0", - "yargs": "11.0.0" + "write-file-atomic": "2.4.2", + "yargs": "11.1.0" }, "dependencies": { "strip-bom": { @@ -3789,31 +3922,36 @@ "dev": true }, "jest-snapshot": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.0.1.tgz", - "integrity": "sha1-ZnT6Gbnraamcq+zUFb3cQtavPn4=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", + "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", "dev": true, "requires": { + "babel-types": "6.26.0", "chalk": "2.4.1", - "jest-diff": "23.0.1", - "jest-matcher-utils": "23.0.1", + "jest-diff": "23.6.0", + "jest-matcher-utils": "23.6.0", + "jest-message-util": "23.4.0", + "jest-resolve": "23.6.0", "mkdirp": "0.5.1", "natural-compare": "1.4.0", - "pretty-format": "23.0.1" + "pretty-format": "23.6.0", + "semver": "5.5.0" } }, "jest-util": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.0.1.tgz", - "integrity": "sha1-aOpb1+2xd9MFn5eXJZ+ODazOL5k=", + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", + "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", "dev": true, "requires": { "callsites": "2.0.0", "chalk": "2.4.1", "graceful-fs": "4.1.11", - "is-ci": "1.1.0", - "jest-message-util": "23.0.0", + "is-ci": "1.2.1", + "jest-message-util": "23.4.0", "mkdirp": "0.5.1", + "slash": "1.0.0", "source-map": "0.6.1" }, "dependencies": { @@ -3822,25 +3960,42 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, "jest-validate": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.0.1.tgz", - "integrity": "sha1-zZ8BqJ0mu4hfEqhmdxXpyGWldU8=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", + "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", "dev": true, "requires": { "chalk": "2.4.1", "jest-get-type": "22.4.3", "leven": "2.1.0", - "pretty-format": "23.0.1" + "pretty-format": "23.6.0" + } + }, + "jest-watcher": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", + "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", + "dev": true, + "requires": { + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", + "string-length": "2.0.0" } }, "jest-worker": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.0.1.tgz", - "integrity": "sha1-nmSd2WP/QEYCb5HEAX8Dmmqkp7w=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", + "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", "dev": true, "requires": { "merge-stream": "1.0.1" @@ -3868,37 +4023,152 @@ "optional": true }, "jsdom": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.11.0.tgz", - "integrity": "sha512-ou1VyfjwsSuWkudGxb03FotDajxAto6USAlmMZjE2lc0jCznt7sBWkhfRBRaWwbnmDqdMSTKTLT5d9sBFkkM7A==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", "dev": true, "requires": { - "abab": "1.0.4", + "abab": "2.0.0", "acorn": "5.5.3", - "acorn-globals": "4.1.0", + "acorn-globals": "4.3.0", "array-equal": "1.0.0", - "cssom": "0.3.2", - "cssstyle": "0.3.1", - "data-urls": "1.0.0", + "cssom": "0.3.6", + "cssstyle": "1.1.1", + "data-urls": "1.1.0", "domexception": "1.0.1", - "escodegen": "1.9.1", + "escodegen": "1.11.0", "html-encoding-sniffer": "1.0.2", "left-pad": "1.3.0", - "nwsapi": "2.0.1", + "nwsapi": "2.1.0", "parse5": "4.0.0", "pn": "1.1.0", - "request": "2.86.0", + "request": "2.88.0", "request-promise-native": "1.0.5", "sax": "1.2.4", "symbol-tree": "3.2.2", "tough-cookie": "2.3.4", "w3c-hr-time": "1.0.1", "webidl-conversions": "4.0.2", - "whatwg-encoding": "1.0.3", - "whatwg-mimetype": "2.1.0", - "whatwg-url": "6.4.1", - "ws": "4.1.0", + "whatwg-encoding": "1.0.5", + "whatwg-mimetype": "2.3.0", + "whatwg-url": "6.5.0", + "ws": "5.2.2", "xml-name-validator": "3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "dev": true, + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "har-schema": "2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "1.37.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.21", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "1.1.31", + "punycode": "1.4.1" + } + } + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + } } }, "jsesc": { @@ -3987,6 +4257,12 @@ "is-buffer": "1.1.6" } }, + "kleur": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", + "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", + "dev": true + }, "kubernetes-client": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/kubernetes-client/-/kubernetes-client-5.3.0.tgz", @@ -4001,20 +4277,13 @@ } }, "kuler": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-0.0.0.tgz", - "integrity": "sha1-tmu0a5NOVQ9Z2BiEjgq7pPf1VTw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", "requires": { - "colornames": "0.0.2" + "colornames": "1.1.1" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -4157,11 +4426,12 @@ "dev": true }, "logform": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-1.7.0.tgz", - "integrity": "sha512-IyyAkQiA0I3LEar69J0bR5kg7204883jsWW4os9ypXiEsHueuPwfEClC3aSelhG+pIbMD0l23nAkz5VRXZYMWA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", "requires": { - "colors": "1.3.0", + "colors": "1.3.3", + "fast-safe-stringify": "2.0.6", "fecha": "2.3.3", "ms": "2.1.1", "triple-beam": "1.3.0" @@ -4172,16 +4442,10 @@ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { "js-tokens": "3.0.2" @@ -4226,9 +4490,9 @@ } }, "math-random": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", - "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", "dev": true }, "mem": { @@ -4241,9 +4505,9 @@ } }, "merge": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz", - "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", "dev": true }, "merge-stream": { @@ -4274,52 +4538,6 @@ "object.omit": "2.0.1", "parse-glob": "3.0.4", "regex-cache": "0.4.4" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - } } }, "mime-db": { @@ -4410,9 +4628,9 @@ "optional": true }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "4.0.0", @@ -4420,7 +4638,6 @@ "define-property": "2.0.2", "extend-shallow": "3.0.2", "fragment-cache": "0.2.1", - "is-odd": "2.0.0", "is-windows": "1.0.2", "kind-of": "6.0.2", "object.pick": "1.3.0", @@ -4429,6 +4646,18 @@ "to-regex": "3.0.2" }, "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -4477,27 +4706,28 @@ } }, "node-notifier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", - "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", "dev": true, "requires": { "growly": "1.3.0", + "is-wsl": "1.1.0", "semver": "5.5.0", "shellwords": "0.1.1", "which": "1.3.0" } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", + "hosted-git-info": "2.7.1", + "resolve": "1.10.0", "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" + "validate-npm-package-license": "3.0.4" } }, "normalize-path": { @@ -4535,9 +4765,9 @@ "dev": true }, "nwsapi": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.1.tgz", - "integrity": "sha512-xOJJb7kAAGy6UOklbaIPA0iu/27VMHfAbMUgYJlXz4qRXytIkPGM2vwfbxa+tbaqcqHNsP6RN4eDZlePelWKpQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.0.tgz", + "integrity": "sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg==", "dev": true }, "oauth-sign": { @@ -4573,9 +4803,9 @@ } }, "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", "dev": true }, "object-visit": { @@ -4585,6 +4815,14 @@ "dev": true, "requires": { "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "object.getownpropertydescriptors": { @@ -4593,8 +4831,8 @@ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" + "define-properties": "1.1.3", + "es-abstract": "1.13.0" } }, "object.omit": { @@ -4614,6 +4852,14 @@ "dev": true, "requires": { "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, "oidc-token-hash": { @@ -4732,9 +4978,9 @@ "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { "p-try": "1.0.0" @@ -4746,7 +4992,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "1.3.0" } }, "p-timeout": { @@ -4781,7 +5027,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "1.3.2" } }, "parse5": { @@ -4821,9 +5067,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-type": { @@ -4915,9 +5161,9 @@ "dev": true }, "pretty-format": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.0.1.tgz", - "integrity": "sha1-1h0GUmjkx1kIO8y8onoBrXx2AfQ=", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", "dev": true, "requires": { "ansi-regex": "3.0.0", @@ -4936,16 +5182,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.1" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "1.1.3" + "color-convert": "1.9.3" } } } @@ -4967,6 +5204,16 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, + "prompts": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", + "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", + "dev": true, + "requires": { + "kleur": "2.0.2", + "sisteransi": "0.1.1" + } + }, "promy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/promy/-/promy-0.1.0.tgz", @@ -4977,6 +5224,12 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -4998,14 +5251,14 @@ } }, "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", "dev": true, "requires": { "is-number": "4.0.0", "kind-of": "6.0.2", - "math-random": "1.0.1" + "math-random": "1.0.4" }, "dependencies": { "is-number": { @@ -5029,7 +5282,7 @@ "dev": true, "requires": { "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", "path-type": "1.1.0" } }, @@ -5079,21 +5332,311 @@ } }, "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.6", - "set-immediate-shim": "1.0.1" + "micromatch": "3.1.10", + "readable-stream": "2.3.6" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "realpath-native": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.0.0.tgz", - "integrity": "sha512-XJtlRJ9jf0E1H1SLeJyQ9PGzQD7S65h1pRXEcAeK48doKOnKxcgPeNohJvD5u/2sI9J1oke6E8bZHS/fmW1UiQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", "dev": true, "requires": { "util.promisify": "1.0.0" @@ -5137,9 +5680,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -5228,10 +5771,13 @@ } }, "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "1.0.6" + } }, "resolve-cwd": { "version": "2.0.0", @@ -5286,16 +5832,6 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, - "requires": { - "align-text": "0.1.4" - } - }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -5363,7 +5899,7 @@ "requires": { "anymatch": "2.0.0", "capture-exit": "1.2.0", - "exec-sh": "0.2.1", + "exec-sh": "0.2.2", "fb-watchman": "2.0.0", "fsevents": "1.2.4", "micromatch": "3.1.10", @@ -5372,6 +5908,262 @@ "watch": "0.18.0" }, "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -5392,7 +6184,7 @@ "extglob": "2.0.4", "fragment-cache": "0.2.1", "kind-of": "6.0.2", - "nanomatch": "1.2.9", + "nanomatch": "1.2.13", "object.pick": "1.3.0", "regex-not": "1.0.2", "snapdragon": "0.8.2", @@ -5404,6 +6196,12 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -5425,12 +6223,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -5493,6 +6285,27 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "0.3.2" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "sisteransi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", + "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", + "dev": true + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -5521,7 +6334,7 @@ "map-cache": "0.2.2", "source-map": "0.5.7", "source-map-resolve": "0.5.2", - "use": "3.1.0" + "use": "3.1.1" }, "dependencies": { "debug": { @@ -5556,12 +6369,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, @@ -5614,6 +6421,12 @@ "kind-of": "6.0.2" } }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -5648,9 +6461,9 @@ } }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "source-map-resolve": { @@ -5659,7 +6472,7 @@ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "2.1.1", + "atob": "2.1.2", "decode-uri-component": "0.2.0", "resolve-url": "0.2.1", "source-map-url": "0.4.0", @@ -5667,13 +6480,12 @@ } }, "source-map-support": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", - "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "buffer-from": "1.0.0", - "source-map": "0.6.1" + "source-map": "0.5.7" } }, "source-map-url": { @@ -5683,19 +6495,19 @@ "dev": true }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-license-ids": "3.0.3" } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { @@ -5704,14 +6516,14 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "2.2.0", + "spdx-license-ids": "3.0.3" } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, "split-string": { @@ -5749,9 +6561,9 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", "dev": true }, "static-extend": { @@ -5896,51 +6708,22 @@ } }, "test-exclude": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", - "integrity": "sha512-qpqlP/8Zl+sosLxBcVKl9vYy26T9NPalxSzzCP/OY6K7j938ui2oKgo+kRZYfxAeIpLqpbVnsHq1tyV70E4lWQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", + "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", "dev": true, "requires": { "arrify": "1.0.1", - "micromatch": "3.1.10", + "micromatch": "2.3.11", "object-assign": "4.1.1", "read-pkg-up": "1.0.1", "require-main-filename": "1.0.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - } - } } }, "text-hex": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-0.0.0.tgz", - "integrity": "sha1-V4+8haapJjbkLdF7QdAhjM6esrM=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" }, "text-table": { "version": "0.2.0", @@ -6015,6 +6798,17 @@ "requires": { "is-number": "3.0.0", "repeat-string": "1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + } } }, "tough-cookie": { @@ -6068,8 +6862,8 @@ "jest-config": "22.4.4", "lodash": "4.17.10", "pkg-dir": "2.0.0", - "source-map-support": "0.5.6", - "yargs": "11.0.0" + "source-map-support": "0.5.10", + "yargs": "11.1.0" }, "dependencies": { "ansi-regex": { @@ -6084,7 +6878,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "1.9.3" } }, "babel-plugin-jest-hoist": { @@ -6109,15 +6903,6 @@ "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, "expect": { "version": "22.4.3", "resolved": "https://registry.npmjs.org/expect/-/expect-22.4.3.tgz", @@ -6171,7 +6956,7 @@ "requires": { "jest-mock": "22.4.3", "jest-util": "22.4.3", - "jsdom": "11.11.0" + "jsdom": "11.12.0" } }, "jest-environment-node": { @@ -6200,7 +6985,7 @@ "jest-message-util": "22.4.3", "jest-snapshot": "22.4.3", "jest-util": "22.4.3", - "source-map-support": "0.5.6" + "source-map-support": "0.5.10" } }, "jest-matcher-utils": { @@ -6220,11 +7005,11 @@ "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.49", + "@babel/code-frame": "7.0.0", "chalk": "2.4.1", "micromatch": "2.3.11", "slash": "1.0.0", - "stack-utils": "1.0.1" + "stack-utils": "1.0.2" } }, "jest-mock": { @@ -6245,7 +7030,7 @@ "integrity": "sha512-u3BkD/MQBmwrOJDzDIaxpyqTxYH+XqAXzVJP51gt29H8jpj3QgKof5GGO2uPGKGeA1yTMlpbMs1gIQ6U4vcRhw==", "dev": true, "requires": { - "browser-resolve": "1.11.2", + "browser-resolve": "1.11.3", "chalk": "2.4.1" } }, @@ -6272,7 +7057,7 @@ "callsites": "2.0.0", "chalk": "2.4.1", "graceful-fs": "4.1.11", - "is-ci": "1.1.0", + "is-ci": "1.2.1", "jest-message-util": "22.4.3", "mkdirp": "0.5.1", "source-map": "0.6.1" @@ -6300,6 +7085,22 @@ "ansi-regex": "3.0.0", "ansi-styles": "3.2.1" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "1.0.0", + "source-map": "0.6.1" + } } } }, @@ -6333,9 +7134,9 @@ "dev": true }, "typescript": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz", - "integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", "dev": true }, "typescript-eslint-parser": { @@ -6349,46 +7150,25 @@ } }, "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, "optional": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "commander": "2.17.1", + "source-map": "0.6.1" }, "dependencies": { "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -6425,9 +7205,9 @@ } }, "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, "unset-value": { @@ -6467,6 +7247,29 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true } } }, @@ -6490,21 +7293,10 @@ "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "util-deprecate": { "version": "1.0.2", @@ -6517,7 +7309,7 @@ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", "dev": true, "requires": { - "define-properties": "1.1.2", + "define-properties": "1.1.3", "object.getownpropertydescriptors": "2.0.3" } }, @@ -6527,12 +7319,12 @@ "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "3.0.0", + "spdx-correct": "3.1.0", "spdx-expression-parse": "3.0.0" } }, @@ -6552,7 +7344,7 @@ "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", "dev": true, "requires": { - "browser-process-hrtime": "0.1.2" + "browser-process-hrtime": "0.1.3" } }, "walker": { @@ -6570,7 +7362,7 @@ "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", "dev": true, "requires": { - "exec-sh": "0.2.1", + "exec-sh": "0.2.2", "minimist": "1.2.0" }, "dependencies": { @@ -6589,32 +7381,35 @@ "dev": true }, "whatwg-encoding": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz", - "integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", "dev": true, "requires": { - "iconv-lite": "0.4.19" + "iconv-lite": "0.4.24" }, "dependencies": { "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } } } }, "whatwg-mimetype": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz", - "integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", "dev": true }, "whatwg-url": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.4.1.tgz", - "integrity": "sha512-FwygsxsXx27x6XXuExA/ox3Ktwcbf+OAvrKmLulotDAiO1Q6ixchPFaHYsis2zZBZSJTR0+dR+JVtf7MlbqZjw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", "dev": true, "requires": { "lodash.sortby": "4.7.0", @@ -6637,32 +7432,50 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, "winston": { - "version": "3.0.0-rc5", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.0.0-rc5.tgz", - "integrity": "sha512-BRYS7jsNkfLAqGu4dZW3kp6CmqiWKrComvfsIMYdsnpPre7g8BIw63nWRyX69vxyLYvZdszcEyxJkJmILXd/pA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", "requires": { - "async": "2.6.0", - "diagnostics": "1.1.0", + "async": "2.6.1", + "diagnostics": "1.1.1", "is-stream": "1.1.0", - "logform": "1.7.0", + "logform": "2.1.2", "one-time": "0.0.4", + "readable-stream": "3.1.1", "stack-trace": "0.0.10", "triple-beam": "1.3.0", - "winston-transport": "3.2.1" + "winston-transport": "4.3.0" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "requires": { + "lodash": "4.17.10" + } + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "requires": { + "inherits": "2.0.3", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + } } }, "winston-transport": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-3.2.1.tgz", - "integrity": "sha512-WPqbdAmMK/kfWCWKM2bA1o997wWPZ0jg5NpO8JPqoaDZgCiZnFpIEddcf7Ur4UZV2sYRUdySVBWKx0+gQQ/jrg==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "requires": { + "readable-stream": "2.3.6", + "triple-beam": "1.3.0" + } }, "wordwrap": { "version": "1.0.0", @@ -6727,9 +7540,9 @@ } }, "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", + "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", "dev": true, "requires": { "graceful-fs": "4.1.11", @@ -6738,13 +7551,12 @@ } }, "ws": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", - "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", "dev": true, "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.2" + "async-limiter": "1.0.0" } }, "xml-name-validator": { @@ -6765,15 +7577,15 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", - "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { "cliui": "4.1.0", "decamelize": "1.2.0", "find-up": "2.1.0", - "get-caller-file": "1.0.2", + "get-caller-file": "1.0.3", "os-locale": "2.1.0", "require-directory": "2.1.1", "require-main-filename": "1.0.1", @@ -6782,19 +7594,6 @@ "which-module": "2.0.0", "y18n": "3.2.1", "yargs-parser": "9.0.2" - }, - "dependencies": { - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" - } - } } }, "yargs-parser": { @@ -6804,14 +7603,6 @@ "dev": true, "requires": { "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } } } } diff --git a/package.json b/package.json index 004d75d..8395fe0 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "kube-icinga", - "version": "1.0.0", + "version": "2.0.0", "description": "Icinga2 autodiscovery service for kubernetes", "main": "main.js", "scripts": { "prebuild": "tsc", "start": "tsc && nodejs build/main.js", "build": "eslint --fix src/ --ext ts && tsc && jest --coverage", - "test": "jest --coverage", + "test": "tsc && jest --coverage", "coveralls": "coveralls < coverage/lcov.info" }, "repository": { @@ -27,10 +27,10 @@ }, "homepage": "https://github.com/raffis/kube-icinga#readme", "dependencies": { - "icinga2-api": "raffis/nodejs-icinga2api", + "icinga2-api": "github:raffis/nodejs-icinga2api", "json-stream": "^1.0.0", "kubernetes-client": "^5.3.0", - "winston": "^3.0.0-rc5" + "winston": "^3.0.0" }, "devDependencies": { "@types/jest": "^22.2.3", @@ -38,10 +38,9 @@ "coveralls": "^3.0.1", "eslint": "^4.19.1", "eslint-config-google": "^0.9.1", - "jest": "^23.0.1", - "source-map-support": "^0.5.6", + "jest": "^23.0.0", "ts-jest": "^22.4.6", - "typescript": "^2.8.3", + "typescript": "^2.9.2", "typescript-eslint-parser": "^15.0.0" }, "jest": { diff --git a/src/client/icinga.ts b/src/client/icinga.ts index 1c0782e..134e9de 100644 --- a/src/client/icinga.ts +++ b/src/client/icinga.ts @@ -1,5 +1,5 @@ import * as IcingaApi from 'icinga2-api'; import config from '../config'; -const icingaClient = new IcingaApi(config.icinga.address, config.icinga.port, config.icinga.apiUser, config.icinga.apiPassword); +const icingaClient = new IcingaApi(config.icinga.address || '127.0.0.1', config.icinga.port || 5661, config.icinga.apiUser || 'admin', config.icinga.apiPassword || 'admin'); export default icingaClient; diff --git a/src/client/kube.ts b/src/client/kube.ts index 10382b1..61947ad 100644 --- a/src/client/kube.ts +++ b/src/client/kube.ts @@ -1,12 +1,12 @@ const KubeApi = require('kubernetes-client').Client; const KubeConfig = require('kubernetes-client').config; -var config; -if(process.env.KUBERNETES_SERVICE_HOST && process.env.KUBERNETES_SERVICE_PORT) { +let config; +if (process.env.KUBERNETES_SERVICE_HOST && process.env.KUBERNETES_SERVICE_PORT) { config = KubeConfig.getInCluster(); } else { config = KubeConfig.fromKubeconfig(); } - + const kubeClient = new KubeApi({config: config, version: '1.9'}); export default kubeClient; diff --git a/src/config.ts b/src/config.ts index bd356af..41a5a94 100644 --- a/src/config.ts +++ b/src/config.ts @@ -9,58 +9,68 @@ try { const config = { log: { - level: process.env.LOG_LEVEL || defaultConfig.log.level || 'info', + level: process.env.LOG_LEVEL || defaultConfig.log.level, }, - cleanup: process.env.CLEANUP || defaultConfig.cleanup || true, + cleanup: process.env.CLEANUP || defaultConfig.cleanup, icinga: { - address: process.env.ICINGA_ADDRESS || defaultConfig.icinga.address || '127.0.0.1', - port: process.env.ICINGA_PORT || defaultConfig.icinga.port || '5661', - apiUser: process.env.ICINGA_API_USER || defaultConfig.icinga.apiUser || 'admin', - apiPassword: process.env.ICINGA_API_PASSWORD || defaultConfig.icinga.apiPassword || 'admin', + address: process.env.ICINGA_ADDRESS || defaultConfig.icinga.address, + port: process.env.ICINGA_PORT || defaultConfig.icinga.port, + apiUser: process.env.ICINGA_API_USERNAME || defaultConfig.icinga.apiUser, + apiPassword: process.env.ICINGA_API_PASSWORD || defaultConfig.icinga.apiPassword, }, kubernetes: { nodes: { - discover: process.env.KUBERNETES_NODES_DISCOVER || defaultConfig.kubernetes.nodes.discover || true, - hostDefinition: process.env.KUBERNETES_NODES_HOST_DEFINITION || defaultConfig.kubernetes.nodes.hostDefinition || {}, - hostTemplates: process.env.KUBERNETES_NODES_HOST_TEMPLATES || defaultConfig.kubernetes.nodes.hostTemplates || ['generic-host'], + discover: process.env.KUBERNETES_NODES_DISCOVER || defaultConfig.kubernetes.nodes.discover, + hostDefinition: process.env.KUBERNETES_NODES_HOST_DEFINITION || defaultConfig.kubernetes.nodes.hostDefinition, + hostTemplates: process.env.KUBERNETES_NODES_HOST_TEMPLATES || defaultConfig.kubernetes.nodes.hostTemplates, }, ingresses: { - discover: process.env.KUBERNETES_INGRESSES_DISCOVER || defaultConfig.kubernetes.ingresses.discover || true, - applyServices: process.env.KUBERNETES_INGRESSES_APPLYSERVICES || defaultConfig.kubernetes.ingresses.applyServices || true, - serviceDefinition: process.env.KUBERNETES_INGRESSES_SERVICE_DEFINITION || defaultConfig.kubernetes.ingresses.serviceDefinition || {}, - hostDefinition: process.env.KUBERNETES_INGRESSES_HOST_DEFINITION || defaultConfig.kubernetes.ingresses.hostDefinition || {}, - serviceTemplates: process.env.KUBERNETES_INGRESSES_SERVICE_TEMPLATES || defaultConfig.kubernetes.ingresses.serviceTemplates || ['generic-service'], - hostTemplates: process.env.KUBERNETES_INGRESSES_HOST_TEMPLATES || defaultConfig.kubernetes.ingresses.hostTemplates || ['generic-host'], - attachToNodes: process.env.KUBERNETES_INGRESSES_ATTACHTONODES || defaultConfig.kubernetes.ingresses.attachToNodes || false, + discover: process.env.KUBERNETES_INGRESSES_DISCOVER || defaultConfig.kubernetes.ingresses.discover, + hostName: process.env.KUBERNETES_INGRESSES_HOSTNAME || defaultConfig.kubernetes.ingresses.hostName, + applyServices: process.env.KUBERNETES_INGRESSES_APPLYSERVICES || defaultConfig.kubernetes.ingresses.applyServices, + serviceDefinition: process.env.KUBERNETES_INGRESSES_SERVICE_DEFINITION || defaultConfig.kubernetes.ingresses.serviceDefinition, + hostDefinition: process.env.KUBERNETES_INGRESSES_HOST_DEFINITION || defaultConfig.kubernetes.ingresses.hostDefinition, + serviceTemplates: process.env.KUBERNETES_INGRESSES_SERVICE_TEMPLATES || defaultConfig.kubernetes.ingresses.serviceTemplates, + hostTemplates: process.env.KUBERNETES_INGRESSES_HOST_TEMPLATES || defaultConfig.kubernetes.ingresses.hostTemplates, + attachToNodes: process.env.KUBERNETES_INGRESSES_ATTACHTONODES || defaultConfig.kubernetes.ingresses.attachToNodes, + }, + volumes: { + discover: process.env.KUBERNETES_VOLUMES_DISCOVER || defaultConfig.kubernetes.volumes.discover, + hostName: process.env.KUBERNETES_VOLUMES_HOSTNAME || defaultConfig.kubernetes.volumes.hostName, + applyServices: process.env.KUBERNETES_VOLUMES_APPLYSERVICES || defaultConfig.kubernetes.volumes.applyServices, + serviceDefinition: process.env.KUBERNETES_VOLUMES_SERVICE_DEFINITION || defaultConfig.kubernetes.volumes.serviceDefinition, + hostDefinition: process.env.KUBERNETES_VOLUMES_HOST_DEFINITION || defaultConfig.kubernetes.volumes.hostDefinition, + serviceTemplates: process.env.KUBERNETES_VOLUMES_SERVICE_TEMPLATES || defaultConfig.kubernetes.volumes.serviceTemplates, + hostTemplates: process.env.KUBERNETES_VOLUMES_HOST_TEMPLATES || defaultConfig.kubernetes.volumes.hostTemplates, + attachToNodes: process.env.KUBERNETES_VOLUMES_ATTACHTONODES || defaultConfig.kubernetes.volumes.attachToNodes, }, services: { ClusterIP: { - discover: process.env.KUBERNETES_SERVICES_CLUSTERIP_DISCOVER || defaultConfig.kubernetes.services.ClusterIP.discover || false, - applyServices: process.env.KUBERNETES_SERVICES_CLUSTERIP_APPLYSERVICES || defaultConfig.kubernetes.services.ClusterIP.applyServices || true, - serviceDefinition: process.env.KUBERNETES_SERVICES_CLUSTERIP_SERVICE_DEFINITION || defaultConfig.kubernetes.services.ClusterIP.serviceDefinition || {}, - hostDefinition: process.env.KUBERNETES_SERVICES_CLUSTERIP_HOST_DEFINITION || defaultConfig.kubernetes.services.ClusterIP.hostDefinition || {}, - serviceTemplates: process.env.KUBERNETES_SERVICES_CLUSTERIP_SERVICE_TEMPLATES || defaultConfig.kubernetes.services.ClusterIP.serviceTemplates || ['generic-service'], - hostTemplates: process.env.KUBERNETES_SERVICES_CLUSTERIP_HOST_TEMPLATES || defaultConfig.kubernetes.services.ClusterIP.hostTemplates || ['generic-host'], - portNameAsCommand: process.env.KUBERNETES_SERVICES_CLUSTERIP_PORTNAMEASCOMMAND || defaultConfig.kubernetes.services.ClusterIP.portNameAsCommand || true, + discover: process.env.KUBERNETES_SERVICES_CLUSTERIP_DISCOVER || defaultConfig.kubernetes.services.ClusterIP.discover, + hostName: process.env.KUBERNETES_SERVICES_CLUSTERIP_HOSTNAME || defaultConfig.kubernetes.services.ClusterIP.hostName, + applyServices: process.env.KUBERNETES_SERVICES_CLUSTERIP_APPLYSERVICES || defaultConfig.kubernetes.services.ClusterIP.applyServices, + serviceDefinition: process.env.KUBERNETES_SERVICES_CLUSTERIP_SERVICE_DEFINITION || defaultConfig.kubernetes.services.ClusterIP.serviceDefinition, + hostDefinition: process.env.KUBERNETES_SERVICES_CLUSTERIP_HOST_DEFINITION || defaultConfig.kubernetes.services.ClusterIP.hostDefinition, + serviceTemplates: process.env.KUBERNETES_SERVICES_CLUSTERIP_SERVICE_TEMPLATES || defaultConfig.kubernetes.services.ClusterIP.serviceTemplates, + hostTemplates: process.env.KUBERNETES_SERVICES_CLUSTERIP_HOST_TEMPLATES || defaultConfig.kubernetes.services.ClusterIP.hostTemplates, }, NodePort: { - discover: process.env.KUBERNETES_SERVICES_NODEPORT_DISCOVER || defaultConfig.kubernetes.services.NodePort.discover || true, - applyServices: process.env.KUBERNETES_SERVICES_NODEPORT_APPLYSERVICES || defaultConfig.kubernetes.services.NodePort.applyServices || true, - serviceDefinition: process.env.KUBERNETES_SERVICES_NODEPORT_SERVICE_DEFINITION || defaultConfig.kubernetes.services.NodePort.serviceDefinition || {}, - hostDefinition: process.env.KUBERNETES_SERVICES_NODEPORT_HOST_DEFINITION || defaultConfig.kubernetes.services.NodePort.hostDefinition || {}, - serviceTemplates: process.env.KUBERNETES_SERVICES_NODEPORT_SERVICE_TEMPLATES || defaultConfig.kubernetes.services.NodePort.serviceTemplates || ['generic-service'], - hostTemplates: process.env.KUBERNETES_SERVICES_NODEPORT_HOST_TEMPLATES || defaultConfig.kubernetes.services.NodePort.hostTemplates || ['generic-host'], - portNameAsCommand: process.env.KUBERNETES_SERVICES_NODEPORT_PORTNAMEASCOMMAND || defaultConfig.kubernetes.services.NodePort.portNameAsCommand || true, - attachToNodes: process.env.KUBERNETES_SERVICES_NODEPORT_ATTACHTONODES || defaultConfig.kubernetes.services.NodePort.attachToNodes || true, + discover: process.env.KUBERNETES_SERVICES_NODEPORT_DISCOVER || defaultConfig.kubernetes.services.NodePort.discover, + hostName: process.env.KUBERNETES_SERVICES_NODEPORT_HOSTNAME || defaultConfig.kubernetes.services.NodePort.hostName, + applyServices: process.env.KUBERNETES_SERVICES_NODEPORT_APPLYSERVICES || defaultConfig.kubernetes.services.NodePort.applyServices, + serviceDefinition: process.env.KUBERNETES_SERVICES_NODEPORT_SERVICE_DEFINITION || defaultConfig.kubernetes.services.NodePort.serviceDefinition, + hostDefinition: process.env.KUBERNETES_SERVICES_NODEPORT_HOST_DEFINITION || defaultConfig.kubernetes.services.NodePort.hostDefinition, + serviceTemplates: process.env.KUBERNETES_SERVICES_NODEPORT_SERVICE_TEMPLATES || defaultConfig.kubernetes.services.NodePort.serviceTemplates, + hostTemplates: process.env.KUBERNETES_SERVICES_NODEPORT_HOST_TEMPLATES || defaultConfig.kubernetes.services.NodePort.hostTemplates, }, LoadBalancer: { - discover: process.env.KUBERNETES_SERVICES_LOADBALANCER_DISCOVER || defaultConfig.kubernetes.services.LoadBalancer.discover || true, - applyServices: process.env.KUBERNETES_SERVICES_LOADBALANCER_APPLYSERVICES || defaultConfig.kubernetes.services.LoadBalancer.applyServices || true, - serviceDefinition: process.env.KUBERNETES_SERVICES_LOADBALANCER_SERVICE_DEFINITION || defaultConfig.kubernetes.services.LoadBalancer.serviceDefinition || {}, - hostDefinition: process.env.KUBERNETES_SERVICES_LOADBALANCER_HOST_DEFINITION || defaultConfig.kubernetes.services.LoadBalancer.hostDefinition || {}, - serviceTemplates: process.env.KUBERNETES_SERVICES_LOADBALANCER_SERVICE_TEMPLATES || defaultConfig.kubernetes.services.LoadBalancer.serviceTemplates || ['generic-service'], - hostTemplates: process.env.KUBERNETES_SERVICES_LOADBALANCER_HOST_TEMPLATES || defaultConfig.kubernetes.services.LoadBalancer.hostTemplates || ['generic-host'], - portNameAsCommand: process.env.KUBERNETES_SERVICES_LOADBALANCER_PORTNAMEASCOMMAND || defaultConfig.kubernetes.services.LoadBalancer.portNameAsCommand || true, + discover: process.env.KUBERNETES_SERVICES_LOADBALANCER_DISCOVER || defaultConfig.kubernetes.services.LoadBalancer.discover, + hostName: process.env.KUBERNETES_SERVICES_LOADBALANCER_HOSTNAME || defaultConfig.kubernetes.services.LoadBalancer.hostName, + applyServices: process.env.KUBERNETES_SERVICES_LOADBALANCER_APPLYSERVICES || defaultConfig.kubernetes.services.LoadBalancer.applyServices, + serviceDefinition: process.env.KUBERNETES_SERVICES_LOADBALANCER_SERVICE_DEFINITION || defaultConfig.kubernetes.services.LoadBalancer.serviceDefinition, + hostDefinition: process.env.KUBERNETES_SERVICES_LOADBALANCER_HOST_DEFINITION || defaultConfig.kubernetes.services.LoadBalancer.hostDefinition, + serviceTemplates: process.env.KUBERNETES_SERVICES_LOADBALANCER_SERVICE_TEMPLATES || defaultConfig.kubernetes.services.LoadBalancer.serviceTemplates, + hostTemplates: process.env.KUBERNETES_SERVICES_LOADBALANCER_HOST_TEMPLATES || defaultConfig.kubernetes.services.LoadBalancer.hostTemplates, }, }, }, diff --git a/src/icinga.ts b/src/icinga.ts index 4a3d2ba..bc83bc7 100644 --- a/src/icinga.ts +++ b/src/icinga.ts @@ -1,19 +1,42 @@ -import {LoggerInstance} from 'winston'; +import {Logger} from 'winston'; import IcingaClient from 'icinga2-api'; /** * icinga wrapper */ export default class Icinga { - protected logger: LoggerInstance; + protected logger: Logger; protected icingaClient: IcingaClient; + protected triggerRestart: boolean=false; /** * icinga wrapper */ - constructor(logger: LoggerInstance, icingaClient: IcingaClient) { + constructor(logger: Logger, icingaClient: IcingaClient) { this.logger = logger; this.icingaClient = icingaClient; + this.checkRestart(); + } + + /** + * Check if restarted needed + */ + protected checkRestart(): void { + setInterval(() => { + this.logger.debug(`check if icinga service restart is required (https://github.com/Icinga/icinga2/issues/6012)`); + if(this.triggerRestart === true) { + this.logger.debug(`icinga service restart required`); + this.triggerRestart = false; + + this.icingaClient.restartProcess((err, result) => { + if(err) { + this.logger.error(`trigger icinga service restart`, {error: err}); + } else { + this.logger.info(`icinga service restart triggered`); + } + }); + } + }, 30000); } /** @@ -26,7 +49,7 @@ export default class Icinga { if (err.Statuscode == '404') { resolve(false); } else { - reject(); + reject(err); } } else { resolve(true); @@ -38,7 +61,7 @@ export default class Icinga { /** * Create host group */ - public applyHostGroup(name: string): Promise { + public applyHostGroup(name: string): Promise { return new Promise((resolve, reject) => { this.logger.info(`apply host group ${name} aka kubernetes namespace`); @@ -48,19 +71,20 @@ export default class Icinga { this.logger.info(`host group ${name} on monitoring was not found, create one`, {error: err}); this.icingaClient.createHostGroup(name, name, [], (err, result) => { - resolve(); - if (err) { this.logger.error(`failed create host group ${name}`, {error: err}); + resolve(false); } else { this.logger.info(`host group ${name} was created successfully`, {result: result}); + this.triggerRestart = true; + resolve(true); } }); } else { - reject(); + reject(err); } } else { - resolve(); + resolve(true); } }); }); @@ -79,16 +103,17 @@ export default class Icinga { this.logger.info(`service group ${name} on monitoring was not found, create one`, {error: err}); this.icingaClient.createServiceGroup(name, name, [], (err, result) => { - resolve(true); - if (err) { this.logger.error(`failed create service group ${name}`, {error: err}); + resolve(false); } else { this.logger.info(`service group ${name} was created successfully`, {result: result}); + this.triggerRestart = true; + resolve(true); } }); } else { - reject(); + reject(err); } } else { resolve(true); @@ -100,7 +125,7 @@ export default class Icinga { /** * Create or update a new icinga host object */ - public applyHost(name: string, address: string, definition, templates: string[]=[]): Promise { + public applyHost(name: string, definition, templates: string[]=[]): Promise { let host = { attrs: definition, templates: templates, @@ -115,17 +140,20 @@ export default class Icinga { this.logger.info(`host ${name} on monitoring was not found, create one`, {error: err}); this.icingaClient.createHostCustom(JSON.stringify(host), name, (err, result) => { - resolve(true); - if (err) { this.logger.error(`failed create host ${name}`, {error: err}); + resolve(false); } else { this.logger.info(`host ${name} was created successfully`, {result: result}); + this.triggerRestart = true; + resolve(true); } }); } else { - reject(); + reject(err); } + } else { + resolve(true); } }); }); @@ -134,28 +162,37 @@ export default class Icinga { /** * Create or update an icinga service object */ - public applyService(host: string, name: string, definition, templates: string[]=[]) { + public applyService(host: string, name: string, definition, templates: string[]=[]): Promise { let service = { attrs: definition, templates: templates, }; - this.logger.info(`apply service ${name} to host ${host}`, {service: definition}); + return new Promise((resolve, reject) => { + this.logger.info(`apply service ${name} to host ${host}`, {service: definition}); - this.icingaClient.getService(host, name, (err, result) => { - if (err) { - if (err.Statuscode == '404') { - this.logger.info(`service ${name} on host ${host} was not found, create one`, {error: err}); + this.icingaClient.getService(host, name, (err, result) => { + if (err) { + if (err.Statuscode == '404') { + this.logger.info(`service ${name} on host ${host} was not found, create one`, {error: err}); - this.icingaClient.createServiceCustom(JSON.stringify(service), host, name, (err, result) => { - if (err) { - this.logger.error(`failed create service ${name} on host ${host}`, {error: err}); - } else { - this.logger.info(`service ${name} on host ${host} was created successfully`, {result: result}); - } - }); + this.icingaClient.createServiceCustom(JSON.stringify(service), host, name, (err, result) => { + if (err) { + this.logger.error(`failed create service ${name} on host ${host}`, {error: err}); + resolve(false); + } else { + this.logger.info(`service ${name} on host ${host} was created successfully`, {result: result}); + this.triggerRestart = true; + resolve(true); + } + }); + } else { + reject(err); + } + } else { + resolve(true); } - } + }); }); } @@ -167,11 +204,12 @@ export default class Icinga { this.logger.info(`delete service ${name} from host ${host}`); return this.icingaClient.deleteService(name, host, (err, result) => { - resolve(true); if (err) { this.logger.error(`failed delete service ${name} from host ${host}`, {error: err}); + resolve(false); } else { this.logger.info(`service ${name} was deleted successfully from host ${host}`, {result: result}); + resolve(true); } }); }); @@ -185,25 +223,28 @@ export default class Icinga { this.logger.info(`delete host ${name}`); this.icingaClient.deleteHost(name, (err, result) => { - resolve(true); if (err) { this.logger.error(`failed delete host ${name}`, {error: err}); + resolve(false); } else { this.logger.info(`host ${name} was deleted successfully`, {result: result}); + resolve(true); } }); }); } /** - * Cleanup all previously deployed icinga kubernetes objects + * Delete services by filter */ - public async cleanup(): Promise { - this.logger.info('start cleanup, removing all kubernetes objects from icinga'); - + public async deleteServicesByFilter(filter: string): Promise { return new Promise((resolve, reject) => { - this.icingaClient.getServiceFiltered({filter: 'service.vars._kubernetes == true'}, async (err, result) => { - const handlers = []; + this.icingaClient.getServiceFiltered({filter: filter}, async (err, result) => { + if (err) { + return reject(err); + } + + let handlers = []; for (const service of result) { handlers.push(this.deleteService(service.attrs.host_name, service.attrs.name)); } @@ -211,9 +252,20 @@ export default class Icinga { await Promise.all(handlers); resolve(true); }); + }); + } + + /** + * Delete hosts by filter + */ + public async deleteHostsByFilter(filter: string): Promise { + return new Promise((resolve, reject) => { + this.icingaClient.getHostFiltered({filter: filter}, async (err, result) => { + if (err) { + return reject(err); + } - this.icingaClient.getHostFiltered({filter: 'host.vars._kubernetes == true'}, async (err, result) => { - const handlers = []; + let handlers = []; for (const host of result) { handlers.push(this.deleteHost(host.attrs.name)); } diff --git a/src/kube/abstract.resource.ts b/src/kube/abstract.resource.ts new file mode 100644 index 0000000..a4cd12b --- /dev/null +++ b/src/kube/abstract.resource.ts @@ -0,0 +1,51 @@ +/** + * kubernetes hosts + */ +export default abstract class Resource { + /** + * Replace invalid icinga2 chars + */ + protected escapeName(name: string): string { + return name.replace(/\\|\//g, '-'); + } + + /** + * Prepare icinga object with kube annotations + */ + protected prepareResource(resource: any): any { + let definition: any = {}; + + if (!resource.metadata.annotations) { + return definition; + } + + let annotations: any = resource.metadata.annotations; + + if (annotations['kube-icinga/check_command']) { + definition.check_command = annotations['kube-icinga/check_command']; + } + + if (annotations['kube-icinga/definition']) { + Object.assign(definition, JSON.parse(annotations['kube-icinga/definition'])); + } + + return definition; + } + + /** + * Prepare icinga object with kube annotations + */ + protected prepareTemplates(resource: any): string[] { + if (!resource.metadata.annotations) { + return []; + } + + let annotations: any = resource.metadata.annotations; + + if (annotations['kube-icinga/templates']) { + return annotations['kube-icinga/templates'].split(','); + } + + return []; + } +} diff --git a/src/kube/ingress.ts b/src/kube/ingress.ts index a0e748d..c2786f9 100644 --- a/src/kube/ingress.ts +++ b/src/kube/ingress.ts @@ -1,19 +1,29 @@ -import {LoggerInstance} from 'winston'; +import {Logger} from 'winston'; import Icinga from '../icinga'; -import JSONStream from 'json-stream'; import KubeNode from './node'; +import Resource from './abstract.resource'; + +interface IngressOptions { + discover?: boolean; + hostName?: string; + applyServices?: boolean; + attachToNodes?: boolean; + hostDefinition?: object; + serviceDefinition?: object; + hostTemplates?: string[]; + serviceTemplates?: string[]; +} /** * kubernetes ingresses */ -export default class Ingress { - protected logger: LoggerInstance; - protected kubeClient; +export default class Ingress extends Resource { + protected logger: Logger; protected icinga: Icinga; - protected jsonStream: JSONStream; protected kubeNode: KubeNode; protected options = { applyServices: true, + hostName: 'kubernetes-ingresses', attachToNodes: false, hostDefinition: {}, serviceDefinition: {}, @@ -22,21 +32,20 @@ export default class Ingress { }; /** - * kubernetes ingresses + * kubernetes hosts */ - constructor(logger: LoggerInstance, kubeNode: KubeNode, kubeClient, icinga: Icinga, jsonStream: JSONStream, options: object={}) { + constructor(logger: Logger, kubeNode: KubeNode, icinga: Icinga, options: IngressOptions) { + super(); this.logger = logger; - this.kubeClient = kubeClient; this.icinga = icinga; - this.jsonStream = jsonStream; - this.options = Object.assign(this.options, options); this.kubeNode = kubeNode; + this.options = Object.assign(this.options, options); } /** * Apply host */ - protected async applyHost(name: string, address: string, metadata, templates: string[]) { + protected async applyHost(name: string, address: string, metadata, templates: string[]): Promise { let definition = { 'display_name': name, 'address': address, @@ -47,21 +56,21 @@ export default class Ingress { }; Object.assign(definition, this.options.hostDefinition); - return this.icinga.applyHost(name, address, definition, this.options.hostTemplates); + return this.icinga.applyHost(name, definition, this.options.hostTemplates); } /** * Apply service */ - protected async applyService(host: string, name: string, definition) { + protected async applyService(host: string, name: string, definition, templates: string[]) { if (this.options.attachToNodes) { for (const node of this.kubeNode.getWorkerNodes()) { definition.host_name = node; - this.icinga.applyService(node, name, definition, this.options.serviceTemplates); + this.icinga.applyService(node, name, definition, templates); } } else { definition.host_name = host; - this.icinga.applyService(host, name, definition, this.options.serviceTemplates); + this.icinga.applyService(host, name, definition, templates); } } @@ -69,9 +78,14 @@ export default class Ingress { * Preapre icinga object and apply */ public async prepareObject(definition: any): Promise { + let hostname = this.getHostname(definition); + if (!this.options.attachToNodes) { - await this.applyHost(definition.metadata.name, definition.metadata.name, definition, this.options.hostTemplates); + await this.applyHost(hostname, hostname, definition, this.options.hostTemplates); } + let service = this.prepareResource(definition); + let templates = this.options.serviceTemplates; + templates = templates.concat(this.prepareTemplates(definition)); if (this.options.applyServices) { await this.icinga.applyServiceGroup(definition.metadata.namespace); @@ -79,10 +93,12 @@ export default class Ingress { for (const spec of definition.spec.rules) { for (const path of spec.http.paths) { let base = path.path || '/'; - let service = { + let name = this.escapeName([spec.host, 'http', base].join('-')); + let addition = { 'check_command': 'http', - 'display_name': `${spec.host}:http`, + 'display_name': `${spec.host}${base}:http`, 'vars._kubernetes': true, + 'vars._kubernetes_uid': definition.metadata.uid, 'vars.kubernetes': definition, 'vars.http_address': spec.host, 'vars.http_vhost': spec.host, @@ -91,46 +107,76 @@ export default class Ingress { 'groups': [definition.metadata.namespace], }; - Object.assign(service, this.options.serviceDefinition); - this.applyService(definition.metadata.name, service.display_name, service); + Object.assign(addition, this.options.serviceDefinition); + Object.assign(addition, service); + this.applyService(hostname, name, addition, templates); // tls secret set, also apply https service if (definition.spec.tls) { - service.display_name += 's'; - service['vars.http_ssl'] = true; - this.applyService(definition.metadata.name, service.display_name, service); + name = this.escapeName([spec.host, 'https', base].join('-')); + addition.display_name += 's'; + addition['vars.http_ssl'] = true; + this.applyService(hostname, name, addition, templates); } } } } } + /** + * Get hostname + */ + protected getHostname(definition: any): string { + if (definition.metadata.annotations['kube-icinga/host']) { + return definition.metadata.annotations['kube-icinga/host']; + } else if (this.options.hostName === null) { + return this.escapeName(['ingress', definition.metadata.namespace, definition.metadata.name].join('-')); + } + + return this.options.hostName; + } + + /** + * Delete object + */ + protected deleteObject(definition: any): Promise { + if (this.options.hostName === null) { + let hostname = this.getHostname(definition); + return this.icinga.deleteHost(hostname); + } + + return this.icinga.deleteServicesByFilter('service.vars.kubernetes.metadata.uid=="'+definition.metadata.uid+'"'); + } + /** * Start kube listener */ - public async kubeListener(): Promise { + public async kubeListener(provider) { try { - const stream = this.kubeClient.apis.extensions.v1beta1.watch.ingresses.getStream(); - stream.pipe(this.jsonStream); - this.jsonStream.on('data', async (object) => { - this.logger.debug('received kubernetes ingress', {object}); + let stream = provider(); + stream.on('data', async (object) => { + this.logger.debug('received kubernetes ingress resource', {object}); - if(object.object.kind !== 'Ingress') { + if (object.object.kind !== 'Ingress') { this.logger.error('skip invalid ingress object', {object: object}); return; } if (object.type == 'MODIFIED' || object.type == 'DELETED') { - await this.icinga.deleteHost(object.object.metadata.name); + await this.deleteObject(object.object).catch((err) => { + this.logger.error('failed to remove objects', {error: err}); + }); } if (object.type == 'ADDED' || object.type == 'MODIFIED') { - this.prepareObject(object.object); + this.prepareObject(object.object).catch((err) => { + this.logger.error('failed to handle resource', {error: err}); + }); } }); - this.jsonStream.on('finish', () => { - this.kubeListener(); + stream.on('finish', () => { + this.kubeListener(provider); }); } catch (err) { this.logger.error('failed start ingresses listener', {error: err}); diff --git a/src/kube/node.ts b/src/kube/node.ts index a5891ca..ed7c759 100644 --- a/src/kube/node.ts +++ b/src/kube/node.ts @@ -1,18 +1,22 @@ -import {LoggerInstance} from 'winston'; +import {Logger} from 'winston'; import Icinga from '../icinga'; -import JSONStream from 'json-stream'; +import Resource from './abstract.resource'; + +interface NodeOptions { + discover?: boolean; + hostDefinition?: object; + hostTemplates?: string[]; +} /** * kubernetes hosts */ -export default class Node { - protected logger: LoggerInstance; - protected kubeClient; +export default class Node extends Resource { + protected logger: Logger; protected icinga: Icinga; - protected jsonStream: JSONStream; protected nodes: string[] = []; - protected options = { - discovery: true, + protected options: NodeOptions = { + discover: true, hostDefinition: {}, hostTemplates: [], }; @@ -20,24 +24,23 @@ export default class Node { /** * kubernetes hosts */ - constructor(logger: LoggerInstance, kubeClient, icinga: Icinga, jsonStream: JSONStream, options) { + constructor(logger: Logger, icinga: Icinga, options: NodeOptions) { + super(); this.logger = logger; - this.kubeClient = kubeClient; this.icinga = icinga; - this.jsonStream = jsonStream; this.options = Object.assign(this.options, options); } /** * Preapre icinga object and apply */ - protected async prepareObject(definition: any): Promise { + public async prepareObject(definition: any): Promise { let host = { 'display_name': definition.metadata.name, - 'host_name': definition.metadata.name, + 'address': definition.metadata.name, 'vars._kubernetes': true, 'vars.kubernetes': definition, - 'groups': [definition.metadata.namespace], + 'check_command': 'ping', }; if (!definition.spec.unschedulable) { @@ -45,8 +48,8 @@ export default class Node { this.nodes.push(definition.metadata.name); } - host = Object.assign(host, this.options.hostDefinition); - this.icinga.applyHost(host.host_name, host.host_name, host, this.options.hostTemplates); + Object.assign(host, this.options.hostDefinition); + return this.icinga.applyHost(definition.metadata.name, host, this.options.hostTemplates); } /** @@ -59,18 +62,17 @@ export default class Node { /** * Start kube listener */ - public async kubeListener(): Promise { + public async kubeListener(provider) { try { - const stream = this.kubeClient.apis.v1.watch.nodes.getStream(); - stream.pipe(this.jsonStream); - this.jsonStream.on('data', async (object) => { + let stream = provider(); + stream.on('data', async (object) => { // ignore MODIFIER for kube nodes if (object.type === 'MODIFIED') { return; } - this.logger.debug('received kubernetes host', {object}); - if(object.object.kind !== 'Node') { + this.logger.debug('received kubernetes host resource', {object}); + if (object.object.kind !== 'Node') { this.logger.error('skip invalid node object', {object: object}); return; } @@ -80,12 +82,14 @@ export default class Node { } if (object.type == 'ADDED') { - this.prepareObject(object.object); + this.prepareObject(object.object).catch((err) => { + this.logger.error('failed to handle resource', {error: err}); + }); } }); - this.jsonStream.on('finish', () => { - this.kubeListener(); + stream.on('finish', () => { + this.kubeListener(provider); }); } catch (err) { this.logger.error('failed start nodes listener', {error: err}); diff --git a/src/kube/service.ts b/src/kube/service.ts index 555d4f1..f18c765 100644 --- a/src/kube/service.ts +++ b/src/kube/service.ts @@ -1,64 +1,86 @@ -import {LoggerInstance} from 'winston'; +import {Logger} from 'winston'; import Icinga from '../icinga'; -import JSONStream from 'json-stream'; import KubeNode from './node'; +import Resource from './abstract.resource'; + +interface ServiceTypeOptions { + discover?: boolean; + hostName?: string; + applyServices?: boolean; + hostDefinition?: any; + serviceDefinition?: any; + hostTemplates?: string[]; + serviceTemplates?: string[]; +} + +interface ServiceOptions { + ClusterIP?: ServiceTypeOptions; + NodePort?: ServiceTypeOptions; + LoadBalancer?: ServiceTypeOptions; +} + +const defaults: ServiceOptions = { + ClusterIP: { + discover: false, + hostName: 'kubernetes-clusterip-services', + applyServices: true, + hostDefinition: {}, + serviceDefinition: {}, + hostTemplates: [], + serviceTemplates: [], + }, + NodePort: { + discover: true, + hostName: 'kubernetes-nodeport-services', + applyServices: true, + hostDefinition: {}, + serviceDefinition: {}, + hostTemplates: [], + serviceTemplates: [], + }, + LoadBalancer: { + discover: true, + hostName: 'kubernetes-loadbalancer-services', + applyServices: true, + hostDefinition: {}, + serviceDefinition: {}, + hostTemplates: [], + serviceTemplates: [], + }, +}; /** * kubernetes services */ -export default class Service { +export default class Service extends Resource { static readonly TYPE_CLUSTERIP = 'ClusterIP'; static readonly TYPE_NODEPORT = 'NodePort'; static readonly TYPE_LOADBALANCER = 'LoadBalancer'; - protected logger: LoggerInstance; - protected kubeClient; + protected logger: Logger; protected icinga: Icinga; - protected jsonStream: JSONStream; protected kubeNode: KubeNode; - protected options = { - ClusterIP: { - discovery: false, - applyServices: true, - hostDefinition: {}, - serviceDefinition: {}, - hostTemplates: [], - serviceTemplates: [], - }, - NodePort: { - discovery: true, - applyServices: true, - hostDefinition: {}, - serviceDefinition: {}, - hostTemplates: [], - serviceTemplates: [], - }, - LoadBalancer: { - discovery: true, - applyServices: true, - hostDefinition: {}, - serviceDefinition: {}, - hostTemplates: [], - serviceTemplates: [], - }, - }; + protected options: ServiceOptions = defaults; /** * kubernetes services */ - constructor(logger: LoggerInstance, kubeNode: KubeNode, kubeClient, icinga: Icinga, jsonStream: JSONStream, options: object={}) { + constructor(logger: Logger, kubeNode: KubeNode, icinga: Icinga, options: ServiceOptions=defaults) { + super(); this.logger = logger; - this.kubeClient = kubeClient; this.icinga = icinga; - this.jsonStream = jsonStream; - this.options = Object.assign(this.options, options); + let clone = JSON.parse(JSON.stringify(defaults)); + Object.assign(clone.ClusterIP, options.ClusterIP); + Object.assign(clone.NodePort, options.NodePort); + Object.assign(clone.LoadBalancer, options.LoadBalancer); + this.options = clone; this.kubeNode = kubeNode; } /** * Apply host */ - protected async applyHost(name: string, address: string, type: string, metadata, templates: string[]) { + protected async applyHost(name: string, address: string, type: string, metadata, templates: string[]): Promise { let definition = { 'display_name': name, 'address': address, @@ -69,7 +91,7 @@ export default class Service { }; Object.assign(definition, this.options[type].hostDefinition); - return this.icinga.applyHost(name, address, definition, templates); + return this.icinga.applyHost(name, definition, templates); } /** @@ -78,7 +100,6 @@ export default class Service { protected async applyService(host: string, name: string, type: string, definition, templates: string[]) { if (type === Service.TYPE_NODEPORT) { for (const node of this.kubeNode.getWorkerNodes()) { - definition.host_name = node; this.icinga.applyService(node, name, definition, templates); } } else { @@ -92,88 +113,132 @@ export default class Service { */ public async prepareObject(definition): Promise { let serviceType = definition.spec.type; + + if (!this.options[serviceType]) { + throw new Error('unknown service type provided'); + } + let options = this.options[serviceType]; + let service = JSON.parse(JSON.stringify(options.serviceDefinition)); + service['groups'] = [definition.metadata.namespace]; + Object.assign(service, this.prepareResource(definition)); + + let hostname = this.getHostname(definition); + let templates = options.serviceTemplates; + templates = templates.concat(this.prepareTemplates(definition)); if (serviceType !== Service.TYPE_NODEPORT) { - await this.applyHost(definition.metadata.name, definition.spec.clusterIP, serviceType, definition, options.hostTemplates); + let address = options.hostName || definition.spec.clusterIP; + await this.applyHost(hostname, address, serviceType, definition, options.hostTemplates); } if (options.applyServices) { await this.icinga.applyServiceGroup(definition.metadata.namespace); for (const servicePort of definition.spec.ports) { - let service; - - if (servicePort.name && options.portNameAsCommand) { - let name = servicePort.name.toLowerCase(); - let hasCommand = await this.icinga.hasCheckCommand(name); - + let port = JSON.parse(JSON.stringify(service)); + if (port.check_command) { + let hasCommand = await this.icinga.hasCheckCommand(port.check_command); if (hasCommand) { - this.logger.debug('service can be checked via check command '+name); - service = { - 'check_command': name, - 'display_name': name, - 'vars._kubernetes': true, - 'vars.kubernetes': definition, - 'groups': [definition.metadata.namespace], - }; - service['vars.'+servicePort.name+'_port'] = servicePort.nodePort || servicePort.port; + this.logger.debug('service can be checked via check command '+port.check_command); + + if (serviceType !== Service.TYPE_NODEPORT) { + port['vars.'+port.check_command+'_address'] = definition.spec.clusterIP; + } + + port['vars.'+port.check_command+'_port'] = servicePort.nodePort || servicePort.port; } else { - this.logger.warn('service can not be checked via check command '+servicePort.name+', icinga check command does not exists, fallback to '+servicePort.protocol); + delete port.check_command; + this.logger.warn('service can not be checked via check command '+port.check_command+', icinga check command does not exists, fallback to service protocol '+servicePort.protocol); } } - if (!service) { - let protocol = servicePort.protocol.toLowerCase(); - let name = servicePort.name || protocol+':'+servicePort.port; - service = { - 'check_command': servicePort.protocol, - 'display_name': name.toLowerCase(), - 'vars._kubernetes': true, - 'vars.kubernetes': definition, - 'groups': [definition.metadata.namespace], - }; - - service['vars.'+protocol+'_port'] = servicePort.nodePort || servicePort.port; + let protocol = servicePort.protocol.toLowerCase(); + let portName = servicePort.name || protocol+':'+servicePort.port; + + if (!port.check_command) { + port.check_command = protocol; + + if (serviceType !== Service.TYPE_NODEPORT) { + port['vars.'+protocol+'_address'] = definition.spec.clusterIP; + } + + port['vars.'+protocol+'_port'] = servicePort.nodePort || servicePort.port; } - Object.assign(service, options.serviceDefinition); - this.applyService(definition.metadata.name, service.display_name, serviceType, service, options.serviceTemplates); + port['vars._kubernetes'] = true; + port['vars.kubernetes'] = definition; + let name = this.escapeName([definition.metadata.namespace, definition.metadata.name, portName].join('-')); + port['display_name'] = name; + + this.applyService(hostname, name, serviceType, port, templates); } } } + /** + * Get hostname + */ + protected getHostname(definition: any): string { + let serviceType = definition.spec.type; + + if (definition.metadata.annotations['kube-icinga/host']) { + return definition.metadata.annotations['kube-icinga/host']; + } else if (this.options[serviceType].hostName === null) { + return this.escapeName(['service', definition.metadata.namespace, definition.metadata.name].join('-')); + } + + return this.options[serviceType].hostName; + } + + /** + * Delete object + */ + protected deleteObject(definition: any): Promise { + let serviceType = definition.spec.type; + + if (this.options[serviceType].hostName === null) { + let hostname = this.getHostname(definition); + return this.icinga.deleteHost(hostname); + } + + return this.icinga.deleteServicesByFilter('service.vars.kubernetes.metadata.uid=="'+definition.metadata.uid+'"'); + } + /** * Start kube listener */ - public async kubeListener(): Promise { + public async kubeListener(provider) { try { - const stream = this.kubeClient.apis.v1.watch.services.getStream(); - stream.pipe(this.jsonStream); - this.jsonStream.on('data', async (object) => { - this.logger.debug('received kubernetes service', {object}); + let stream = provider(); + stream.on('data', async (object) => { + this.logger.debug('received kubernetes service resource', {object}); - if(object.object.kind !== 'Service') { + if (object.object.kind !== 'Service') { this.logger.error('skip invalid service object', {object: object}); return; } if (!this.options[object.object.spec.type].discover) { - this.logger.debug('skip service object, since ['+object.object.spec.type+'] is not enabled for discovery', {object: object}); + this.logger.debug('skip service object, since ['+object.object.spec.type+'] is not enabled for discover', {object: object}); return; } if (object.type == 'MODIFIED' || object.type == 'DELETED') { - await this.icinga.deleteHost(object.object.metadata.name); + await this.deleteObject(object.object).catch((err) => { + this.logger.error('failed to remove objects', {error: err}); + }); } if (object.type == 'ADDED' || object.type == 'MODIFIED') { - this.prepareObject(object.object); + this.prepareObject(object.object).catch((err) => { + this.logger.error('failed to handle resource', {error: err}); + }); } }); - this.jsonStream.on('finish', () => { - this.kubeListener(); + stream.on('finish', () => { + this.kubeListener(provider); }); } catch (err) { this.logger.error('failed start services listener', {error: err}); diff --git a/src/kube/volume.ts b/src/kube/volume.ts new file mode 100644 index 0000000..fb500d4 --- /dev/null +++ b/src/kube/volume.ts @@ -0,0 +1,173 @@ +import {Logger} from 'winston'; +import Icinga from '../icinga'; +import KubeNode from './node'; +import Resource from './abstract.resource'; + +interface VolumeOptions { + discover?: boolean; + applyServices?: boolean; + attachToNodes?: boolean; + hostName?: string; + hostDefinition?: object; + serviceDefinition?: object; + hostTemplates?: string[]; + serviceTemplates?: string[]; +} + +/** + * kubernetes ingresses + */ +export default class Volume extends Resource { + protected logger: Logger; + protected icinga: Icinga; + protected kubeNode: KubeNode; + protected options = { + applyServices: true, + attachToNodes: false, + hostName: 'kubernetes-volumes', + hostDefinition: {}, + serviceDefinition: {}, + hostTemplates: [], + serviceTemplates: [], + }; + + /** + * kubernetes hosts + */ + constructor(logger: Logger, kubeNode: KubeNode, icinga: Icinga, options: VolumeOptions) { + super(); + this.logger = logger; + this.icinga = icinga; + this.kubeNode = kubeNode; + this.options = Object.assign(this.options, options); + } + + /** + * Apply host + */ + protected async applyHost(name: string, address: string, metadata, templates: string[]): Promise { + let definition = { + 'display_name': name, + 'address': address, + 'check_command': 'dummy', + 'vars.dummy_state': 0, + 'vars._kubernetes': true, + 'vars.kubernetes': metadata, + }; + + Object.assign(definition, this.options.hostDefinition); + return this.icinga.applyHost(name, definition, this.options.hostTemplates); + } + + /** + * Apply service + */ + protected async applyService(host: string, name: string, definition, templates: string[]) { + if (this.options.attachToNodes) { + for (const node of this.kubeNode.getWorkerNodes()) { + definition.host_name = node; + this.icinga.applyService(node, name, definition, templates); + } + } else { + definition.host_name = host; + this.icinga.applyService(host, name, definition, templates); + } + } + + /** + * Preapre icinga object and apply + */ + public async prepareObject(definition: any): Promise { + let hostname = this.getHostname(definition); + + if (!this.options.attachToNodes) { + await this.applyHost(hostname, hostname, definition, this.options.hostTemplates); + } + + if (this.options.applyServices) { + let groups = []; + + if (definition.spec.claimRef.namespace) { + groups.push(definition.spec.claimRef.namespace); + await this.icinga.applyServiceGroup(definition.spec.claimRef.namespace); + } + + let templates = this.options.serviceTemplates; + templates = templates.concat(this.prepareTemplates(definition)); + + let service = this.options.serviceDefinition; + let name = this.escapeName(definition.metadata.name); + let addition = { + 'check_command': 'dummy', + 'display_name': `${definition.metadata.name}:volume`, + 'vars._kubernetes': true, + 'vars.kubernetes': definition, + 'groups': groups, + }; + + Object.assign(addition, service); + Object.assign(addition, this.prepareResource(definition)); + this.applyService(hostname, name, addition, templates); + } + } + + /** + * Get hostname + */ + protected getHostname(definition: any): string { + if (definition.metadata.annotations['kube-icinga/host']) { + return definition.metadata.annotations['kube-icinga/host']; + } else if (this.options.hostName === null) { + return this.escapeName(['volume', definition.metadata.name].join('-')); + } + + return this.options.hostName; + } + + /** + * Delete object + */ + protected deleteObject(definition: any): Promise { + if (this.options.hostName === null) { + let hostname = this.getHostname(definition); + return this.icinga.deleteHost(hostname); + } + + return this.icinga.deleteServicesByFilter('service.vars.kubernetes.metadata.uid=="'+definition.metadata.uid+'"'); + } + + /** + * Start kube listener + */ + public async kubeListener(provider) { + try { + let stream = provider(); + stream.on('data', async (object) => { + this.logger.debug('received kubernetes persistent volume resource', {object}); + + if (object.object.kind !== 'PersistentVolume') { + this.logger.error('skip invalid object', {object: object}); + return; + } + + if (object.type == 'MODIFIED' || object.type == 'DELETED') { + await this.deleteObject(object.object).catch((err) => { + this.logger.error('failed to remove objects', {error: err}); + }); + } + + if (object.type == 'ADDED' || object.type == 'MODIFIED') { + this.prepareObject(object.object).catch((err) => { + this.logger.error('failed to handle resource', {error: err}); + }); + } + }); + + stream.on('finish', () => { + this.kubeListener(provider); + }); + } catch (err) { + this.logger.error('failed start ingresses listener', {error: err}); + } + } +} diff --git a/src/logger.ts b/src/logger.ts index 4728c8a..2e054b7 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,11 +1,16 @@ -import * as winston from 'winston'; +import {createLogger, format, transports} from 'winston'; import config from './config'; -const logger = winston.createLogger({ - level: config.log.level, - format: winston.format.simple(), +const logger = createLogger({ + level: config.log.level || 'info', + format: format.combine( + format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + }), + format.json() + ), transports: [ - new winston.transports.Console(), + new transports.Console(), ], }); diff --git a/src/main.ts b/src/main.ts index c11944f..bfcb3fb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,33 +6,65 @@ import config from './config'; import Node from './kube/node'; import Service from './kube/service'; import Ingress from './kube/ingress'; +import Volume from './kube/volume'; import * as JSONStream from 'json-stream'; const icinga = new IcingaWrapper(logger, icingaClient); -const kubeNode = new Node(logger, kubeClient, icinga, new JSONStream(), config.kubernetes.nodes); -const kubeIngress = new Ingress(logger, kubeNode, kubeClient, icinga, new JSONStream(), config.kubernetes.ingresses); -const kubeService = new Service(logger, kubeNode, kubeClient, icinga, new JSONStream(), config.kubernetes.services); +const kubeNode = new Node(logger, icinga, config.kubernetes.nodes); +const kubeIngress = new Ingress(logger, kubeNode, icinga, config.kubernetes.ingresses); +const kubeService = new Service(logger, kubeNode, icinga, config.kubernetes.services); +const kubeVolume = new Volume(logger, kubeNode, icinga, config.kubernetes.volumes); /** * Main */ async function main() { if (config.cleanup) { - await icinga.cleanup(); - } + await icinga.deleteServicesByFilter('service.vars._kubernetes == true').catch((err) => { + logger.error('failed to cleanup icinga services', {error: err}); + }); + await icinga.deleteHostsByFilter('host.vars._kubernetes == true').catch((err) => { + logger.error('failed to cleanup icinga hosts', {error: err}); + }); + } + if (config.kubernetes.nodes.discover) { - kubeNode.kubeListener(); + kubeNode.kubeListener(function() { + let json = new JSONStream(); + let stream = kubeClient.apis.v1.watch.nodes.getStream(); + stream.pipe(json); + return json; + }); } if (config.kubernetes.ingresses.discover) { - kubeIngress.kubeListener(); + kubeIngress.kubeListener(() => { + let json = new JSONStream(); + let stream = kubeClient.apis.extensions.v1beta1.watch.ingresses.getStream(); + stream.pipe(json); + return json; + }); + } + + if (config.kubernetes.volumes.discover) { + kubeVolume.kubeListener(() => { + let json = new JSONStream(); + let stream = kubeClient.apis.v1.watch.persistentvolumes.getStream(); + stream.pipe(json); + return json; + }); } if (config.kubernetes.services.ClusterIP.discover || config.kubernetes.services.NodePort.discover || config.kubernetes.services.LoadBalancer.discover) { - kubeService.kubeListener(); + kubeService.kubeListener(() => { + let json = new JSONStream(); + let stream = kubeClient.apis.v1.watch.services.getStream(); + stream.pipe(json); + return json; + }); } } diff --git a/tests/icinga.test.ts b/tests/icinga.test.ts new file mode 100644 index 0000000..35daf47 --- /dev/null +++ b/tests/icinga.test.ts @@ -0,0 +1,441 @@ +import * as IcingaApi from 'icinga2-api'; +import IcingaClient from '../src/icinga'; +import Logger from '../src/logger'; +jest.mock('icinga2-api'); +jest.mock('../src/logger'); +var icinga; + +beforeEach(() => { + icinga = new IcingaClient(Logger, IcingaApi); +}); + +describe('icinga', () => { + describe('check_command', () => { + it('icinga check_command does not exists', async () => { + IcingaApi.getCheckCommand = jest.fn() + .mockImplementation((command, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.hasCheckCommand('foobar')).resolves.toEqual(false); + const calls = IcingaApi.getCheckCommand.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + }); + + it('icinga check_command does exists', async () => { + IcingaApi.getCheckCommand = jest.fn() + .mockImplementation((command, cb) => cb(null, {foo: "bar"})); + + await expect(icinga.hasCheckCommand('foobar')).resolves.toEqual(true); + const calls = IcingaApi.getCheckCommand.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + }); + + it('icinga check_command error response', async () => { + IcingaApi.getCheckCommand = jest.fn() + .mockImplementation((command, cb) => cb({Statuscode: 500}, null)); + + var result = await expect(icinga.hasCheckCommand('foobar')).rejects.toEqual({ + Statuscode: 500 + }); + + const calls = IcingaApi.getCheckCommand.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + }); + + describe('apply host group', () => { + it('icinga host group does exists', async () => { + IcingaApi.createHostGroup = jest.fn(); + IcingaApi.getHostGroup = jest.fn() + .mockImplementation((hostgroup, cb) => cb(null, {foo: "bar"})); + + await expect(icinga.applyHostGroup('foobar')).resolves.toEqual(true); + const calls = IcingaApi.getHostGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createHostGroup.mock.calls.length).toBe(0); + }); + + it('get host group error response', async () => { + IcingaApi.createHostGroup = jest.fn(); + IcingaApi.getHostGroup = jest.fn() + .mockImplementation((hostgroup, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.applyHostGroup('foobar')).rejects.toEqual({ + Statuscode: 500 + }); + const calls = IcingaApi.getHostGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createHostGroup.mock.calls.length).toBe(0); + }); + + it('add new host group', async () => { + IcingaApi.createHostGroup = jest.fn() + .mockImplementation((hostgroup, display_name, data, cb) => cb(null, {foo: "bar"})); + + IcingaApi.getHostGroup = jest.fn() + .mockImplementation((hostgroup, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyHostGroup('foobar')).resolves.toEqual(true); + var calls = IcingaApi.getHostGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createHostGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + }); + + it('add new host group error response', async () => { + IcingaApi.createHostGroup = jest.fn() + .mockImplementation((hostgroup, display_name, data, cb) => cb({Statuscode: 500}, null)); + + IcingaApi.getHostGroup = jest.fn() + .mockImplementation((hostgroup, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyHostGroup('foobar')).resolves.toEqual(false); + + var calls = IcingaApi.getHostGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createHostGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + }); + }); + + describe('apply service group', () => { + it('icinga service group does exists', async () => { + IcingaApi.createServiceGroup = jest.fn(); + IcingaApi.getServiceGroup = jest.fn() + .mockImplementation((ServiceGroup, cb) => cb(null, {foo: "bar"})); + + await expect(icinga.applyServiceGroup('foobar')).resolves.toEqual(true); + const calls = IcingaApi.getServiceGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createServiceGroup.mock.calls.length).toBe(0); + }); + + it('get service group error response', async () => { + IcingaApi.createServiceGroup = jest.fn(); + IcingaApi.getServiceGroup = jest.fn() + .mockImplementation((ServiceGroup, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.applyServiceGroup('foobar')).rejects.toEqual({ + Statuscode: 500 + }); + const calls = IcingaApi.getServiceGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createServiceGroup.mock.calls.length).toBe(0); + }); + + it('add new service group', async () => { + IcingaApi.createServiceGroup = jest.fn() + .mockImplementation((ServiceGroup, display_name, data, cb) => cb(null, {foo: "bar"})); + + IcingaApi.getServiceGroup = jest.fn() + .mockImplementation((ServiceGroup, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyServiceGroup('foobar')).resolves.toEqual(true); + var calls = IcingaApi.getServiceGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createServiceGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + }); + + it('add new service group error response', async () => { + IcingaApi.createServiceGroup = jest.fn() + .mockImplementation((ServiceGroup, display_name, data, cb) => cb({Statuscode: 500}, null)); + + IcingaApi.getServiceGroup = jest.fn() + .mockImplementation((ServiceGroup, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyServiceGroup('foobar')).resolves.toEqual(false); + + var calls = IcingaApi.getServiceGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createServiceGroup.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + }); + }); + + describe('apply host', () => { + it('icinga host does exists', async () => { + IcingaApi.createHostCustom = jest.fn(); + IcingaApi.getHostState = jest.fn() + .mockImplementation((Host, cb) => cb(null, {foo: "bar"})); + + await expect(icinga.applyHost('foobar')).resolves.toEqual(true); + const calls = IcingaApi.getHostState.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createHostCustom.mock.calls.length).toBe(0); + }); + + it('get host error response', async () => { + IcingaApi.createHostCustom = jest.fn(); + IcingaApi.getHostState = jest.fn() + .mockImplementation((Host, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.applyHost('foobar')).rejects.toEqual({ + Statuscode: 500 + }); + const calls = IcingaApi.getHostState.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createHostCustom.mock.calls.length).toBe(0); + }); + + it('add new host', async () => { + IcingaApi.createHostCustom = jest.fn() + .mockImplementation((data, name, cb) => cb(null, {foo: "bar"})); + + IcingaApi.getHostState = jest.fn() + .mockImplementation((Host, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyHost('foobar', {foo: "bar"}, ["foobar"])).resolves.toEqual(true); + var calls = IcingaApi.getHostState.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createHostCustom.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe(JSON.stringify({ + attrs: { + foo: "bar" + }, + templates: ["foobar"] + })); + + expect(calls[0][1]).toBe('foobar'); + }); + + it('add new host error response', async () => { + IcingaApi.createHostCustom = jest.fn() + .mockImplementation((data, name, cb) => cb({Statuscode: 500}, null)); + + IcingaApi.getHostState = jest.fn() + .mockImplementation((Host, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyHost('foobar')).resolves.toEqual(false); + + var calls = IcingaApi.getHostState.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createHostCustom.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][1]).toBe('foobar'); + }); + + it('add new host restarts icinga service', async () => { + IcingaApi.restartProcess = jest.fn() + .mockImplementation((cb) => cb(null, {foo: "bar"})); + + jest.useFakeTimers(); + icinga = new IcingaClient(Logger, IcingaApi); + + IcingaApi.createHostCustom = jest.fn() + .mockImplementation((data, name, cb) => cb(null, {foo: "bar"})); + + IcingaApi.getHostState = jest.fn() + .mockImplementation((Host, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyHost('foobar', {foo: "bar"}, ["foobar"])).resolves.toEqual(true); + + var calls = IcingaApi.getHostState.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createHostCustom.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe(JSON.stringify({ + attrs: { + foo: "bar" + }, + templates: ["foobar"] + })); + + expect(calls[0][1]).toBe('foobar'); + jest.runOnlyPendingTimers(); + expect(IcingaApi.restartProcess.mock.calls.length).toBe(1); + }); + }); + + describe('apply service', () => { + it('icinga service does exists', async () => { + IcingaApi.createServiceCustom = jest.fn(); + IcingaApi.getService = jest.fn() + .mockImplementation((host, service, cb) => cb(null, {foo: "bar"})); + + await expect(icinga.applyService('foobar')).resolves.toEqual(true); + const calls = IcingaApi.getService.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createServiceCustom.mock.calls.length).toBe(0); + }); + + it('get service error response', async () => { + IcingaApi.createServiceCustom = jest.fn(); + IcingaApi.getService = jest.fn() + .mockImplementation((host, service, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.applyService('foobar')).rejects.toEqual({ + Statuscode: 500 + }); + const calls = IcingaApi.getService.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(IcingaApi.createServiceCustom.mock.calls.length).toBe(0); + }); + + it('add new service', async () => { + IcingaApi.createServiceCustom = jest.fn() + .mockImplementation((data, host, name, cb) => cb(null, {foo: "bar"})); + + IcingaApi.getService = jest.fn() + .mockImplementation((host, service, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyService('foobar', 'bar', {foo: "bar"}, ["foobar"])).resolves.toEqual(true); + var calls = IcingaApi.getService.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + + calls = IcingaApi.createServiceCustom.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe(JSON.stringify({ + attrs: { + foo: "bar" + }, + templates: ["foobar"] + })); + + expect(calls[0][1]).toBe('foobar'); + expect(calls[0][2]).toBe('bar'); + }); + + it('add new service error response', async () => { + IcingaApi.createServiceCustom = jest.fn() + .mockImplementation((data, host, name, cb) => cb({Statuscode: 500}, null)); + + IcingaApi.getService = jest.fn() + .mockImplementation((host, service, cb) => cb({Statuscode: 404}, null)); + + await expect(icinga.applyService('foobar', 'bar')).resolves.toEqual(false); + + var calls = IcingaApi.getService.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('foobar'); + expect(calls[0][1]).toBe('bar'); + + calls = IcingaApi.createServiceCustom.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][1]).toBe('foobar'); + expect(calls[0][2]).toBe('bar'); + }); + }); + + describe('delete service', () => { + it('delete service successfully', async () => { + IcingaApi.deleteService = jest.fn() + .mockImplementation((host, service, cb) => cb(null, {foo: "bar"})); + + await expect(icinga.deleteService('foo', 'bar')).resolves.toEqual(true); + const calls = IcingaApi.deleteService.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('bar'); + expect(calls[0][1]).toBe('foo'); + }); + + it('delete service failed', async () => { + IcingaApi.deleteService = jest.fn() + .mockImplementation((host, service, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.deleteService('foo', 'bar')).resolves.toEqual(false); + const calls = IcingaApi.deleteService.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('bar'); + expect(calls[0][1]).toBe('foo'); + }); + }); + + describe('delete host', () => { + it('delete host successfully', async () => { + IcingaApi.deleteHost = jest.fn() + .mockImplementation((host, cb) => cb(null, {foo: "bar"})); + + await expect(icinga.deleteHost('bar')).resolves.toEqual(true); + const calls = IcingaApi.deleteHost.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('bar'); + }); + + it('delete host failed', async () => { + IcingaApi.deleteHost = jest.fn() + .mockImplementation((host, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.deleteHost('bar')).resolves.toEqual(false); + const calls = IcingaApi.deleteHost.mock.calls; + expect(calls.length).toBe(1); + expect(calls[0][0]).toBe('bar'); + }); + }); + + + describe('delete services by filter', () => { + it('delete service filtered', async () => { + icinga.deleteService = jest.fn(); + IcingaApi.getServiceFiltered = jest.fn() + .mockImplementation((host, cb) => cb(null, [ + {attrs: {name: "foo", host_name: "foo"}}, + {attrs: {name: "bar", host_name: "bar"}}, + ])); + + await expect(icinga.deleteServicesByFilter('vars.bar==true')).resolves.toEqual(true); + expect(icinga.deleteService.mock.calls.length).toBe(2); + }); + + it('get services filtered failed', async () => { + IcingaApi.deleteService = jest.fn(); + IcingaApi.getServiceFiltered = jest.fn() + .mockImplementation((host, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.deleteServicesByFilter('vars.bar==true')).rejects.toEqual({Statuscode: 500}); + expect(IcingaApi.deleteService.mock.calls.length).toBe(0); + }); + }); + + describe('delete hosts by filter', () => { + it('delete host filtered', async () => { + icinga.deleteHost = jest.fn(); + IcingaApi.getHostFiltered = jest.fn() + .mockImplementation((host, cb) => cb(null, [ + {attrs: {name: "foo"}}, + {attrs: {name: "bar"}}, + ])); + + await expect(icinga.deleteHostsByFilter('vars.bar==true')).resolves.toEqual(true); + expect(icinga.deleteHost.mock.calls.length).toBe(2); + }); + + it('get hosts filtered failed', async () => { + IcingaApi.deleteHost = jest.fn(); + IcingaApi.getHostFiltered = jest.fn() + .mockImplementation((host, cb) => cb({Statuscode: 500}, null)); + + await expect(icinga.deleteHostsByFilter('vars.bar==true')).rejects.toEqual({Statuscode: 500}); + expect(IcingaApi.deleteHost.mock.calls.length).toBe(0); + }); + }); +}); diff --git a/tests/kube/ingress.test.ts b/tests/kube/ingress.test.ts index f1b88f7..0f71e42 100644 --- a/tests/kube/ingress.test.ts +++ b/tests/kube/ingress.test.ts @@ -1,26 +1,19 @@ import Ingress from '../../src/kube/ingress'; import Node from '../../src/kube/node'; import Icinga from '../../src/icinga'; -import {LoggerInstance} from 'winston'; -import * as JSONStream from 'json-stream'; -const KubeApi = require('kubernetes-client').Client; +import Logger from '../../src/logger'; jest.mock('../../src/icinga'); -jest.mock('../../src/kube/node'); -jest.mock('json-stream'); jest.mock('kubernetes-client'); +jest.mock('../../src/logger'); -const fixture = { +const template = { "apiVersion": "extensions/v1beta1", "kind": "Ingress", "metadata": { "annotations": {}, - "creationTimestamp": "2018-02-06T15:30:59Z", - "generation": 4, "name": "foo", "namespace": "foobar", - "resourceVersion": "21173149", - "selfLink": "/apis/extensions/v1beta1/namespaces/foobar/ingresses/foo", - "uid": "bba7f53c-0b52-11e8-a755-0050568fe3c2" + "uid": "xyz" }, "spec": { "rules": [ @@ -57,58 +50,168 @@ const fixture = { "loadBalancer": {} } }; -const tlsFixture = { - "apiVersion": "extensions/v1beta1", - "kind": "Ingress", - "metadata": { - "annotations": {}, - "creationTimestamp": "2018-02-06T15:30:59Z", - "generation": 4, - "name": "foo", - "namespace": "foobar", - "resourceVersion": "21173149", - "selfLink": "/apis/extensions/v1beta1/namespaces/foobar/ingresses/foo", - "uid": "bba7f53c-0b52-11e8-a755-0050568fe3c2" - }, - "spec": { - "rules": [ - { - "host": "barfoo.example.org", - "http": { - "paths": [ - { - "backend": { - "serviceName": "foo-backend", - "servicePort": 80 - } - } - ] - } - } - ] - "tls": { - "secretName": "foo" - } - }, -}; + +var fixture; + +beforeEach(() => { + fixture = JSON.parse(JSON.stringify(template)); +}); describe('kubernetes ingresses', () => { - var instance: Ingress; + describe('ingress watch stream', () => { + it('create icinga ingress object', async () => { + let instance = new Ingress(Logger, Node, Icinga); + var resource = { + type: 'ADDED', + object: fixture + }; + + Icinga.deleteServicesByFilter = jest.fn(); + Icinga.applyHost = jest.fn(); + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + expect(Icinga.deleteServicesByFilter.mock.calls.length).toBe(0); + }); + + it('modify ingress object delete and create', async () => { + let instance = new Ingress(Logger, Node, Icinga); + var resource = { + type: 'MODIFIED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = function(definition) { + expect(definition).toEqual('service.vars.kubernetes.metadata.uid==\"xyz\"'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + var bindings = {}; + var json = { + on: async function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + }); + + it('modify ingress object delete and create host', async () => { + let instance = new Ingress(Logger, Node, Icinga, { + hostName: null + }); + + var resource = { + type: 'MODIFIED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = jest.fn(); + Icinga.deleteHost = function(name) { + expect(name).toEqual('ingress-foobar-foo'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + var bindings = {}; + var json = { + on: async function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + expect(Icinga.deleteServicesByFilter.mock.calls.length).toBe(0); + }); + + it('delete ingress object delete', async () => { + let instance = new Ingress(Logger, Node, Icinga); + + var resource = { + type: 'DELETED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = function(definition) { + expect(definition).toEqual('service.vars.kubernetes.metadata.uid==\"xyz\"'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback.bind(instance); + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(0); + }); + }); + describe('add ingress object with dummy host', () => { it('create icinga host object', () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga); + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: false + }); Icinga.applyHost = jest.fn(); instance.prepareObject(fixture); const call = Icinga.applyHost.mock.calls[0]; - expect(call[0]).toBe('foo'); - expect(call[1]).toBe('foo'); - expect(call[2].display_name).toBe('foo'); - expect(call[2].check_command).toBe('dummy'); + expect(call[0]).toBe('kubernetes-ingresses'); + expect(call[1].display_name).toBe('kubernetes-ingresses'); + expect(call[1].check_command).toBe('dummy'); + }); + + it('create icinga host object with dynamic host', () => { + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: false, + hostName: null + }); + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[0]).toBe('ingress-foobar-foo'); + expect(call[1].display_name).toBe('ingress-foobar-foo'); + expect(call[1].check_command).toBe('dummy'); }); it('create icinga host object with custom definitions', () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream, { + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: false, hostDefinition: { 'vars.foo': 'bar', 'vars.check_command': 'foo' @@ -118,23 +221,25 @@ describe('kubernetes ingresses', () => { Icinga.applyHost = jest.fn(); instance.prepareObject(fixture); const call = Icinga.applyHost.mock.calls[0]; - expect(call[2]['vars.foo']).toBe('bar'); - expect(call[2]['vars.check_command']).toBe('foo'); + expect(call[1]['vars.foo']).toBe('bar'); + expect(call[1]['vars.check_command']).toBe('foo'); }); it('create icinga host object with templates', () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream, { + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: false, hostTemplates: ['foo', 'bar'] }); Icinga.applyHost = jest.fn(); instance.prepareObject(fixture); const call = Icinga.applyHost.mock.calls[0]; - expect(call[3]).toEqual(['foo', 'bar']); + expect(call[2]).toEqual(['foo', 'bar']); }); it('do not create icinga host object while attachToNodes is enabled', () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream, { + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: false, attachToNodes: true }); @@ -146,7 +251,7 @@ describe('kubernetes ingresses', () => { describe('add ingress object namespace as service group', () => { it('create service group per default', async () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream); + let instance = new Ingress(Logger, Node, Icinga); Icinga.applyHost = jest.fn(); Icinga.applyService = jest.fn(); @@ -157,7 +262,7 @@ describe('kubernetes ingresses', () => { }); it('do not create servicegroup if applyServices is disabled', () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream, { + let instance = new Ingress(Logger, Node, Icinga, { applyServices: false }); @@ -170,22 +275,37 @@ describe('kubernetes ingresses', () => { describe('add all ingress object http path rules as service objects', () => { it('create all service objects', async () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream); + let instance = new Ingress(Logger, Node, Icinga); + Icinga.applyServiceGroup = jest.fn(); Icinga.applyService = jest.fn(); await instance.prepareObject(fixture); const calls = Icinga.applyService.mock.calls; expect(Icinga.applyService.mock.instances.length).toBe(2); - expect(calls[0][0]).toBe('foo'); - expect(calls[1][0]).toBe('foo'); - expect(calls[0][1]).toBe('foobar.example.org:http'); - expect(calls[1][1]).toBe('barfoo.example.org:http'); + expect(calls[0][0]).toBe('kubernetes-ingresses'); + expect(calls[1][0]).toBe('kubernetes-ingresses'); + expect(calls[0][1]).toBe('foobar.example.org-http--'); + expect(calls[1][1]).toBe('barfoo.example.org-http--foo'); expect(calls[0][2]['vars.http_path']).toBe('/'); expect(calls[1][2]['vars.http_path']).toBe('/foo'); }); + + it('create all service objects with dynamic hosts', async () => { + let instance = new Ingress(Logger, Node, Icinga, { + hostName: null + }); + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][0]).toBe('ingress-foobar-foo'); + expect(calls[1][0]).toBe('ingress-foobar-foo'); + } it('create all service objects with custom service definition', async () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream, { + let instance = new Ingress(Logger, Node, Icinga, { applyServices: true, serviceDefinition: { 'check_command': 'tcp', @@ -204,7 +324,7 @@ describe('kubernetes ingresses', () => { }); it('create all service objects with templates', async () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream, { + let instance = new Ingress(Logger, Node, Icinga, { applyServices: true, serviceTemplates: ['foo', 'bar'] }); @@ -218,18 +338,107 @@ describe('kubernetes ingresses', () => { }); it('create service objects for tls enabled ingresses', async () => { - let instance = new Ingress(LoggerInstance, Node, KubeApi, Icinga, JSONStream, { + let instance = new Ingress(Logger, Node, Icinga, { applyServices: true, serviceTemplates: ['foo', 'bar'] }); + fixture.spec.tls = { + secretName: 'foo' + } + Icinga.applyService = jest.fn(); - await instance.prepareObject(tlsFixture); + await instance.prepareObject(fixture); const calls = Icinga.applyService.mock.calls; - expect(Icinga.applyService.mock.instances.length).toBe(2); - expect(calls[0][1]).toBe('barfoo.example.org:http'); - expect(calls[1][1]).toBe('barfoo.example.org:https'); + expect(Icinga.applyService.mock.instances.length).toBe(4); + expect(calls[0][1]).toBe('foobar.example.org-http--'); + expect(calls[1][1]).toBe('foobar.example.org-https--'); expect(calls[1][2]['vars.http_ssl']).toBe(true); }); + + it('attach services to kube workers if attachToNodes is enabled', async () => { + let instance = new Ingress(Logger, Node, Icinga, { + attachToNodes: true + }); + + Node.getWorkerNodes = function() { + return ['foo', 'bar']; + }; + + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyHost = jest.fn(); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyHost.mock.instances.length).toBe(0); + expect(Icinga.applyService.mock.instances.length).toBe(4); + + expect(calls[0][0]).toBe('foo'); + expect(calls[1][0]).toBe('bar'); + expect(calls[2][0]).toBe('foo'); + expect(calls[3][0]).toBe('bar'); + expect(calls[0][1]).toBe('foobar.example.org-http--'); + expect(calls[1][1]).toBe('foobar.example.org-http--'); + expect(calls[2][1]).toBe('barfoo.example.org-http--foo'); + expect(calls[3][1]).toBe('barfoo.example.org-http--foo'); + }); + }); + + describe('kubernetes annotations', () => { + it('check_command/templates annotation', async () => { + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: true + }); + + fixture.metadata.annotations['kube-icinga/check_command'] = 'bar'; + fixture.metadata.annotations['kube-icinga/templates'] = 'foobar,barfoo'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2].check_command).toBe('bar'); + expect(calls[1][2].check_command).toBe('bar'); + expect(calls[0][3]).toEqual(['foobar', 'barfoo']); + expect(calls[1][3]).toEqual(['foobar', 'barfoo']); + }); + + it('use annotation instead global definition', async () => { + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: true, + serviceDefinition: { + check_command: 'foo' + } + }); + fixture.metadata.annotations['kube-icinga/check_command'] = 'bar'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2].check_command).toBe('bar'); + expect(calls[1][2].check_command).toBe('bar'); + }); + + it('definiton merge', async () => { + let instance = new Ingress(Logger, Node, Icinga, { + applyServices: true, + serviceDefinition: { + check_command: 'foo' + } + }); + fixture.metadata.annotations['kube-icinga/definition'] = '{"vars.foo": "foobar"}'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2]["check_command"]).toBe('foo'); + expect(calls[0][2]["vars.foo"]).toBe('foobar'); + }); }); }); diff --git a/tests/kube/node.test.ts b/tests/kube/node.test.ts new file mode 100644 index 0000000..7b5d08f --- /dev/null +++ b/tests/kube/node.test.ts @@ -0,0 +1,158 @@ +import Node from '../../src/kube/node'; +import Icinga from '../../src/icinga'; +import Logger from '../../src/logger'; +import * as JSONStream from 'json-stream'; +jest.mock('../../src/icinga'); +jest.mock('../../src/logger'); +const Readable = require('stream').Readable; + +const template = { + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "annotations": {}, + "name": "kubernetes-worker001.foo.bar", + }, + "spec": { + "externalID": "kubernetes-worker001.foo.bar" + } +}; + +var fixture; + +beforeEach(() => { + fixture = JSON.parse(JSON.stringify(template)); +}); + +describe('kubernetes nodes', () => { + describe('nodes watch stream', () => { + it('create icinga host object', async () => { + let instance = new Node(Logger, Icinga); + var resource = { + type: 'ADDED', + object: fixture + }; + + instance.prepareObject = function(definition) { + expect(definition).toEqual(resource.object); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + bindings.data(resource); + }); + + it('modify host object no action', async () => { + let instance = new Node(Logger, Icinga); + var resource = { + type: 'MODIFIED', + object: fixture + }; + + instance.prepareObject = jest.fn(); + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + bindings.data(resource); + expect(instance.prepareObject.mock.calls.length).toBe(0); + }); + + it('delete host object delete', async () => { + let instance = new Node(Logger, Icinga); + var resource = { + type: 'DELETED', + object: fixture + }; + + Icinga.deleteHost = jest.fn(); + instance.prepareObject = jest.fn(); + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + bindings.data(resource); + expect(Icinga.deleteHost.mock.calls.length).toEqual(1) + expect(instance.prepareObject.mock.calls.length).toEqual(0); + }); + }); + + describe('add node object', () => { + it('create icinga host object', () => { + let instance = new Node(Logger, Icinga, JSONStream); + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[0]).toBe('kubernetes-worker001.foo.bar'); + expect(call[1].display_name).toBe('kubernetes-worker001.foo.bar'); + expect(call[1].check_command).toBe('ping'); + expect(instance.getWorkerNodes()).toEqual(['kubernetes-worker001.foo.bar']); + }); + + it('create icinga host object (kubernetes worker unschedulable)', () => { + let instance = new Node(Logger, Icinga, JSONStream); + Icinga.applyHost = jest.fn(); + fixture.spec.unschedulable = true; + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[0]).toBe('kubernetes-worker001.foo.bar'); + expect(call[1].display_name).toBe('kubernetes-worker001.foo.bar'); + expect(instance.getWorkerNodes()).toEqual([]); + }); + + it('create icinga host object with custom definitions', () => { + let instance = new Node(Logger, Icinga, { + hostDefinition: { + 'vars.foo': 'bar', + 'vars.check_command': 'foo' + } + }); + + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[1]['vars.foo']).toBe('bar'); + expect(call[1]['vars.check_command']).toBe('foo'); + }); + + it('create icinga host object with templates', () => { + let instance = new Node(Logger, Icinga, { + hostTemplates: ['foo', 'bar'] + }); + + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[2]).toEqual(['foo', 'bar']); + }); + }); +}); + diff --git a/tests/kube/service.test.ts b/tests/kube/service.test.ts new file mode 100644 index 0000000..5e26641 --- /dev/null +++ b/tests/kube/service.test.ts @@ -0,0 +1,520 @@ +import Service from '../../src/kube/service'; +import Node from '../../src/kube/node'; +import Icinga from '../../src/icinga'; +import Logger from '../../src/logger'; +jest.mock('../../src/icinga'); +jest.mock('../../src/kube/node'); +jest.mock('../../src/logger'); + +const template = { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "foo", + "namespace": "foobar", + "annotations": {}, + "uid": "xyz" + }, + "spec": { + "clusterIP": "10.99.24.32", + "ports": [ + { + "name": "http", + "port": 80, + "protocol": "TCP", + "targetPort": 80 + }, + { + "name": "bar", + "port": 10000, + "protocol": "tcp", + "targetPort": 10000 + } + ], + "selector": { + "app": "bar" + }, + "sessionAffinity": "None", + "type": "ClusterIP" + }, + "status": { + "loadBalancer": {} + } +}; + +var fixture; + +beforeEach(() => { + fixture = JSON.parse(JSON.stringify(template)); +}); + +describe('kubernetes services', () => { + describe('service watch stream', () => { + it('do not create icinga service object if typ is disabled for provisioning', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + discover: false + } + }); + + var resource = { + type: 'ADDED', + object: fixture + }; + + Icinga.deleteServicesByFilter = jest.fn(); + Icinga.applyHost = jest.fn(); + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(0); + expect(Icinga.deleteServicesByFilter.mock.calls.length).toBe(0); + }); + + it('create icinga service object', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + discover: true + } + }); + var resource = { + type: 'ADDED', + object: fixture + }; + + Icinga.deleteServicesByFilter = jest.fn(); + Icinga.applyHost = jest.fn(); + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + expect(Icinga.deleteServicesByFilter.mock.calls.length).toBe(0); + }); + + it('modify service object delete and create', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + discover: true + } + }); + + var resource = { + type: 'MODIFIED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = function(definition) { + expect(definition).toEqual('service.vars.kubernetes.metadata.uid==\"xyz\"'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + var bindings = {}; + var json = { + on: async function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + }); + + it('delete service object delete', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + discover: true + } + }); + + var resource = { + type: 'DELETED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = function(definition) { + expect(definition).toEqual('service.vars.kubernetes.metadata.uid==\"xyz\"'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback.bind(instance); + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(0); + }); + }); + + describe('add service object with dummy host', () => { + it('create icinga host object', () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: false + } + }); + + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[0]).toBe('kubernetes-clusterip-services'); + expect(call[1].address).toBe('kubernetes-clusterip-services'); + expect(call[1].display_name).toBe('kubernetes-clusterip-services'); + expect(call[1].check_command).toBe('dummy'); + }); + + it('create dynamic icinga host object', () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + hostName: null, + applyServices: false + } + }); + + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[0]).toBe('service-foobar-foo'); + expect(call[1].address).toBe('10.99.24.32'); + expect(call[1].display_name).toBe('service-foobar-foo'); + expect(call[1].check_command).toBe('dummy'); + }); + + it('create icinga host object with custom definitions', () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: false, + hostDefinition: { + 'vars.foo': 'bar', + 'vars.check_command': 'foo' + } + } + }); + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[1]['vars.foo']).toBe('bar'); + expect(call[1]['vars.check_command']).toBe('foo'); + }); + + it('create icinga host object with templates', () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: false, + hostTemplates: ['foo', 'bar'] + } + }); + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[2]).toEqual(['foo', 'bar']); + }); + + it('do not create icinga host object while service is of type NodePort', () => { + let instance = new Service(Logger, Node, Icinga, { + NodePort: { + applyServices: false + } + }); + + fixture.spec.type = 'NodePort'; + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + expect(Icinga.applyHost.mock.instances.length).toBe(0); + }); + }); + + describe('add service object namespace as service group', () => { + it('create service group per default', async () => { + let instance = new Service(Logger, Node, Icinga); + + Icinga.applyHost = jest.fn(); + Icinga.hasCheckCommand = jest.fn(); + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + await instance.prepareObject(fixture); + const call = Icinga.applyServiceGroup.mock.calls[0]; + expect(call[0]).toBe('foobar'); + }); + + it('do not create servicegroup if applyServices is disabled', () => { + let instance = new Service(Logger, Node, Icinga, { + applyServices: false + }); + + instance.prepareObject(fixture); + Icinga.applyServiceGroup = jest.fn(); + const call = Icinga.applyServiceGroup.mock.instances[0]; + expect(Icinga.applyServiceGroup.mock.instances.length).toBe(0); + }); + }); + + describe('add all service object ports as service objects', () => { + it('create all service objects', async () => { + let instance = new Service(Logger, Node, Icinga); + + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][0]).toBe('kubernetes-clusterip-services'); + expect(calls[1][0]).toBe('kubernetes-clusterip-services'); + expect(calls[0][1]).toBe('foobar-foo-http'); + expect(calls[1][1]).toBe('foobar-foo-bar'); + + expect(calls[0][2]['check_command']).toBe('tcp'); + expect(calls[0][2]['vars.tcp_address']).toBe('10.99.24.32'); + expect(calls[0][2]['vars.tcp_port']).toBe(80); + expect(calls[1][2]['check_command']).toBe('tcp'); + expect(calls[1][2]['vars.tcp_address']).toBe('10.99.24.32'); + expect(calls[1][2]['vars.tcp_port']).toBe(10000); + }); + + it('create all service objects with dynamic hosts', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + hostName: null + } + }); + + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][0]).toBe('service-foobar-foo'); + expect(calls[1][0]).toBe('service-foobar-foo'); + }); + + it('create all service objects with custom service definition', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: true, + serviceDefinition: { + 'check_command': 'http', + 'vars.foo': 'bar' + } + } + }); + + Icinga.applyHost = jest.fn(); + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + Icinga.hasCheckCommand = jest.fn(); + Icinga.hasCheckCommand.mockResolvedValue(true); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2].check_command).toBe('http'); + expect(calls[0][2]['vars.http_address']).toBe('10.99.24.32'); + expect(calls[0][2]['vars.http_port']).toBe(80); + expect(calls[0][2]['vars.foo']).toBe('bar'); + expect(calls[1][2].check_command).toBe('http'); + expect(calls[1][2]['vars.http_address']).toBe('10.99.24.32'); + expect(calls[1][2]['vars.http_port']).toBe(10000); + expect(calls[1][2]['vars.foo']).toBe('bar'); + }); + + it('create all service objects with custom service definition, check_command not found, fallback to protocol', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: true, + serviceDefinition: { + 'check_command': 'http', + 'vars.foo': 'bar' + } + } + }); + + Icinga.applyHost = jest.fn(); + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + Icinga.hasCheckCommand = jest.fn(); + Icinga.hasCheckCommand.mockResolvedValue(false); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2].check_command).toBe('tcp'); + expect(calls[0][2]['vars.foo']).toBe('bar'); + expect(calls[1][2].check_command).toBe('tcp'); + expect(calls[1][2]['vars.foo']).toBe('bar'); + }); + + it('create all service objects with templates', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: true, + serviceTemplates: ['foo', 'bar'] + } + }); + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][3]).toEqual(['foo', 'bar']); + expect(calls[1][3]).toEqual(['foo', 'bar']); + }); + + it('create NodePort service objects', async () => { + let instance = new Service(Logger, Node, Icinga); + + Node.getWorkerNodes = function() { + return ['foo', 'bar']; + }; + + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + fixture.spec.type = 'NodePort' + Icinga.applyHost = jest.fn(); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyHost.mock.instances.length).toBe(0); + expect(Icinga.applyService.mock.instances.length).toBe(4); + + expect(calls[0][0]).toBe('foo'); + expect(calls[1][0]).toBe('bar'); + expect(calls[0][1]).toBe('foobar-foo-http'); + expect(calls[1][1]).toBe('foobar-foo-http'); + expect(calls[0][2]['check_command']).toBe('tcp'); + expect(calls[0][2]['vars.tcp_address']).toBe(undefined); + expect(calls[0][2]['vars.tcp_port']).toBe(80); + expect(calls[1][2]['check_command']).toBe('tcp'); + expect(calls[1][2]['vars.tcp_address']).toBe(undefined); + expect(calls[1][2]['vars.tcp_port']).toBe(80); + + expect(calls[2][0]).toBe('foo'); + expect(calls[3][0]).toBe('bar'); + expect(calls[2][1]).toBe('foobar-foo-bar'); + expect(calls[3][1]).toBe('foobar-foo-bar'); + expect(calls[2][2]['check_command']).toBe('tcp'); + expect(calls[2][2]['vars.tcp_address']).toBe(undefined); + expect(calls[2][2]['vars.tcp_port']).toBe(10000); + expect(calls[3][2]['check_command']).toBe('tcp'); + expect(calls[3][2]['vars.tcp_address']).toBe(undefined); + expect(calls[3][2]['vars.tcp_port']).toBe(10000); + }); + }); + + describe('kubernetes annotations', () => { + it('check_command/templates annotation', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: true + } + }); + + fixture.metadata.annotations['kube-icinga/check_command'] = 'bar'; + fixture.metadata.annotations['kube-icinga/templates'] = 'foobar,barfoo'; + Icinga.applyService = jest.fn(); + Icinga.hasCheckCommand = jest.fn(); + Icinga.hasCheckCommand.mockResolvedValue(true); + Icinga.applyHost = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2].check_command).toBe('bar'); + expect(calls[1][2].check_command).toBe('bar'); + expect(calls[0][2]['vars.bar_address']).toBe('10.99.24.32'); + expect(calls[0][2]['vars.bar_port']).toBe(80); + expect(calls[1][2]['vars.bar_address']).toBe('10.99.24.32'); + expect(calls[1][2]['vars.bar_port']).toBe(10000); + expect(calls[0][3]).toEqual(['foobar', 'barfoo']); + expect(calls[1][3]).toEqual(['foobar', 'barfoo']); + }); + + it('use annotation instead global definition', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: true, + serviceDefinition: { + check_command: 'foo' + } + } + }); + fixture.metadata.annotations['kube-icinga/check_command'] = 'bar'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + Icinga.hasCheckCommand = jest.fn(); + Icinga.hasCheckCommand.mockResolvedValue(true); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2].check_command).toBe('bar'); + expect(calls[1][2].check_command).toBe('bar'); + }); + + it('definiton merge', async () => { + let instance = new Service(Logger, Node, Icinga, { + ClusterIP: { + applyServices: true, + serviceDefinition: { + check_command: 'foo' + } + } + }); + fixture.metadata.annotations['kube-icinga/definition'] = '{"vars.foo": "foobar"}'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + Icinga.hasCheckCommand = jest.fn(); + Icinga.hasCheckCommand.mockResolvedValue(true); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(2); + expect(calls[0][2]["check_command"]).toBe('foo'); + expect(calls[0][2]["vars.foo"]).toBe('foobar'); + }); + }); +}); diff --git a/tests/kube/volume.test.ts b/tests/kube/volume.test.ts new file mode 100644 index 0000000..857dc38 --- /dev/null +++ b/tests/kube/volume.test.ts @@ -0,0 +1,405 @@ +import Volume from '../../src/kube/volume'; +import Node from '../../src/kube/node'; +import Icinga from '../../src/icinga'; +import Logger from '../../src/logger'; +jest.mock('../../src/logger'); +jest.mock('../../src/icinga'); +jest.mock('kubernetes-client'); + +const template = { + "apiVersion": "v1", + "kind": "PersistentVolume", + "metadata": { + "annotations": { + "hpe.com/docker-volume-name": "generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2", + "pv.kubernetes.io/provisioned-by": "hpe.com/nimble", + "volume.beta.kubernetes.io/storage-class": "generic-nimble" + }, + "name": "generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2", + "uid": "xyz" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "capacity": { + "storage": "50Gi" + }, + "claimRef": { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "name": "nimbletestpvc-006", + "namespace": "foobar" + }, + "flexVolume": { + "driver": "hpe.com/nimble", + "options": { + "name": "generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2", + "thick": "false" + } + }, + "persistentVolumeReclaimPolicy": "Delete", + "storageClassName": "generic-nimble" + }, + "status": { + "phase": "Bound" + } +}; + +var fixture; + +beforeEach(() => { + fixture = JSON.parse(JSON.stringify(template)); +}); + +describe('kubernetes volumes', () => { + describe('volume watch stream', () => { + it('create icinga volume object', async () => { + let instance = new Volume(Logger, Node, Icinga); + var resource = { + type: 'ADDED', + object: fixture + }; + + Icinga.deleteServicesByFilter = jest.fn(); + Icinga.applyHost = jest.fn(); + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + expect(Icinga.deleteServicesByFilter.mock.calls.length).toBe(0); + }); + + it('modify volume object delete and create', async () => { + let instance = new Volume(Logger, Node, Icinga); + var resource = { + type: 'MODIFIED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = function(definition) { + expect(definition).toEqual('service.vars.kubernetes.metadata.uid==\"xyz\"'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + var bindings = {}; + var json = { + on: async function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + }); + + it('modify ingress object delete and create host', async () => { + let instance = new Volume(Logger, Node, Icinga, { + hostName: null + }); + + var resource = { + type: 'MODIFIED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = jest.fn(); + Icinga.deleteHost = function(name) { + expect(name).toEqual('volume-generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + var bindings = {}; + var json = { + on: async function(name, callback) { + bindings[name] = callback; + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(1); + expect(Icinga.deleteServicesByFilter.mock.calls.length).toBe(0); + }); + + it('delete volume object delete', async () => { + let instance = new Volume(Logger, Node, Icinga); + + var resource = { + type: 'DELETED', + object: fixture + }; + + Icinga.applyHost = jest.fn(); + Icinga.deleteServicesByFilter = function(definition) { + expect(definition).toEqual('service.vars.kubernetes.metadata.uid==\"xyz\"'); + return new Promise((resolve,reject) => { + resolve(true); + }); + }; + + + var bindings = {}; + var json = { + on: function(name, callback) { + bindings[name] = callback.bind(instance); + } + }; + + await instance.kubeListener(() => { + return json; + }); + + await bindings.data(resource); + expect(Icinga.applyHost.mock.calls.length).toBe(0); + }); + }); + + describe('add volume object with dummy host', () => { + it('create icinga host object', () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: false + }); + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[0]).toBe('kubernetes-volumes'); + expect(call[1].display_name).toBe('kubernetes-volumes'); + expect(call[1].check_command).toBe('dummy'); + }); + + it('create icinga host object with dynamic host', () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: false, + hostName: null + }); + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[0]).toBe('volume-generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2'); + expect(call[1].display_name).toBe('volume-generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2'); + expect(call[1].check_command).toBe('dummy'); + }); + + it('create icinga host object with custom definitions', () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: false, + hostDefinition: { + 'vars.foo': 'bar', + 'vars.check_command': 'foo' + } + }); + + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[1]['vars.foo']).toBe('bar'); + expect(call[1]['vars.check_command']).toBe('foo'); + }); + + it('create icinga host object with templates', () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: false, + hostTemplates: ['foo', 'bar'] + }); + + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyHost.mock.calls[0]; + expect(call[2]).toEqual(['foo', 'bar']); + }); + + it('do not create icinga host object while attachToNodes is enabled', () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: false, + attachToNodes: true + }); + + Icinga.applyHost = jest.fn(); + instance.prepareObject(fixture); + expect(Icinga.applyHost.mock.instances.length).toBe(0); + }); + }); + + describe('add volume object namespace as service group', () => { + it('create service group per default', async () => { + let instance = new Volume(Logger, Node, Icinga); + + Icinga.applyHost = jest.fn(); + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + await instance.prepareObject(fixture); + const call = Icinga.applyServiceGroup.mock.calls[0]; + expect(call[0]).toBe('foobar'); + }); + + it('do not create servicegroup if applyServices is disabled', () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: false + }); + + Icinga.applyServiceGroup = jest.fn(); + instance.prepareObject(fixture); + const call = Icinga.applyServiceGroup.mock.instances[0]; + expect(Icinga.applyServiceGroup.mock.instances.length).toBe(0); + }); + }); + + describe('add all volume objects as service objects', () => { + it('create service object', async () => { + let instance = new Volume(Logger, Node, Icinga); + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(1); + expect(calls[0][0]).toBe('kubernetes-volumes'); + expect(calls[0][1]).toBe('generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2'); + }); + + it('create service object with dynamic host', async () => { + let instance = new Volume(Logger, Node, Icinga, { + hostName: null + }); + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(1); + expect(calls[0][0]).toBe('volume-generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2'); + } + + it('create all service objects with custom service definition', async () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: true, + serviceDefinition: { + 'check_command': 'tcp', + 'vars.foo': 'bar' + } + }); + + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(1); + expect(calls[0][2].check_command).toBe('tcp'); + expect(calls[0][2]['vars.foo']).toBe('bar'); + }); + + it('create all service objects with templates', async () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: true, + serviceTemplates: ['foo', 'bar'] + }); + + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(1); + expect(calls[0][3]).toEqual(['foo', 'bar']); + }); + + it('attach services to kube workers if attachToNodes is enabled', async () => { + let instance = new Volume(Logger, Node, Icinga, { + attachToNodes: true + }); + + Node.getWorkerNodes = function() { + return ['foo', 'bar']; + }; + + Icinga.applyService = jest.fn(); + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyHost = jest.fn(); + + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyHost.mock.instances.length).toBe(0); + expect(Icinga.applyService.mock.instances.length).toBe(2); + + expect(calls[0][0]).toBe('foo'); + expect(calls[1][0]).toBe('bar'); + expect(calls[0][1]).toBe('generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2'); + expect(calls[1][1]).toBe('generic-nimble-fad5684e-22fb-11e9-94e3-0050568fe3c2'); + }); + }); + + describe('kubernetes annotations', () => { + it('check_command/templates annotation', async () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: true + }); + + fixture.metadata.annotations['kube-icinga/check_command'] = 'bar'; + fixture.metadata.annotations['kube-icinga/templates'] = 'foobar,barfoo'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(1); + expect(calls[0][2].check_command).toBe('bar'); + expect(calls[0][3]).toEqual(['foobar', 'barfoo']); + }); + + it('use annotation instead global definition', async () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: true, + serviceDefinition: { + check_command: 'foo' + } + }); + fixture.metadata.annotations['kube-icinga/check_command'] = 'bar'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(1); + expect(calls[0][2].check_command).toBe('bar'); + }); + + it('definiton merge', async () => { + let instance = new Volume(Logger, Node, Icinga, { + applyServices: true, + serviceDefinition: { + check_command: 'foo' + } + }); + fixture.metadata.annotations['kube-icinga/definition'] = '{"vars.foo": "foobar"}'; + + Icinga.applyServiceGroup = jest.fn(); + Icinga.applyService = jest.fn(); + await instance.prepareObject(fixture); + const calls = Icinga.applyService.mock.calls; + expect(Icinga.applyService.mock.instances.length).toBe(1); + expect(calls[0][2]["check_command"]).toBe('foo'); + expect(calls[0][2]["vars.foo"]).toBe('foobar'); + }); + }); +});