diff --git a/.ci/jenkins-go-agent/Dockerfile b/.ci/jenkins-go-agent/Dockerfile new file mode 100644 index 00000000000..0d3214557c7 --- /dev/null +++ b/.ci/jenkins-go-agent/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.14.2 + +RUN \ + apt-get update \ + && apt-get install -y --no-install-recommends \ + zip rsync\ + && rm -rf /var/lib/apt/lists/* + +RUN go get -u github.com/magefile/mage \ + && go get -u github.com/elastic/go-licenser \ + && go get -u -v golang.org/x/tools/cmd/goimports + +ENV GO111MODULE="auto" diff --git a/.github/ISSUE_TEMPLATE/integration.md b/.github/ISSUE_TEMPLATE/integration.md deleted file mode 100644 index f69cbf32e20..00000000000 --- a/.github/ISSUE_TEMPLATE/integration.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: 'Integration' -labels: integration -about: "Meta issue to track the lifecycle of an integration" ---- - -# <_Name_> - -This meta issue will remain open through the whole lifecycle of the integration. - -<_Set the issue title to the name of the integration_> - -## Motivation - -<_Describe the rationale to include this integration in the solution_> - -## Expected Scope - -<_Provide a high level overview of the expected capabilities of the integration_> diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..036d7c3b78c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store + +.idea +build +public diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..94a0f0d2612 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,347 @@ +# Contributing + +This page is intended for contributors to the registry and packages. + +## Definitions + +### Package + +A package contains the dashboards, visualisations, and configurations to monitor the logs and metrics of a particular technology or group of related services, such as “MySQL”, or “System”. + +The package consists of: + +* Name +* Zero or more dashboards and visualisations and Canvas workpads +* Zero or more ML job definitions +* Zero or more dataset templates + +The package is versioned. + +### Integration + +An integration is a specific type of a _package_ defining datasets used to observe the same product (logs and metrics). + +### Dataset Template + +A dataset template is part of a package and contains all the assets which are needed to create a dataset. Example for assets are: ingest pipeline, agent config template, ES index template, ... + +Dataset templates are inside the package directory under `dataset`. + +The dataset template consists of: + +* An alias templates (or the fields.yml to create it) +* Zero or more ingest pipelines +* An Elastic Agent config template + +### Migration from Beats + +A defined importing procedure used to transform both Filebeat and Metricbeat modules, related to +the same observed product, into a single integration. The integration contains extracted dataset configuration of beat +modules, hence no modules are required to exist anymore. + +## Package structure + +### Elements + +Link: https://github.com/elastic/package-registry/blob/master/ASSETS.md + +### Reference packages + +The following packages can be considered as reference points for all integrations. + +#### Integration: reference + +Link: https://github.com/elastic/package-registry/tree/master/dev/packages/example/reference-1.0.0 + +The directory contains mandatory manifest files defining the integration and its datasets. All manifests have fields +annotated with comments to better understand their goals. + +_Keep in mind that this package doesn't contain all file resources (images, screenshots, icons) referenced in manifests. +Let's assume that they're also there._ + +#### Integration: mysql + +Link: https://github.com/mtojek/package-registry/tree/package-mysql-0.0.2/dev/packages/alpha/mysql-0.0.2 + +The MySQL integration was the first integration built using the [import-beats](https://github.com/elastic/package-registry/tree/master/dev/import-beats) script. +The script imported filesets and metricsets from both MySQL modules, and converted them to a package. + +The MySQL integration contains all parts that should be present (or are required) in the integration package. + +After using the _import-beats_ script, the integration has been manually adjusted and extended with dedicated docs. + +## Create a new integration + +This section describes steps required to build a new integration. If you plan to prepare the integration +with a product unsupported by [Beats](https://github.com/elastic/beats), feel free to skip the section about importing +existing modules. + +### Import from existing modules + +The import procedure heavily uses on the _import-beats_ script. If you are interested how does it work internally, +feel free to review the script's [README](https://github.com/elastic/package-registry/blob/master/dev/import-beats/README.md). + +1. Create an issue in the [package-registry](https://github.com/elastic/package-registry) to track ongoing progress with + the integration (especially manual changes). + + Focus on the one particular product (e.g. MySQL, ActiveMQ) you would like to integrate with. + Use this issue to mention every manual change that has been applied. It will help in adjusting the `import-beats` + script and reviewing the integration. + +2. Prepare the developer environment: + 1. Clone/refresh the following repositories: + * https://github.com/elastic/beats + * https://github.com/elastic/ecs + * https://github.com/elastic/eui + * https://github.com/elastic/kibana + + Make sure you don't have any manual changes applied as they will reflect on the integration. + 2. Clone/refresh the Elastic Package Registry (EPR) to always use the latest version of the script: + * https://github.com/elastic/package-registry + 3. Make sure you've the `mage` tool installed: + ```bash + $ go get -u -d github.com/magefile/mage + ``` +3. Boot up required dependencies: + 1. Elasticseach instance: + * Kibana's dependency + 2. Kibana instance: + * used to migrate dashboards, if not available, you can skip the generation (`SKIP_KIBANA=true`) + + _Hint_. There is dockerized environment in beats (`cd testing/environments`). Boot it up with the following command: + `docker-compose -f snapshot.yml -f local.yml up --force-recreate elasticsearch kibana`. +4. Create a new branch for the integration in `package-registry` repository (diverge from master). +5. Run the command: `mage ImportBeats` to start the import process. + + The outcome of running the `import-beats` script is directory with refreshed and updated integrations. + + It will take a while to finish, but the console output should be updated frequently to track the progress. + The command must end up with the exit code 0. Kindly please to open an issue if it doesn't. + + Generated packages are stored by default in the `dev/packages/beats` directory. Generally, the import process + updates all of the integrations, so don't be surprised if you notice updates to multiple integrations, including + the one you're currently working on (e.g. `dev/packages/beats/foobarbaz-0.0.1`). You can either commit this changes + or leave them for later. + + If you want to select a subgroup of packages, set the environment variable `PACKAGES` (comma-delimited list): + + ```bash + $ PACKAGES=aws,cisco mage ImportBeats + ``` + +6. Copy the package output for your integration (e.g. `dev/packages/beats/foobarbaz-0.0.1`) to the _alpha_ directory and + raise the version manually: `dev/packages/alpha/foobarbaz-0.0.2`. + +### Fine-tune the integration + +#### Motivation + +Most of migration work has been done by the `import-beats` script, but there're tasks that require developer's +interaction. + +It may happen that your integration misses a screenshot or an icon, it's a good moment to add missing resources to +Beats/Kibana repositories and re-import the integration (idempotent). + +#### Checklist + +The order of action items on the checklist is advised to prevent the contributor from repeating some actions (fixing +what's been already fixed, as the script has overridden part of it). + +1. Add icon if missing. + + The integration icons are presented in different places in Kibana, hence it's better to define custom icons to make + the UI easier to navigate. + + As the `import-beats` script looks for icons in Kibana and EUI repositories, add an icon to the first one the same + way as for tutorial resources (Kibana directory: `src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/`). + +2. Add screenshot if missing. + + The Kibana Integration Manager shows screenshots related with the integration. Screenshots present Kibana + dashboards visualizing the metric/log data. + + The `import-beats` script finds references to screenshots mentioned in `_meta/docs.asciidoc` and copies image files + from the Beats directories: + * `metricbeat/docs/images` + * `filebeat/docs/images` + +3. Improve/correct spelling product names. + + The correct spelling of product names simply makes better impression. The `import-beats` scripts uses the `fields.yml` + file as the source of the correct spelling (`title` property), e.g. Mysql - MySQL, Nginx - NGINX, Aws - AWS. + + Keep in mind that this step requires reimporting package contents. + +4. Write README template file for the integration. + + The README template is used to render the final README file including exported fields. The template should be placed + in the `dev/import-beats-resources//docs/README.md`. + + Review the MySQL docs template to see how to use template functions (e.g. `{{fields "dataset-name"}}`). + If the same dataset name is used in both metrics and logs, please add `-metrics` and `-logs` in the template. For example, `elb` is a dataset for log and also a dataset for metrics. In README.md template, `{{fields "elb-logs"}}` and `{{fields "elb-metrics"}}` are used to separate them. + +5. Review fields file and exported fields in docs. + + The goal of this action item is to verify if produced artifacts are correct. + + The fields files (package-fields.yml, fields.yml and ecs.yml) in the package were created from original fields.yml + files (that may contain ECS schema fields) and fields.epr.yml (defining some other fields used in the ingest + pipeline). It may happen that original sources have a typo, bad description or misses a field definition. + The sum of fields in all present files should contain only fields that are really used, e.g. not all existing ECS + fields. + + It may happen that the ingest pipeline uses fields abstracted from ECS, but not mentioned in `fields.yml`. + Integrations should contain these fields and also have them documented. + + See the PR https://github.com/elastic/beats/pull/17895 to understand how to add them to Beats (e.g. `event.code`, + `event.provider`) using the `fields.epr.yml` file. + +6. Metricbeat: add missing configuration options. + + The `import-beats` script extracts configuration options from Metricbeat module's `_meta` directory. It analyzes + the configuration files and selects options based on enabled metricsets (not commented). If you notice that some + configuration options are missing in your package's manifest files, simply create the `config.epr.yml` file with all + required options. + + Sample PR: https://github.com/elastic/beats/pull/17323 + +7. Review _titles_ and _descriptions_ in manifest files. + + Titles and descriptions are fields visualized in the Kibana UI. Most users will use them to see how to configure + the integration with their installation of a product or to how to use advanced configuration options. + +8. Compact configuration options (vars). + + Currently, all configuration options are set by the `import-beats` script on the stream level + (path: `dataset//manifest.yml`). + + It may happen that some of them in different datasets are simply duplicates or concern the same setting, which + will be always equal (e.g. MySQL username, password). Keep in mind that two datasets may have the same configuration + option, but different values (e.g. `period`, `paths`), hence can't be compacted. + + To sum up, compacting takes down from the user the necessity to setup the same configuration option few times (one + per dataset). + +9. Define all variable properties. + + The variable properties customize visualization of configuration options in the Kibana UI. Make sure they're + defined in all manifest files. + +```yaml + vars: + - name: paths + required: true + show_user: true + title: Access log paths + description: Paths to the nginx access log file. + type: text + multi: true + default: + - /var/log/nginx/access.log* +``` + +**required** - option is required + +**show_user** - don't hide the configuration option (collapsed menu) + +**title** - human readable variable name + +**description** - variable description (may contain some details) + +**type** - field type (according to the reference: text, password, bool, integer) + +**multi** - the field has mutliple values. + +10. Review stream configuration. + + Due to changed templating engine from a standard Golang one to [handlebars](https://handlebarsjs.com/), it may be + hard to automatically convert the Filebeat input configuration (nested variables, many representations, conditions, + loops). Kindly please to review the output stream configuration and review potential bugs. + +11. Update docs template with sample events. + + The events collected by the agent slightly differ from original, Metricbeat's and Filebeat's, ones. Adjust the event + content manually basing on already migrated integrations (e.g. [MySQL integration](https://github.com/elastic/package-registry/tree/master/dev/import-beats-resources/mysql/docs)) + or copy them once managed to run whole setup with real agent. + +12. Kibana: use `stream.dataset` field instead of `event.dataset`. + + Using `stream.dataset` instead of `event.dataset` also makes queries a lot more efficient as this is a + `constant_keyword`. Make sure that dashboards in your package don't use the `event.dataset` field. If so, + simply replace them with the more efficient one. + +## Testing and validation + +### Run the whole setup + +1. Build docker image with EPR: + + ```bash + $ docker build --rm -t docker.elastic.co/package-registry/package-registry:master . + ``` + + +2. Start testing environment: + ```bash + $ cd testing/environments + $ docker-compose -f snapshot.yml -f local.yml up + ``` + + + The command will boot up a docker cluster with Elasticsearch, Kibana and Package Registry. + +3. Verify that your integration is available (in the right version), e.g. MySQL: http://localhost:8080/search?package=mysql + + ```json + [ + { + "description": "MySQL Integration", + "download": "/epr/mysql/mysql-0.0.1.tar.gz", + "icons": [ + { + "src": "/package/mysql/0.0.1/img/logo_mysql.svg", + "title": "logo mysql", + "size": "32x32", + "type": "image/svg+xml" + } + ], + "name": "mysql", + "path": "/package/mysql/0.0.1", + "title": "MySQL", + "type": "integration", + "version": "0.0.1" + } + ] + ``` + +4. Build agent code: + ```bash + $ cd $GOPATH/src/github.com/elastic/beats/x-pack/elastic-agent + $ PLATFORMS=darwin mage package + ``` + + Unpack the distribution you'd like to use (e.g. tar.gz): + ```bash + $ cd build/distributions/ + $ tar xzf elastic-agent-8.0.0-darwin-x86_64.tar.gz + $ cd elastic-agent-8.0.0-darwin-x86_64/ + ``` + +5. Enroll the agent and start it: + + Use the "Enroll new agent" option in the Kibana UI (Ingest Manager -> Fleet -> Create user and enable Fleet) and run a similar command: + + ```bash + $ ./elastic-agent enroll http://localhost:5601/rel cFhNVlZIRUIxYjhmbFhqNTBoS2o6OUhMWkF4SFJRZmFNZTh3QmtvR1cxZw== + $ ./elastic-agent run + ``` + + The `elastic-agent` will start two other processes - `metricbeat` and `filebeat`. + +6. Run the product you're integrating with (e.g. a docker image with MySQL). + +7. Install package. + + Click out the configuration in the Kibana UI, deploy it and wait for the agent to pick out the updated configuration. + +8. Navigate with Kibana UI to freshly installed dashboards, verify the metrics/logs flow. diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000000..70403966518 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,113 @@ +#!/usr/bin/env groovy + +@Library('apm@current') _ + +pipeline { + agent { label 'ubuntu && immutable' } + environment { + BASE_DIR="src/github.com/elastic/package-registry" + JOB_GIT_CREDENTIALS = "f6c7695a-671e-4f4f-a331-acdce44ff9ba" + PIPELINE_LOG_LEVEL='INFO' + DOCKER_REGISTRY = 'docker.elastic.co' + DOCKER_REGISTRY_SECRET = 'secret/observability-team/ci/docker-registry/prod' + DOCKER_IMG = "${env.DOCKER_REGISTRY}/package-registry/package-registry" + } + options { + timeout(time: 1, unit: 'HOURS') + buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20', daysToKeepStr: '30')) + timestamps() + ansiColor('xterm') + disableResume() + durabilityHint('PERFORMANCE_OPTIMIZED') + rateLimitBuilds(throttle: [count: 60, durationName: 'hour', userBoost: true]) + quietPeriod(10) + } + triggers { + issueCommentTrigger('(?i).*(?:jenkins\\W+)?run\\W+(?:the\\W+)?tests(?:\\W+please)?.*') + } + stages { + /** + Checkout the code and stash it, to use it on other stages. + */ + stage('Checkout') { + steps { + deleteDir() + gitCheckout(basedir: "${BASE_DIR}") + stash allowEmpty: true, name: 'source', useDefaultExcludes: false + } + } + /** + Checks formatting / linting. + */ + stage('Lint') { + steps { + deleteDir() + unstash 'source' + dir("${BASE_DIR}"){ + insideGo{ + sh(label: 'Checks formatting / linting',script: 'mage -debug check ') + } + } + } + } + /** + Build the project from code.. + */ + stage('Build') { + steps { + deleteDir() + unstash 'source' + dir("${BASE_DIR}"){ + insideGo(){ + sh(label: 'Checks formatting / linting',script: 'mage -debug build ') + } + } + } + } + /** + Execute unit tests. + */ + stage('Test') { + steps { + deleteDir() + unstash 'source' + dir("${BASE_DIR}"){ + insideGo(){ + sh(label: 'Runs the (unit) tests',script: 'mage -debug test ') + } + } + } + post { + always { + junit(allowEmptyResults: true, + keepLongStdio: true, + testResults: "${BASE_DIR}/**/junit-*.xml") + } + } + } + } + post { + success { + echoColor(text: '[SUCCESS]', colorfg: 'green', colorbg: 'default') + } + aborted { + echoColor(text: '[ABORTED]', colorfg: 'magenta', colorbg: 'default') + } + failure { + echoColor(text: '[FAILURE]', colorfg: 'red', colorbg: 'default') + } + unstable { + echoColor(text: '[UNSTABLE]', colorfg: 'yellow', colorbg: 'default') + } + } +} + +def insideGo(Closure body){ + def goAgent = docker.build("go-agent", ".ci/jenkins-go-agent") + goAgent.inside(){ + env.HOME="${WORKSPACE}/${BASE_DIR}" + sh(label: 'Go version', script: 'go version') + sh(label: 'Install Mage', script: 'mage -version') + body() + } +} diff --git a/dev/import-beats-resources/mysql/docs/README.md b/dev/import-beats-resources/mysql/docs/README.md new file mode 100644 index 00000000000..68c1d9555cb --- /dev/null +++ b/dev/import-beats-resources/mysql/docs/README.md @@ -0,0 +1,286 @@ +# MySQL Integration + +This integration periodically fetches logs and metrics from [https://www.mysql.com/](MySQL) servers. + +## Compatibility + +The `error` and `slowlog` datasets were tested with logs from MySQL 5.5, 5.7 and 8.0, MariaDB 10.1, 10.2 and 10.3, and Percona 5.7 and 8.0. + +The `galera_status` and `status` datasets were tested with MySQL and Percona 5.7 and 8.0 and are expected to work with all +versions >= 5.7.0. It is also tested with MariaDB 10.2, 10.3 and 10.4. + +## Logs + +### error + +The `error` dataset collects the MySQL error logs. + +{{fields "error"}} + +### slowlog + +The `slowlog` dataset collects the MySQL slow logs. + +{{fields "slowlog"}} + +## Metrics + +### galera_status + +The `galera_status` dataset periodically fetches metrics from [http://galeracluster.com/](Galera)-MySQL cluster servers. + +An example event for `galera_status` looks as following: + +```$json +{ + "@timestamp":"2020-04-20T12:33:24.613Z", + "mysql":{ + "galera_status":{ + "apply":{ + "oooe":0, + "oool":0, + "window":1 + }, + "connected":"ON", + "flow_ctl":{ + "recv":0, + "sent":0, + "paused":0, + "paused_ns":0 + }, + "ready":"ON", + "received":{ + "count":173, + "bytes":152425 + }, + "local":{ + "state":"Synced", + "bf_aborts":0, + "cert_failures":0, + "commits":1325, + "recv":{ + "queue_max":2, + "queue_min":0, + "queue":0, + "queue_avg":0.011561 + }, + "replays":0, + "send":{ + "queue_min":0, + "queue":0, + "queue_avg":0, + "queue_max":1 + } + }, + "evs":{ + "evict":"", + "state":"OPERATIONAL" + }, + "repl":{ + "bytes":1689804, + "data_bytes":1540647, + "keys":4170, + "keys_bytes":63973, + "other_bytes":0, + "count":1331 + }, + "commit":{ + "oooe":0, + "window":1 + }, + "cluster":{ + "conf_id":930, + "size":3, + "status":"Primary" + }, + "last_committed":23944, + "cert":{ + "deps_distance":43.524557, + "index_size":22, + "interval":0 + } + } + }, + "fields":{ + "stream":{ + "type":"metrics", + "dataset":"mysql.galera_status", + "namespace":"default" + } + }, + "ecs":{ + "version":"1.5.0" + }, + "agent":{ + "hostname":"MacBook-Elastic.local", + "id":"ede0be38-46a9-4ffc-8f1e-2ff9195193b6", + "version":"8.0.0", + "type":"metricbeat", + "ephemeral_id":"4c773a2e-16d5-4d86-be49-cfb3573f4f4f" + }, + "event":{ + "dataset":"mysql.galera_status", + "module":"mysql", + "duration":3275482 + }, + "metricset":{ + "name":"galera_status", + "period":10000 + }, + "service":{ + "address":"127.0.0.1:3306", + "type":"mysql" + } +} +``` + +The fields reported are: + +{{fields "galera_status"}} + +### status + +The MySQL `status` dataset collects data from MySQL by running a `SHOW GLOBAL STATUS;` SQL query. This query returns a large number of metrics. + +An example event for `status` looks as following: + +```$json +{ + "@timestamp":"2020-04-20T12:32:54.614Z", + "mysql":{ + "status":{ + "max_used_connections":3, + "queries":479, + "handler":{ + "prepare":0, + "savepoint":0, + "update":0, + "delete":0, + "read":{ + "rnd_next":59604, + "first":8, + "key":6, + "last":0, + "next":1, + "prev":0, + "rnd":0 + }, + "rollback":0, + "write":0, + "commit":5, + "savepoint_rollback":0, + "external_lock":552, + "mrr_init":0 + }, + "aborted":{ + "clients":0, + "connects":0 + }, + "threads":{ + "running":2, + "cached":1, + "created":3, + "connected":2 + }, + "flush_commands":1, + "created":{ + "tmp":{ + "disk_tables":0, + "files":6, + "tables":0 + } + }, + "connections":159, + "command":{ + "insert":0, + "select":155, + "update":0, + "delete":0 + }, + "opened_tables":122, + "binlog":{ + "cache":{ + "use":0, + "disk_use":0 + } + }, + "delayed":{ + "writes":0, + "errors":0, + "insert_threads":0 + }, + "questions":479, + "innodb":{ + "buffer_pool":{ + "read":{ + "ahead_rnd":0, + "requests":1488, + "ahead":0, + "ahead_evicted":0 + }, + "pool":{ + "wait_free":0, + "reads":405 + }, + "write_requests":325, + "bytes":{ + "data":7176192, + "dirty":0 + }, + "pages":{ + "dirty":0, + "flushed":36, + "free":7753, + "misc":0, + "total":8191, + "data":438 + } + } + }, + "bytes":{ + "received":38468, + "sent":1622162 + }, + "open":{ + "streams":0, + "tables":115, + "files":14 + } + } + }, + "event":{ + "dataset":"mysql.status", + "module":"mysql", + "duration":4708776 + }, + "metricset":{ + "name":"status", + "period":10000 + }, + "fields":{ + "stream":{ + "type":"metrics", + "dataset":"mysql.status", + "namespace":"default" + } + }, + "ecs":{ + "version":"1.5.0" + }, + "agent":{ + "id":"ede0be38-46a9-4ffc-8f1e-2ff9195193b6", + "version":"8.0.0", + "type":"metricbeat", + "ephemeral_id":"4c773a2e-16d5-4d86-be49-cfb3573f4f4f", + "hostname":"MacBook-Elastic.local" + }, + "service":{ + "address":"127.0.0.1:3306", + "type":"mysql" + } +} +``` + +The fields reported are: + +{{fields "status"}} diff --git a/dev/import-beats-resources/nginx/docs/README.md b/dev/import-beats-resources/nginx/docs/README.md new file mode 100644 index 00000000000..323b2be4e18 --- /dev/null +++ b/dev/import-beats-resources/nginx/docs/README.md @@ -0,0 +1,102 @@ +# Nginx Integration + +This integration periodically fetches metrics from [https://nginx.org/](Nginx) servers. It can parse access and error +logs created by the HTTP server. + +## Compatibility + +The Nginx `stubstatus` metrics was tested with Nginx 1.9 and are expected to work with all version >= 1.9. +The logs were tested with version 1.10. +On Windows, the module was tested with Nginx installed from the Chocolatey repository. + +## Logs + +**Timezone support** + +This datasource parses logs that don’t contain timezone information. For these logs, the Elastic Agent reads the local +timezone and uses it when parsing to convert the timestamp to UTC. The timezone to be used for parsing is included +in the event in the `event.timezone` field. + +To disable this conversion, the event.timezone field can be removed with the drop_fields processor. + +If logs are originated from systems or applications with a different timezone to the local one, the `event.timezone` +field can be overwritten with the original timezone using the add_fields processor. + +### Access Logs + +Access logs collects the nginx access logs. + +{{fields "access"}} + +### Error Logs + +Error logs collects the nginx error logs. + +{{fields "error"}} + +### Ingress Controller Logs + +Error logs collects the ingress controller logs. + +{{fields "ingress_controller"}} + +## Metrics + +### Stub Status Metrics + +The Nginx stubstatus stream collects data from the Nginx `ngx_http_stub_status` module. It scrapes the server status +data from the web page generated by ngx_http_stub_status. + +This is a default stream. If the host datasource is unconfigured, this stream is enabled by default. + +An example event for nginx looks as following: + +```$json +{ + "@timestamp":"2020-04-28T11:07:58.223Z", + "service":{ + "type":"nginx", + "address":"127.0.0.1:8081" + }, + "nginx":{ + "stubstatus":{ + "waiting":0, + "hostname":"127.0.0.1:8081", + "dropped":0, + "writing":1, + "handled":7339, + "requests":7411, + "reading":0, + "accepts":7339, + "current":10, + "active":1 + } + }, + "stream":{ + "namespace":"default", + "type":"metrics", + "dataset":"nginx.stubstatus" + }, + "ecs":{ + "version":"1.5.0" + }, + "agent":{ + "type":"metricbeat", + "ephemeral_id":"8eb07b4f-df58-4794-8e00-60f1443f33b6", + "hostname":"MacBook-Elastic.local", + "id":"e47f6e4d-5277-46f3-801d-221c7584c604", + "version":"8.0.0" + }, + "event":{ + "module":"nginx", + "duration":1112095, + "dataset":"nginx.stubstatus" + }, + "metricset":{ + "period":10000, + "name":"stubstatus" + } +} +``` + +{{fields "stubstatus"}} diff --git a/dev/import-beats-resources/redis/docs/README.md b/dev/import-beats-resources/redis/docs/README.md new file mode 100644 index 00000000000..e5393c03ca3 --- /dev/null +++ b/dev/import-beats-resources/redis/docs/README.md @@ -0,0 +1,80 @@ +# Redis Integration + +This integration periodically fetches logs and metrics from [https://redis.io/](Redis) servers. + +## Compatibility + +The `log` and `slowlog` datasets were tested with logs from Redis versions 1.2.6, 2.4.6, and 3.0.2, so we expect +compatibility with any version 1.x, 2.x, or 3.x. + +The `info`, `key` and `keyspace` datasets were tested with Redis 3.2.12, 4.0.11 and 5.0-rc4, and are expected to work +with all versions >= 3.0. + +## Logs + +### log + +The `log` dataset collects the Redis standard logs. + +{{fields "log"}} + +### slowlog + +The `slowlog` dataset collects the Redis slow logs. + +{{fields "log"}} + +## Metrics + +### info + +The `info` dataset collects information and statistics from Redis by running the `INFO` command and parsing the returned +result. + +An example event for `info` looks as following: + +```$json +TODO +``` + +The fields reported are: + +{{fields "info"}} + +### key + +The `key` dataset collects information about Redis keys. + +For each key matching one of the configured patterns, an event is sent to Elasticsearch with information about this key, +what includes the type, its length when available, and its TTL. + +Patterns are configured as a list containing these fields: + +* `pattern` (required): pattern for key names, as accepted by the Redis KEYS or SCAN commands. +* `limit` (optional): safeguard when using patterns with wildcards to avoid collecting too many keys (Default: 0, no limit) +* `keyspace` (optional): Identifier of the database to use to look for the keys (Default: 0) + +An example event for `key` looks as following: + +```$json +TODO +``` + +The fields reported are: + +{{fields "key"}} + +### keyspace + +The `keyspace` dataset collects information about the Redis keyspaces. For each keyspace, an event is sent to +Elasticsearch. The keyspace information is fetched from the `INFO` command. + +An example event for `keyspace` looks as following: + +```$json +TODO +``` + +The fields reported are: + +{{fields "keyspace"}} diff --git a/dev/import-beats/README.md b/dev/import-beats/README.md new file mode 100644 index 00000000000..8341cf4a413 --- /dev/null +++ b/dev/import-beats/README.md @@ -0,0 +1,195 @@ +# import-beats + +The script is responsible for importing existing beats modules and transforming +them into integration packages compatible with Elastic Package Registry (EPR). + +The `import-beats` script depends on active Kibana instance, which is used to +migrate existing dashboards to a newer version. + +## Usage + +```bash +$ mage ImportBeats +``` + +... or using `go run` (no need to install `mage`): + +```bash +$ go run dev/import-beats/*.go -help +Usage of /var/folders/gz/dht4sjdx5w9f72knybys10zw0000gn/T/go-build777100057/b001/exe/agent: + -beatsDir string + Path to the beats repository (default "../beats") + -ecsDir string + Path to the Elastic Common Schema repository (default "../ecs") + -euiDir string + Path to the Elastic UI framework repository (default "../eui") + -kibanaDir string + Path to the kibana repository (default "../kibana") + -kibanaHostPort string + Kibana host and port (default "http://localhost:5601") + -kibanaPassword string + Kibana password (default "changeme") + -kibanaUsername string + Kibana username (default "elastic") + -outputDir string + Path to the output directory (default "dev/packages/beats") + -skipKibana + Skip storing Kibana objects +``` + +## Import all packages + +1. Make sure that the following repositories have been fetched locally: +https://github.com/elastic/beats +https://github.com/elastic/ecs +https://github.com/elastic/eui +https://github.com/elastic/kibana +2. Make sure you've the `mage` tool installed. +3. Start Kibana server (make sure the endpoint is accessible: http://localhost:5601/) +4. Run the importing procedure with the following command: + +```bash +$ mage ImportBeats +``` + +## Package import procedure + +This section describes next steps of the `import-beats` script that are performed to build integration packages in +the output directory. + +Keep in mind that the script doesn't clean previously created artifacts, so you may encounter leftovers (detached +dashboards, renamed ingest pipeline, etc.). If you need to preserve a clean state in the output directory (which is +versioned), remove its content before executing the script. + +The script requires few repositories (Kibana, EUI, etc.) to be present, but doesn't require to execute any of build +targets. It depends only on the existing, version content, so simple `git clone` should be enough. + +### Package repository + +The package repository is responsible for building packages - loading package data from sources (Beats modules, Kibana +resources, etc.) and writing them to disk. It supports two types of beats - **logs** and **metrics**. + +#### Load input data from sources + +The script needs to visit and process input data from [beats](https://github.com/elastic/beats), generally logs and +metrics modules. + +Starting with modules, it collects and processes information about module fields, release type, icons, screenshots, +Kibana dashboards and docs. While browsing datasets content, it focuses on fields specific for the dataset, release +type, ingestion pipeline, stream and agent configuration. + +##### Fields + +Fields are extracted from `fields.yml` files and divided into 3 buckets - ECS fields, module fields +and package fields. + +##### Integration title + +The correct spelling makes better impression on users, so the scripts uses `title` property in the module fields +as the proper form. Remember to adjust this value if working on the migration from Beats. + +##### Release type + +Values: _beta, experimental, ga_ + +~~The value depends on definitions in module and dataset fields. The scripts determines the correct release type +for dataset, depending on overall release status for module (e.g. dataset can't be annotated as GA if the entire module +is in beta).~~ + +Currently, all imported packages are created with default value - experimental. + +##### Images + +The script supports two kinds of images - **icons** and **screenshots**. Even though they're stored in different media +formats, they're analyzed to prepare a metadata information (title, size and media type). + +###### Icons + +The icons are loaded from the following sources: Kibana home tutorials and Elastic UI. Icons must be in SVG format and +have defined dimensions (information stored in manifest, used by Kibana). Keep in mind that only icon files referenced +in tutorials are processed. + +###### Screenshots + +The script parses module docs to find and collect all references to screenshots showing Kibana dashboards. + +##### Kibana dashboards + +The script performs a convertion of all existing Kibana dashboards into new format. Packages stores Kibana objects +divided into buckets based on the object type (e.g. dashboards, visualizations, maps). + +Many existing dashboards are compliant with earlier Kibana versions hence they're loaded to the Kibana instance to let +it migrate to the newer format (Kibana instance must be accessible during the importing process). + +Every Kibana object needs to be stored in a decoded form (unpacked JSON format) as it's easier to find changes between +particular revisions. + +There is also a change related to the `event.module` field - the field is no longer available in the integration. +The script adjusts dashboards automatically by replacing all references with a special clause including all datasets, +e.g.: + +_The module "duck" contains 3 datasets: foo, bar, baz._ + +The `event.module = duck` will be transformed into +`(event.dataset = duck.foo OR event.dataset = duck.bar OR event.dataset = duck.baz)`. + +##### Dependency requirements + +The scripts parses available Kibana objects for information about supported versions and determines what is +the minimal required Kibana version. + +The required version of the Elasticsearch is hardcoded (`>7.0.1`). + +##### Documentation + +Documentation in the Beats repository refers to modules, metricsets and filesets. Unfortunately it doesn't fit +well in the concept of integrations, so all documentation pages need to be adjusted. + +Every integration may have a doc template defined, so that the script can pick it up while building packages. +The template can refer to functions, e.g. to render a table with fields used by a dataset. + +##### Ingest pipelines + +If the fileset used an ingest pipeline, the script includes it in the target package, but renamed to `default.json` or +`default.yml`. + +##### Streams + +Stream configuration defines available Metricbeat and Filebeat settings used to reconfigure the integration. + +Depending on the dataset type, the configuration can be imported from the following files: `_meta/config.*.yml` +for Metricbeat or `manifest.yml` for Filebeat. The new format provides additional properties (required, show_user, +title, multi), which can be used to provide better user experience in Kibana UI. Unfortunately the script can't +detect these properties automatically, so manual adjustments will be required. + +Metricbeat configuration might be hard to extract because of missing variable definitions (`_meta/config.*.yml` are +like samples). The script analyzes the `_meta/config.*.yml` files and tries to deduce, which variables belong to +particular metricsets. + +##### Agent configuration + +The agent configuration is a template used by Kibana to prepare the final configuration deployed on agents. The script +needs to convert the Beats configuration as the templating engine has changed from the standard Golang one to +the [handlebarsjs](https://handlebarsjs.com/). The script doesn't run any advanced syntax analysis and bases only on +simple find-and-replace functions (which actually covers vast majority of cases). + +At the moment, a developer is obliged to verify the convertion result. + +#### Write package content to disk + +The script writes down all packages to the specified output directory. As it doesn't remove existing resources, it's +safer to clean the output directory first. This is the moment when copying resources, rendering doc templates and +creating required directories happens. + +At the moment all packages are annotated with version `0.0.1`. + +## Troubleshooting + +### Importing process takes too long + +While developeing, you can try to perform the migration with skipping migration of all Kibana objects, +as this is the most time consuming part of whole process: + +```bash +$ SKIP_KIBANA=true mage ImportBeats +``` diff --git a/dev/import-beats/agent.go b/dev/import-beats/agent.go new file mode 100644 index 00000000000..6dc983c1254 --- /dev/null +++ b/dev/import-beats/agent.go @@ -0,0 +1,78 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "bytes" + "fmt" + "strings" + + "github.com/elastic/package-registry/util" +) + +type agentContent struct { + streams []streamContent +} + +type streamContent struct { + targetFileName string + body []byte +} + +func extractInputConfigFilename(configFilePath string) string { + i := strings.LastIndex(configFilePath, "/") + return configFilePath[i+1:] +} + +func createAgentContentForMetrics(moduleName, datasetName string, streams []util.Stream) (agentContent, error) { + inputName := moduleName + "/metrics" + vars := extractVarsFromStream(streams, inputName) + + var buffer bytes.Buffer + buffer.WriteString(fmt.Sprintf("metricsets: [\"%s\"]\n", datasetName)) + + for _, aVar := range vars { + variableName := aVar.Name + + if !isAgentConfigOptionRequired(variableName) { + buffer.WriteString(fmt.Sprintf("{{#if %s}}\n", variableName)) + } + + if isArrayConfigOption(variableName) { + buffer.WriteString(fmt.Sprintf("%s:\n{{#each %s}}\n - {{this}}\n{{/each}}\n", variableName, variableName)) + } else { + buffer.WriteString(fmt.Sprintf("%s: {{%s}}\n", variableName, variableName)) + } + + if !isAgentConfigOptionRequired(variableName) { + buffer.WriteString("{{/if}}\n") + } + } + return agentContent{ + streams: []streamContent{ + { + targetFileName: "stream.yml.hbs", + body: buffer.Bytes(), + }, + }, + }, nil +} + +func extractVarsFromStream(streams []util.Stream, inputName string) []util.Variable { + for _, stream := range streams { + if stream.Input == inputName { + return stream.Vars + } + } + return []util.Variable{} +} + +func isAgentConfigOptionRequired(optionName string) bool { + return optionName == "hosts" || optionName == "period" +} + +func isArrayConfigOption(optionName string) bool { + return optionName == "hosts" +} diff --git a/dev/import-beats/common.go b/dev/import-beats/common.go new file mode 100644 index 00000000000..44d96b2ea6d --- /dev/null +++ b/dev/import-beats/common.go @@ -0,0 +1,175 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "errors" + "fmt" + "strings" +) + +// Source code origin: +// github.com/elastic/beats/libbeat/common/mapstr.go + +var ( + // errKeyNotFound indicates that the specified key was not found. + errKeyNotFound = errors.New("key not found") +) + +type mapStr map[string]interface{} + +// getValue gets a value from the map. If the key does not exist then an error +// is returned. +func (m mapStr) getValue(key string) (interface{}, error) { + _, _, v, found, err := mapFind(key, m, false) + if err != nil { + return nil, err + } + if !found { + return nil, errKeyNotFound + } + return v, nil +} + +// put associates the specified value with the specified key. If the map +// previously contained a mapping for the key, the old value is replaced and +// returned. The key can be expressed in dot-notation (e.g. x.y) to put a value +// into a nested map. +// +// If you need insert keys containing dots then you must use bracket notation +// to insert values (e.g. m[key] = value). +func (m mapStr) put(key string, value interface{}) (interface{}, error) { + // XXX `safemapstr.Put` mimics this implementation, both should be updated to have similar behavior + k, d, old, _, err := mapFind(key, m, true) + if err != nil { + return nil, err + } + + d[k] = value + return old, nil +} + +// delete deletes the given key from the map. +func (m mapStr) delete(key string) error { + k, d, _, found, err := mapFind(key, m, false) + if err != nil { + return err + } + if !found { + return errKeyNotFound + } + + delete(d, k) + return nil +} + +// flatten flattens the given MapStr and returns a flat MapStr. +// +// Example: +// "hello": MapStr{"world": "test" } +// +// This is converted to: +// "hello.world": "test" +// +// This can be useful for testing or logging. +func (m mapStr) flatten() mapStr { + return flatten("", m, mapStr{}) +} + +// mapFind iterates a mapStr based on a the given dotted key, finding the final +// subMap and subKey to operate on. +// An error is returned if some intermediate is no map or the key doesn't exist. +// If createMissing is set to true, intermediate maps are created. +// The final map and un-dotted key to run further operations on are returned in +// subKey and subMap. The subMap already contains a value for subKey, the +// present flag is set to true and the oldValue return will hold +// the original value. +func mapFind( + key string, + data mapStr, + createMissing bool, +) (subKey string, subMap mapStr, oldValue interface{}, present bool, err error) { + // XXX `safemapstr.mapFind` mimics this implementation, both should be updated to have similar behavior + + for { + // Fast path, key is present as is. + if v, exists := data[key]; exists { + return key, data, v, true, nil + } + + idx := strings.IndexRune(key, '.') + if idx < 0 { + return key, data, nil, false, nil + } + + k := key[:idx] + d, exists := data[k] + if !exists { + if createMissing { + d = mapStr{} + data[k] = d + } else { + return "", nil, nil, false, errKeyNotFound + } + } + + v, err := toMapStr(d) + if err != nil { + return "", nil, nil, false, err + } + + // advance to sub-map + key = key[idx+1:] + data = v + } +} + +// flatten is a helper for Flatten. See docs for flatten. For convenience the +// out parameter is returned. +func flatten(prefix string, in, out mapStr) mapStr { + for k, v := range in { + var fullKey string + if prefix == "" { + fullKey = k + } else { + fullKey = prefix + "." + k + } + + if m, ok := tryToMapStr(v); ok { + flatten(fullKey, m, out) + } else { + out[fullKey] = v + } + } + return out +} + +// tomapStr performs a type assertion on v and returns a mapStr. v can be either +// a mapStr or a map[string]interface{}. If it's any other type or nil then +// an error is returned. +func toMapStr(v interface{}) (mapStr, error) { + m, ok := tryToMapStr(v) + if !ok { + return nil, fmt.Errorf("expected map but type is %v", v) + } + return m, nil +} + +func tryToMapStr(v interface{}) (mapStr, bool) { + switch m := v.(type) { + case mapStr: + return m, true + case map[string]interface{}: + return m, true + case map[interface{}]interface{}: + n := map[string]interface{}{} + for k, v := range m { + n[k.(string)] = v + } + return n, true + default: + return nil, false + } +} diff --git a/dev/import-beats/datasets.go b/dev/import-beats/datasets.go new file mode 100644 index 00000000000..cc9831a2373 --- /dev/null +++ b/dev/import-beats/datasets.go @@ -0,0 +1,137 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + + "github.com/pkg/errors" + + "github.com/elastic/package-registry/util" +) + +type datasetContent struct { + name string + beatType string + + manifest util.DataSet + + agent agentContent + elasticsearch elasticsearchContent + fields fieldsContent +} + +type datasetContentArray []datasetContent + +func (dca datasetContentArray) names() []string { + var names []string + for _, dc := range dca { + names = append(names, dc.name) + } + return names +} + +type datasetManifestMultiplePipelines struct { + IngestPipeline []string `yaml:"ingest_pipeline"` +} + +type datasetManifestSinglePipeline struct { + IngestPipeline string `yaml:"ingest_pipeline"` +} + +func createDatasets(beatType, modulePath, moduleName, moduleTitle, moduleRelease string, moduleFields []fieldDefinition, + filteredEcsModuleFieldNames []string, ecsFields fieldDefinitionArray) (datasetContentArray, error) { + datasetDirs, err := ioutil.ReadDir(modulePath) + if err != nil { + return nil, errors.Wrapf(err, "cannot read module directory %s", modulePath) + } + + var contents []datasetContent + for _, datasetDir := range datasetDirs { + if !datasetDir.IsDir() { + continue + } + datasetName := datasetDir.Name() + + if datasetName == "_meta" { + continue + } + + datasetPath := filepath.Join(modulePath, datasetName) + _, err := os.Stat(filepath.Join(datasetPath, "_meta")) + if os.IsNotExist(err) { + _, err = os.Stat(filepath.Join(datasetPath, "manifest.yml")) + if os.IsNotExist(err) { + log.Printf("\t%s: not a valid dataset, skipped", datasetName) + continue + } + } + + log.Printf("\t%s: dataset found", datasetName) + + // fields + datasetFields, err := loadDatasetFields(modulePath, moduleName, datasetName) + if err != nil { + return nil, errors.Wrapf(err, "loading dataset fields failed (modulePath: %s, datasetName: %s)", + modulePath, datasetName) + } + datasetFields, filteredEcsDatasetFieldNames, err := filterMigratedFields(datasetFields, ecsFields.names()) + if err != nil { + return nil, errors.Wrapf(err, "filtering uncommon migrated failed (modulePath: %s, datasetName: %s)", + modulePath, datasetName) + } + + foundEcsFieldNames := uniqueStringValues(append(filteredEcsModuleFieldNames, filteredEcsDatasetFieldNames...)) + ecsFields := filterEcsFields(ecsFields, foundEcsFieldNames) + + fieldsFiles := map[string]fieldDefinitionArray{} + if len(ecsFields) > 0 { + fieldsFiles["ecs.yml"] = ecsFields + } + if len(moduleFields) > 0 { + fieldsFiles["package-fields.yml"] = moduleFields + } + if len(datasetFields) > 0 { + fieldsFiles["fields.yml"] = datasetFields + } + fields := fieldsContent{ + files: fieldsFiles, + } + + // elasticsearch + elasticsearch, err := loadElasticsearchContent(datasetPath) + if err != nil { + return nil, errors.Wrapf(err, "loading elasticsearch content failed (datasetPath: %s)", datasetPath) + } + + // streams and agents + streams, agent, err := createStreams(modulePath, moduleName, moduleTitle, datasetName, beatType) + if err != nil { + return nil, errors.Wrapf(err, "creating streams failed (datasetPath: %s)", datasetPath) + } + + // manifest + manifest := util.DataSet{ + Title: fmt.Sprintf("%s %s %s", moduleTitle, datasetName, beatType), + Release: "experimental", + Type: beatType, + Streams: streams, + } + + contents = append(contents, datasetContent{ + name: datasetName, + beatType: beatType, + manifest: manifest, + agent: agent, + elasticsearch: elasticsearch, + fields: fields, + }) + } + return contents, nil +} diff --git a/dev/import-beats/datasources.go b/dev/import-beats/datasources.go new file mode 100644 index 00000000000..e1e2a80289f --- /dev/null +++ b/dev/import-beats/datasources.go @@ -0,0 +1,136 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "sort" + "strings" + + "github.com/elastic/package-registry/util" +) + +type datasourceContent struct { + moduleName string + moduleTitle string + + inputs map[string]datasourceInput // map[inputType].. +} + +type datasourceInput struct { + datasetNames []string + packageType string + vars []util.Variable +} + +func (ds datasourceContent) toMetadataDatasources() []util.Datasource { + var packageTypes []string + for _, input := range ds.inputs { + packageTypes = append(packageTypes, input.packageType) + } + sort.Strings(packageTypes) + + var title, description string + if len(ds.inputs) == 2 { + title = toDatasourceTitleForTwoTypes(ds.moduleTitle, packageTypes[0], packageTypes[1]) + description = toDatasourceDescriptionForTwoTypes(ds.moduleTitle, packageTypes[0], packageTypes[1]) + } else { + title = toDatasourceTitle(ds.moduleTitle, packageTypes[0]) + description = toDatasourceDescription(ds.moduleTitle, packageTypes[0]) + } + + var inputs []util.Input + for _, packageType := range packageTypes { + for inputType, input := range ds.inputs { + if input.packageType == packageType { + inputs = append(inputs, util.Input{ + Type: inputType, + Title: toDatasourceInputTitle(ds.moduleTitle, packageType), + Description: toDatasourceInputDescription(ds.moduleTitle, packageType, ds.inputs[inputType].datasetNames), + Vars: input.vars, + }) + } + } + } + return []util.Datasource{ + { + Name: ds.moduleName, + Title: title, + Description: description, + Inputs: inputs, + }, + } +} + +type updateDatasourcesParameters struct { + moduleName string + moduleTitle string + packageType string + + datasetNames []string + inputVars map[string][]util.Variable +} + +func updateDatasource(dsc datasourceContent, params updateDatasourcesParameters) (datasourceContent, error) { + dsc.moduleName = params.moduleName + dsc.moduleTitle = params.moduleTitle + + if dsc.inputs == nil { + dsc.inputs = map[string]datasourceInput{} + } + + inputType := params.packageType + if inputType == "metrics" { + inputType = fmt.Sprintf("%s/%s", dsc.moduleName, inputType) + } + + dsc.inputs[inputType] = datasourceInput{ + datasetNames: params.datasetNames, + packageType: params.packageType, + vars: params.inputVars[inputType], + } + return dsc, nil +} + +func toDatasourceTitle(moduleTitle, packageType string) string { + return fmt.Sprintf("%s %s", moduleTitle, packageType) +} + +func toDatasourceDescription(moduleTitle, packageType string) string { + return fmt.Sprintf("Collect %s from %s instances", packageType, moduleTitle) +} + +func toDatasourceTitleForTwoTypes(moduleTitle, firstPackageType, secondPackageType string) string { + return fmt.Sprintf("%s %s and %s", moduleTitle, firstPackageType, secondPackageType) +} + +func toDatasourceDescriptionForTwoTypes(moduleTitle, firstPackageType, secondPackageType string) string { + return fmt.Sprintf("Collect %s and %s from %s instances", firstPackageType, secondPackageType, moduleTitle) +} + +func toDatasourceInputTitle(moduleTitle, packageType string) string { + return fmt.Sprintf("Collect %s from %s instances", packageType, moduleTitle) +} + +func toDatasourceInputDescription(moduleTitle, packageType string, datasets []string) string { + firstPart := datasets[:len(datasets)-1] + secondPart := datasets[len(datasets)-1:] + + var description strings.Builder + description.WriteString("Collecting ") + description.WriteString(moduleTitle) + description.WriteString(" ") + + if len(firstPart) > 0 { + fp := strings.Join(firstPart, ", ") + description.WriteString(fp) + description.WriteString(" and ") + } + + description.WriteString(secondPart[0]) + description.WriteString(" ") + description.WriteString(packageType) + return description.String() +} diff --git a/dev/import-beats/docs.go b/dev/import-beats/docs.go new file mode 100644 index 00000000000..41b4aa0ca11 --- /dev/null +++ b/dev/import-beats/docs.go @@ -0,0 +1,144 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "text/template" + + "github.com/pkg/errors" +) + +var emptyReadmeTemplate = template.Must(template.New("README.md").Parse("TODO")) + +type fieldsTableRecord struct { + name string + description string + aType string +} + +type docContent struct { + fileName string + templatePath string +} + +func createDocTemplates(packageDocsPath string) ([]docContent, error) { + readmePath := filepath.Join(packageDocsPath, "README.md") + _, err := os.Stat(readmePath) + if err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "reading README template failed") + } + if os.IsNotExist(err) { + readmePath = "" + } + return []docContent{ + {fileName: "README.md", templatePath: readmePath}, + }, nil +} + +func renderExportedFields(packageDataset string, datasets datasetContentArray) (string, error) { + for _, dataset := range datasets { + if packageDataset == dataset.name { + var buffer strings.Builder + buffer.WriteString("**Exported fields**") + buffer.WriteString("\n\n") + + collected, err := collectFields(dataset.fields) + if err != nil { + return "", errors.Wrapf(err, "collecting fields failed") + } + + if len(collected) == 0 { + buffer.WriteString("(no fields available)") + return buffer.String(), nil + } + + buffer.WriteString("| Field | Description | Type |\n") + buffer.WriteString("|---|---|---|\n") + for _, c := range collected { + description := strings.TrimSpace(strings.ReplaceAll(c.description, "\n", " ")) + buffer.WriteString(fmt.Sprintf("| %s | %s | %s |\n", c.name, description, c.aType)) + } + return buffer.String(), nil + } + } + return "", fmt.Errorf("missing dataset: %s", packageDataset) +} + +func collectFields(content fieldsContent) ([]fieldsTableRecord, error) { + var records []fieldsTableRecord + for fileName, fieldsFile := range content.files { + r, err := collectFieldsFromFile(fileName, fieldsFile) + if err != nil { + return nil, errors.Wrapf(err, "collecting fields from file failed") + } + records = append(records, r...) + } + + sort.Slice(records, func(i, j int) bool { + return sort.StringsAreSorted([]string{records[i].name, records[j].name}) + }) + return uniqueTableRecords(records), nil +} + +func uniqueTableRecords(records []fieldsTableRecord) []fieldsTableRecord { + fieldNames := make(map[string]bool) + var unique []fieldsTableRecord + for _, r := range records { + if _, ok := fieldNames[r.name]; !ok { + fieldNames[r.name] = true + unique = append(unique, r) + } + } + return unique +} + +func collectFieldsFromFile(fileName string, fieldDefinitions []fieldDefinition) ([]fieldsTableRecord, error) { + var records []fieldsTableRecord + + root := fieldDefinitions + if isPackageFields(fileName) { + root = fieldDefinitions[0].Fields + } + + var err error + for _, f := range root { + records, err = visitFields("", f, records) + if err != nil { + return nil, errors.Wrapf(err, "visiting fields failed") + } + } + return records, nil +} + +func visitFields(namePrefix string, f fieldDefinition, records []fieldsTableRecord) ([]fieldsTableRecord, error) { + var name = namePrefix + if namePrefix != "" { + name += "." + } + name += f.Name + + if len(f.Fields) == 0 && f.Type != "group" { + records = append(records, fieldsTableRecord{ + name: name, + description: f.Description, + aType: f.Type, + }) + return records, nil + } + + var err error + for _, fieldEntry := range f.Fields { + records, err = visitFields(name, fieldEntry, records) + if err != nil { + return nil, errors.Wrapf(err, "recursive visiting fields failed (namePrefix: %s)", namePrefix) + } + } + return records, nil +} diff --git a/dev/import-beats/elasticsearch.go b/dev/import-beats/elasticsearch.go new file mode 100644 index 00000000000..047151f2dcd --- /dev/null +++ b/dev/import-beats/elasticsearch.go @@ -0,0 +1,177 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" +) + +type elasticsearchContent struct { + ingestPipelines []ingestPipelineContent +} + +type ingestPipelineContent struct { + targetFileName string + body []byte +} + +var ( + reUnsupportedIfInPipeline = regexp.MustCompile("{<[ ]{0,1}if[^(>})]+>}") + reUnsupportedIngestPipelineInPipeline = regexp.MustCompile("('|\"){< (IngestPipeline).+>}('|\")") + reUnsupportedPlaceholderInPipeline = regexp.MustCompile("{<.+>}") +) + +func loadElasticsearchContent(datasetPath string) (elasticsearchContent, error) { + var esc elasticsearchContent + + datasetManifestPath := filepath.Join(datasetPath, "manifest.yml") + datasetManifestFile, err := ioutil.ReadFile(datasetManifestPath) + if os.IsNotExist(err) { + return elasticsearchContent{}, nil // no manifest.yml file found, + } + if err != nil { + return elasticsearchContent{}, errors.Wrapf(err, "reading dataset manifest file failed (path: %s)", datasetManifestPath) + } + + var ingestPipelines []string + var dmsp datasetManifestSinglePipeline + err = yaml.Unmarshal(datasetManifestFile, &dmsp) + if err == nil { + if len(dmsp.IngestPipeline) > 0 { + ingestPipelines = append(ingestPipelines, dmsp.IngestPipeline) + } + } else { + var dmmp datasetManifestMultiplePipelines + err = yaml.Unmarshal(datasetManifestFile, &dmmp) + if err != nil { + return elasticsearchContent{}, errors.Wrapf(err, "unmarshalling dataset manifest file failed (path: %s)", datasetManifestPath) + } + + if len(dmmp.IngestPipeline) > 0 { + ingestPipelines = append(ingestPipelines, dmmp.IngestPipeline...) + } + } + + for _, ingestPipeline := range ingestPipelines { + ingestPipeline = ensurePipelineFormat(ingestPipeline) + + log.Printf("\tingest-pipeline found: %s", ingestPipeline) + + var targetFileName string + if len(ingestPipelines) == 1 { + targetFileName, err = buildSingleIngestPipelineTargetName(ingestPipeline) + if err != nil { + return elasticsearchContent{}, errors.Wrapf(err, "can't build single ingest pipeline target name (path: %s)", ingestPipeline) + } + } else { + targetFileName, err = determineIngestPipelineTargetName(ingestPipeline) + if err != nil { + return elasticsearchContent{}, errors.Wrapf(err, "can't determine ingest pipeline target name (path: %s)", ingestPipeline) + } + } + + pipelinePath := filepath.Join(datasetPath, ingestPipeline) + body, err := ioutil.ReadFile(pipelinePath) + if err != nil { + return elasticsearchContent{}, errors.Wrapf(err, "reading pipeline body failed (path: %s)", pipelinePath) + } + + // Fix missing "---" at the beginning of the YAML pipeline. + if strings.HasSuffix(targetFileName, ".yml") && bytes.Index(body, []byte("---")) != 0 { + body = append([]byte("---\n"), body...) + } + + ipc := ingestPipelineContent{ + targetFileName: targetFileName, + body: adjustUnsupportedStructuresInPipeline(body), + } + + err = validateIngestPipeline(ipc) + if err != nil { + return elasticsearchContent{}, + errors.Wrapf(err, "validation of modified ingest pipeline failed (original path: %s)", pipelinePath) + } + + esc.ingestPipelines = append(esc.ingestPipelines, ipc) + } + + return esc, nil +} + +func buildSingleIngestPipelineTargetName(path string) (string, error) { + _, ext, err := splitFilenameExt(path) + if err != nil { + return "", errors.Wrapf(err, "processing filename failed (path: %s)", path) + } + return "default." + ext, nil +} + +func ensurePipelineFormat(ingestPipeline string) string { + if strings.Contains(ingestPipeline, "{{.format}}") { + ingestPipeline = strings.ReplaceAll(ingestPipeline, "{{.format}}", "json") + } + return ingestPipeline +} + +func determineIngestPipelineTargetName(path string) (string, error) { + name, ext, err := splitFilenameExt(path) + if err != nil { + return "", errors.Wrapf(err, "processing filename failed (path: %s)", path) + } + + if name == "pipeline" || name == "pipeline-entry" { + return "default." + ext, nil + } + return fmt.Sprintf("%s.%s", name, ext), nil +} + +func adjustUnsupportedStructuresInPipeline(data []byte) []byte { + data = reUnsupportedIfInPipeline.ReplaceAll(data, []byte{}) + data = bytes.ReplaceAll(data, []byte("{< end >}"), []byte{}) + + data = reUnsupportedIngestPipelineInPipeline.ReplaceAllFunc(data, func(found []byte) []byte { + found = bytes.ReplaceAll(found, []byte("{<"), []byte("{{")) + found = bytes.ReplaceAll(found, []byte(">}"), []byte("}}")) + + if found[0] == '"' { + found = bytes.ReplaceAll(found, []byte(`"`), []byte(`'`)) + found[0] = '"' + found[len(found)-1] = '"' + } + return found + }) + + data = reUnsupportedPlaceholderInPipeline.ReplaceAll(data, []byte("FIX_ME")) + return data +} + +func validateIngestPipeline(content ingestPipelineContent) error { + _, ext, err := splitFilenameExt(content.targetFileName) + if err != nil { + return errors.Wrapf(err, "processing filename failed (path: %s)", content.targetFileName) + } + + var m mapStr + switch ext { + case "json": + err = json.Unmarshal(content.body, &m) + case "yml": + err = yaml.Unmarshal(content.body, &m) + default: + return fmt.Errorf("unsupported pipeline extension (path: %s)", content.targetFileName) + } + return err +} diff --git a/dev/import-beats/fields.go b/dev/import-beats/fields.go new file mode 100644 index 00000000000..ac02c070634 --- /dev/null +++ b/dev/import-beats/fields.go @@ -0,0 +1,250 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" +) + +type fieldsContent struct { + files map[string]fieldDefinitionArray +} + +type fieldDefinition struct { + Name string `yaml:"name,omitempty"` + Key string `yaml:"key,omitempty"` + Title string `yaml:"title,omitempty"` + Group *int `yaml:"group,omitempty"` + Level string `yaml:"level,omitempty"` + Required *bool `yaml:"required,omitempty"` + Type string `yaml:"type,omitempty"` + Format string `yaml:"format,omitempty"` + Description string `yaml:"description,omitempty"` + Release string `yaml:"release,omitempty"` + Alias string `yaml:"alias,omitempty"` + Path string `yaml:"path,omitempty"` + Footnote string `yaml:"footnote,omitempty"` + // Example is not consistent in ECS schema (either single field or array) + //Example string `yaml:"example,omitempty"` + IgnoreAbove *int `yaml:"ignore_above,omitempty"` + MultiFields []multiFieldDefinition `yaml:"multi_fields,omitempty"` + Fields fieldDefinitionArray `yaml:"fields,omitempty"` + Migration *bool `yaml:"migration,omitempty"` + + skipped bool +} + +type fieldDefinitionArray []fieldDefinition + +func (fda fieldDefinitionArray) names() []string { + var names []string + for _, f := range fda { + names = append(names, collectFieldNames("", f)...) + } + return names +} + +func (fda fieldDefinitionArray) stripped() fieldDefinitionArray { + var arr fieldDefinitionArray + for _, f := range fda { + stripped := f + if f.Type == "group" { + stripped.Description = "" + } + stripped.Fields = stripped.Fields.stripped() + arr = append(arr, stripped) + } + return arr +} + +func collectFieldNames(namePrefix string, f fieldDefinition) []string { + if namePrefix != "" { + namePrefix = namePrefix + "." + f.Name + } else { + namePrefix = f.Name + } + + if len(f.Fields) == 0 { + return []string{namePrefix} + } + + var collected []string + for _, child := range f.Fields { + collected = append(collected, collectFieldNames(namePrefix, child)...) + } + return collected +} + +type multiFieldDefinition struct { + Name string `yaml:"name,omitempty"` + Type string `yaml:"type,omitempty"` + Norms *bool `yaml:"norms,omitempty"` + DefaultField *bool `yaml:"default_field,omitempty"` +} + +func loadEcsFields(ecsDir string) ([]fieldDefinition, error) { + path := filepath.Join(ecsDir, "generated/beats/fields.ecs.yml") + fs, err := loadFieldsFile(path) + if err != nil { + return nil, errors.Wrapf(err, "loading ECS fields file failed") + } + if len(fs) != 1 { + return nil, errors.Wrapf(err, "expected single root field") + } + return fs[0].Fields, nil +} + +func loadModuleFields(modulePath string) ([]fieldDefinition, error) { + path := filepath.Join(modulePath, "_meta", "fields.yml") + fs, err := loadFieldsFile(path) + if err != nil { + return nil, errors.Wrapf(err, "loading module fields file failed") + } + if len(fs) != 1 { + return nil, errors.Wrapf(err, "expected single root field") + } + return fs, nil +} + +func loadDatasetFields(modulePath, moduleName, datasetName string) ([]fieldDefinition, error) { + fieldsPath := filepath.Join(modulePath, datasetName, "_meta", "fields.yml") + fs, err := loadFieldsFile(fieldsPath) + if err != nil { + return nil, errors.Wrapf(err, "loading dataset fields file failed") + } + for i, f := range fs { + fs[i].Name = fmt.Sprintf("%s.%s", moduleName, f.Name) + } + + fieldsEpr := filepath.Join(modulePath, datasetName, "_meta", "fields.epr.yml") + efs, err := loadFieldsFile(fieldsEpr) + if err != nil { + return nil, errors.Wrapf(err, "loading fields.epr.yml file failed") + } + + fs = append(fs, efs...) + return fs, nil +} + +func loadFieldsFile(path string) ([]fieldDefinition, error) { + fields, err := ioutil.ReadFile(path) + if os.IsNotExist(err) { + return []fieldDefinition{}, nil // return empty array, this is a valid state + } + if err != nil { + return nil, errors.Wrapf(err, "reading fields failed (path: %s)", path) + } + + var fs fieldDefinitionArray + err = yaml.Unmarshal(fields, &fs) + if err != nil { + return nil, errors.Wrapf(err, "unmarshalling fields file failed (path: %s)", path) + } + fs = loadDefaultFieldValues(fs) + return fs, nil +} + +func loadDefaultFieldValues(fs fieldDefinitionArray) fieldDefinitionArray { + var withDefaults fieldDefinitionArray + for _, f := range fs { + if f.Type == "" { + f.Type = "keyword" + } + f.Fields = loadDefaultFieldValues(f.Fields) + withDefaults = append(withDefaults, f) + } + return withDefaults +} + +// filterMigratedFields method filters out fields with "migration: true" property or if it's defined in ECS. +// It returns a migrated fields file and found ECS fields. +func filterMigratedFields(fields []fieldDefinition, ecsFieldNames []string) ([]fieldDefinition, []string, error) { + var filteredEcsFieldNames []string + for i, f := range fields { + fields[i], filteredEcsFieldNames = visitFieldForFilteringMigrated(f, ecsFieldNames, filteredEcsFieldNames) + } + return fields, filteredEcsFieldNames, nil +} + +func visitFieldForFilteringMigrated(f fieldDefinition, ecsFieldNames, filteredEcsFieldNames []string) (fieldDefinition, []string) { + if len(f.Fields) == 0 { + // this field is not a group entry + if f.Type == "alias" { + if f.Migration != nil && *f.Migration { + f.skipped = true // skip the field + } + + for _, ecsFieldName := range ecsFieldNames { + if ecsFieldName == f.Path { + filteredEcsFieldNames = append(filteredEcsFieldNames, ecsFieldName) + f.skipped = true + break + } + } + } + return f, filteredEcsFieldNames + } + + var updated fieldDefinitionArray + for _, fieldsEntry := range f.Fields { + var v fieldDefinition + v, filteredEcsFieldNames = visitFieldForFilteringMigrated(fieldsEntry, ecsFieldNames, filteredEcsFieldNames) + if !v.skipped { + updated = append(updated, v) + } + } + f.Fields = updated + return f, filteredEcsFieldNames +} + +func isPackageFields(fileName string) bool { + return fileName == "package-fields.yml" +} + +func filterEcsFields(ecsFields fieldDefinitionArray, filteredNames []string) fieldDefinitionArray { + var filteredFields fieldDefinitionArray + for _, f := range ecsFields { + visited, checked := visitEcsFieldsToFilter("", f, filteredNames) + if checked { + filteredFields = append(filteredFields, visited) + } + } + return filteredFields +} + +func visitEcsFieldsToFilter(namePrefix string, f fieldDefinition, filteredNames []string) (fieldDefinition, bool) { + var name = namePrefix + if namePrefix != "" { + name += "." + } + name += f.Name + + if len(f.Fields) == 0 && f.Type != "group" { + for _, fn := range filteredNames { + if fn == name { + return f, true + } + } + return f, false + } + + var checked bool + var checkedFields fieldDefinitionArray + for _, fieldEntry := range f.Fields { + visited, fieldChecked := visitEcsFieldsToFilter(name, fieldEntry, filteredNames) + if fieldChecked { + checkedFields = append(checkedFields, visited) + checked = true + } + } + f.Fields = checkedFields + return f, checked +} diff --git a/dev/import-beats/files.go b/dev/import-beats/files.go new file mode 100644 index 00000000000..27de68c8428 --- /dev/null +++ b/dev/import-beats/files.go @@ -0,0 +1,26 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "strings" +) + +func splitFilenameExt(path string) (string, string, error) { + fileName := path + if strings.Contains(path, "/") { + fileName = path[strings.LastIndex(path, "/")+1:] + } + + lastDot := strings.LastIndex(fileName, ".") + if lastDot == -1 { + return "", "", fmt.Errorf("filename doesn't have an extension") + } + + fileNameWithoutExt := fileName[:lastDot] + fileExt := fileName[lastDot+1:] + return fileNameWithoutExt, fileExt, nil +} diff --git a/dev/import-beats/icons.go b/dev/import-beats/icons.go new file mode 100644 index 00000000000..cd1b40346cc --- /dev/null +++ b/dev/import-beats/icons.go @@ -0,0 +1,169 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "bufio" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/pkg/errors" +) + +const ( + tutorialsPath = "src/plugins/home/server/tutorials" + kibanaLogosPath = "src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos" +) + +var ( + errIconNotFound = errors.New("icon not found") + iconRe = regexp.MustCompile(`euiIconType: '[^']+'`) + + aliasedModuleNames = map[string]string{ + "redisenterprise": "redis", + "php_fpm": "php", + "postgresql": "postgres", + "appsearch": "app_search", + "googlecloud": "gcp", + } +) + +type iconRepository struct { + icons map[string]string +} + +func newIconRepository(euiDir, kibanaDir string) (*iconRepository, error) { + icons, err := populateIconRepository(euiDir, kibanaDir) + if err != nil { + return nil, errors.Wrapf(err, "populating icon repository failed") + } + return &iconRepository{icons: icons}, nil +} + +func populateIconRepository(euiDir, kibanaDir string) (map[string]string, error) { + log.Println("Populate icon registry") + + kibanaIconRefs, err := retrieveIconPathFromTutorials(kibanaDir) + if err != nil { + return nil, errors.Wrapf(err, "retrieving icon references failed") + } + + euiRefs, err := retrieveIconPathFromEUI(euiDir) + if err != nil { + return nil, errors.Wrapf(err, "collecting icon data failed") + } + + refs := map[string]string{} + for k, v := range kibanaIconRefs { + refs[k] = v + } + for k, v := range euiRefs { + refs[k] = v + } + return refs, nil +} + +func retrieveIconPathFromTutorials(kibanaDir string) (map[string]string, error) { + refs := map[string]string{} + + tutorialsPath := filepath.Join(kibanaDir, tutorialsPath) + tutorialFilePaths, err := filepath.Glob(filepath.Join(tutorialsPath, "*_*", "index.ts")) + if err != nil { + return nil, errors.Wrapf(err, "globbing tutorial files failed (path: %s)", tutorialsPath) + } + + for _, tutorialFilePath := range tutorialFilePaths { + log.Printf("Scan tutorial file: %s", tutorialFilePath) + + tutorialFile, err := ioutil.ReadFile(tutorialFilePath) + if err != nil { + return nil, errors.Wrapf(err, "reading tutorial file failed (path: %s)", tutorialFile) + } + + m := iconRe.Find(tutorialFile) + if m == nil { + log.Printf("\t%s: icon not found", tutorialFilePath) + continue + } + + s := strings.Split(string(m), `'`) + val := s[1] + + // Extracting module name from tutorials path + // e.g. ./src/plugins/home/server/tutorials//php_fpm_metrics/index.ts -> php_fpm + moduleName := tutorialFilePath[len(tutorialsPath)+1:] + moduleName = moduleName[:strings.Index(moduleName, "/")] + moduleName = moduleName[:strings.LastIndex(moduleName, "_")] + + if val[0] == '/' { + iconFileName := val[strings.LastIndex(val, "/")+1:] + val = path.Join(kibanaDir, kibanaLogosPath, iconFileName) + refs[moduleName] = val + } + } + return refs, nil +} + +func retrieveIconPathFromEUI(euiDir string) (map[string]string, error) { + refs := map[string]string{} + + iconMapPath := filepath.Join(euiDir, "src/components/icon/icon.tsx") + iconMapFile, err := os.Open(iconMapPath) + if err != nil { + return nil, errors.Wrapf(err, "opening icon map file failed (path: %s)", iconMapPath) + } + + scanner := bufio.NewScanner(iconMapFile) + var mapFound bool + for scanner.Scan() { + line := scanner.Text() + if mapFound { + line = strings.TrimLeft(line, " ") + if strings.HasPrefix(line, "logo") { + s := strings.Split(line, `'`) + fileName := s[1] + fileNameWithExt := fileName + ".svg" + filePath := filepath.Join(euiDir, "src/components/icon/assets", fileNameWithExt) + moduleName := fileName[strings.Index(fileName, "_")+1:] + refs[moduleName] = filePath + } + } else if strings.HasPrefix(line, `const typeToPathMap = {`) { + mapFound = true + } + } + return refs, nil +} + +func (ir *iconRepository) iconForModule(moduleName string) (imageContent, error) { + source, ok := ir.icons[aliasModuleName(moduleName)] + if !ok { + return imageContent{}, errIconNotFound + } + return imageContent{source: source}, nil +} + +func aliasModuleName(moduleName string) string { + if v, ok := aliasedModuleNames[moduleName]; ok { + return v + } + return moduleName +} + +func createIcons(iconRepository *iconRepository, moduleName string) (imageContentArray, error) { + anIcon, err := iconRepository.iconForModule(moduleName) + if err == errIconNotFound { + log.Printf("\t%s: icon not found", moduleName) + return []imageContent{}, nil + } + if err != nil { + return nil, errors.Wrapf(err, "fetching icon for module failed (moduleName: %s)", moduleName) + } + return []imageContent{anIcon}, nil +} diff --git a/dev/import-beats/images.go b/dev/import-beats/images.go new file mode 100644 index 00000000000..9ae1ee116e4 --- /dev/null +++ b/dev/import-beats/images.go @@ -0,0 +1,153 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "image" + _ "image/jpeg" + _ "image/png" + "io/ioutil" + "log" + "os" + "path" + "regexp" + "strings" + + "github.com/pkg/errors" + + "github.com/elastic/package-registry/util" +) + +var ( + imageRe = regexp.MustCompile(`image::[^\[]+`) + imageTitleReplacer = strings.NewReplacer("_", " ", "-", " ", "/", "") +) + +type imageContent struct { + source string +} + +type imageContentArray []imageContent + +func createImages(beatDocsPath, modulePath string) (imageContentArray, error) { + var images []imageContent + + moduleDocsPath := path.Join(modulePath, "_meta", "docs.asciidoc") + moduleDocsFile, err := ioutil.ReadFile(moduleDocsPath) + if err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "reading module docs file failed (path: %s)", moduleDocsPath) + } else if os.IsNotExist(err) { + log.Printf("\tNo docs found (path: %s), skipped", moduleDocsPath) + } else { + log.Printf("\tDocs found (path: %s)", moduleDocsPath) + images = append(images, extractImages(beatDocsPath, moduleDocsFile)...) + } + + datasetDirs, err := ioutil.ReadDir(modulePath) + if err != nil { + return nil, errors.Wrapf(err, "cannot read module directory %s", modulePath) + } + + for _, datasetDir := range datasetDirs { + if !datasetDir.IsDir() { + continue + } + datasetName := datasetDir.Name() + + if datasetName == "_meta" { + continue + } + + log.Printf("\t%s: dataset found", datasetName) + + datasetDocsPath := path.Join(modulePath, datasetName, "_meta", "docs.asciidoc") + datasetDocsFile, err := ioutil.ReadFile(datasetDocsPath) + if err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "reading dataset docs file failed (path: %s)", datasetDocsPath) + } else if os.IsNotExist(err) { + log.Printf("\t%s: no docs found (path: %s), skipped", datasetName, datasetDocsPath) + continue + } + + log.Printf("\t%s: docs found (path: %s)", datasetName, datasetDocsPath) + images = append(images, extractImages(beatDocsPath, datasetDocsFile)...) + } + + return images, nil +} + +func extractImages(beatDocsPath string, docsFile []byte) []imageContent { + matches := imageRe.FindAll(docsFile, -1) + + var contents []imageContent + for _, match := range matches { + contents = append(contents, imageContent{ + source: path.Join(beatDocsPath, string(match[7:])), // skip: image:: + }) + } + return contents +} + +func (images imageContentArray) toManifestImages() ([]util.Image, error) { + var imgs []util.Image + for _, image := range images { + i := strings.LastIndex(image.source, "/") + sourceFileName := image.source[i:] + + imageSize, err := readImageSize(image.source) + if err != nil { + return nil, errors.Wrapf(err, "reading image size failed") + } + + imageType, err := extractImageType(image.source) + if err != nil { + return nil, errors.Wrapf(err, "extracting image type failed") + } + + imgs = append(imgs, util.Image{ + Src: path.Join("/img", sourceFileName), + Title: toImageTitle(sourceFileName), + Size: imageSize, + Type: imageType, + }) + } + return imgs, nil +} + +func toImageTitle(fileName string) string { + i := strings.LastIndex(fileName, ".") + return imageTitleReplacer.Replace(fileName[:i]) +} + +func readImageSize(imagePath string) (string, error) { + f, err := os.Open(imagePath) + if err != nil { + return "", errors.Wrapf(err, "opening image failed (path: %s)", imagePath) + } + defer f.Close() + + var img image.Config + if strings.HasSuffix(imagePath, ".svg") { + img, err = SvgDecodeConfig(f) + } else { + img, _, err = image.DecodeConfig(f) + } + if err != nil { + return "", errors.Wrapf(err, "opening image failed (path: %s)", imagePath) + } + return fmt.Sprintf("%dx%d", img.Width, img.Height), nil +} + +func extractImageType(imagePath string) (string, error) { + if strings.HasSuffix(imagePath, ".png") { + return "image/png", nil + } else if strings.HasSuffix(imagePath, ".jpg") { + return "image/jpg", nil + } else if strings.HasSuffix(imagePath, ".svg") { + return "image/svg+xml", nil + } + return "", fmt.Errorf("unknown image type (path: %s)", imagePath) +} diff --git a/dev/import-beats/images_svg.go b/dev/import-beats/images_svg.go new file mode 100644 index 00000000000..a314bdbb626 --- /dev/null +++ b/dev/import-beats/images_svg.go @@ -0,0 +1,103 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "encoding/xml" + "image" + "io" + "io/ioutil" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +type svgFile struct { + Width string `xml:"width,attr"` + Height string `xml:"height,attr"` + + ViewBox string `xml:"viewBox,attr"` +} + +func SvgDecodeConfig(r io.Reader) (image.Config, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return image.Config{}, errors.Wrapf(err, "reading SVG file failed") + } + + var svgFile svgFile + err = xml.Unmarshal(data, &svgFile) + if err != nil { + return image.Config{}, errors.Wrapf(err, "unmarshalling SVG file failed") + } + + var width, height float64 + if svgFile.Width != "" && svgFile.Height != "" { + width, err = svgParseToPixels(svgFile.Width) + if err != nil { + return image.Config{}, errors.Wrapf(err, "parsing width failed (value: %s)", svgFile.Width) + } + + height, err = svgParseToPixels(svgFile.Height) + if err != nil { + return image.Config{}, errors.Wrapf(err, "parsing width failed (value: %s)", svgFile.Width) + } + } + + if width > 0 && height > 0 { + return image.Config{ + Width: int(width), + Height: int(height), + }, nil + } + + dims := strings.Split(svgFile.ViewBox, " ") + var dimX, dimY string + if len(dims) == 2 { + dimX = dims[0] + dimY = dims[1] + } else if len(dims) == 4 { + dimX = dims[2] + dimY = dims[3] + } + width, err = strconv.ParseFloat(dimX, 32) + if err != nil { + return image.Config{}, errors.Wrapf(err, "parsing viewBox failed (value: %s)", svgFile.ViewBox) + } + + height, err = strconv.ParseFloat(dimY, 32) + if err != nil { + return image.Config{}, errors.Wrapf(err, "parsing viewBox failed (value: %s)", svgFile.ViewBox) + } + + return image.Config{ + Width: int(width), + Height: int(height), + }, nil +} + +func svgParseToPixels(value string) (float64, error) { + v, err := strconv.ParseFloat(value, 32) + if err != nil { + var unit string + var scale float64 + if strings.Contains(value, "pt") { + unit = "pt" + scale = 4.0 / 3 + } else if strings.Contains(value, "mm") { + unit = "mm" + scale = 3.77 + } + + value = strings.ReplaceAll(value, unit, "") + v, err = strconv.ParseFloat(value, 32) + if err != nil { + return -1, errors.Wrapf(err, "parsing width failed (value: %s)", value) + } + v = v * scale + } + return v, nil +} diff --git a/dev/import-beats/kibana.go b/dev/import-beats/kibana.go new file mode 100644 index 00000000000..dab165acb5b --- /dev/null +++ b/dev/import-beats/kibana.go @@ -0,0 +1,477 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "strings" + + "github.com/pkg/errors" +) + +var ( + encodedFields = []string{ + "attributes.kibanaSavedObjectMeta.searchSourceJSON", + "attributes.layerListJSON", + "attributes.mapStateJSON", + "attributes.optionsJSON", + "attributes.panelsJSON", + "attributes.uiStateJSON", + "attributes.visState", + } +) + +type kibanaContent struct { + files map[string]map[string][]byte +} + +type kibanaMigrator struct { + hostPort string + username string + password string + + skipKibana bool +} + +type kibanaDocuments struct { + Objects []mapStr `json:"objects"` +} + +func newKibanaMigrator(hostPort string, username string, password string, skipKibana bool) *kibanaMigrator { + return &kibanaMigrator{ + hostPort: hostPort, + username: username, + password: password, + skipKibana: skipKibana, + } +} + +func (km *kibanaMigrator) migrateDashboardFile(dashboardFile []byte, moduleName string, datasetNames []string) ([]byte, error) { + dashboardFile, err := prepareDashboardFile(dashboardFile) + if err != nil { + return nil, errors.Wrapf(err, "preparing file failed") + } + + request, err := http.NewRequest("POST", + fmt.Sprintf("%s/api/kibana/dashboards/import?force=true", km.hostPort), + bytes.NewReader(dashboardFile)) + if err != nil { + return nil, errors.Wrapf(err, "creating POST request failed") + } + request.Header.Add("kbn-xsrf", "8.0.0") + if km.username != "" { + request.SetBasicAuth(km.username, km.password) + } + response, err := http.DefaultClient.Do(request) + if err != nil { + return nil, errors.Wrapf(err, "making POST request to Kibana failed") + } + defer response.Body.Close() + + saved, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, errors.Wrapf(err, "reading saved object failed") + } + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("making POST request failed: %s", string(saved)) + } + return saved, nil +} + +func prepareDashboardFile(dashboardFile []byte) ([]byte, error) { + var documents kibanaDocuments + + // Rename indices (metricbeat, filebeat) + dashboardFile = bytes.ReplaceAll(dashboardFile, []byte(`metricbeat-*`), []byte(`metrics-*`)) + dashboardFile = bytes.ReplaceAll(dashboardFile, []byte(`filebeat-*`), []byte(`logs-*`)) + + err := json.Unmarshal(dashboardFile, &documents) + if err != nil { + return nil, errors.Wrapf(err, "unmarshalling dashboard file failed") + } + + for i, object := range documents.Objects { + object, err = encodeFields(object) + if err != nil { + return nil, errors.Wrapf(err, "encoding fields failed") + } + documents.Objects[i] = object + } + + data, err := json.Marshal(&documents) + if err != nil { + return nil, errors.Wrapf(err, "marshalling dashboard file failed") + } + return data, nil +} + +func encodeFields(ms mapStr) (mapStr, error) { + for _, field := range encodedFields { + v, err := ms.getValue(field) + if err == errKeyNotFound { + continue + } else if err != nil { + return mapStr{}, errors.Wrapf(err, "retrieving value failed (key: %s)", field) + } + + _, isString := v.(string) + if isString { + continue + } + + ve, err := json.Marshal(v) + if err != nil { + return mapStr{}, errors.Wrapf(err, "marshalling value failed (key: %s)", field) + } + + _, err = ms.put(field, string(ve)) + if err != nil { + return mapStr{}, errors.Wrapf(err, "putting value failed (key: %s)", field) + } + } + return ms, nil +} + +func createKibanaContent(kibanaMigrator *kibanaMigrator, modulePath string, moduleName string, + datasetNames []string) (kibanaContent, error) { + if kibanaMigrator.skipKibana { + log.Printf("\tKibana migrator disabled, skipped (modulePath: %s)", modulePath) + return kibanaContent{}, nil + } + + moduleDashboardPath := path.Join(modulePath, "_meta", "kibana", "7", "dashboard") + moduleDashboards, err := ioutil.ReadDir(moduleDashboardPath) + if os.IsNotExist(err) { + log.Printf("\tno dashboards present, skipped (modulePath: %s)", modulePath) + return kibanaContent{}, nil + } + if err != nil { + return kibanaContent{}, errors.Wrapf(err, "reading module dashboard directory failed (path: %s)", + moduleDashboardPath) + } + + kibana := kibanaContent{ + files: map[string]map[string][]byte{}, + } + for _, moduleDashboard := range moduleDashboards { + log.Printf("\tdashboard found: %s", moduleDashboard.Name()) + + dashboardFilePath := path.Join(moduleDashboardPath, moduleDashboard.Name()) + dashboardFile, err := ioutil.ReadFile(dashboardFilePath) + if err != nil { + return kibanaContent{}, errors.Wrapf(err, "reading dashboard file failed (path: %s)", + dashboardFilePath) + } + + migrated, err := kibanaMigrator.migrateDashboardFile(dashboardFile, moduleName, datasetNames) + if err != nil { + return kibanaContent{}, errors.Wrapf(err, "migrating dashboard file failed (path: %s)", + dashboardFilePath) + } + + extracted, err := convertToKibanaObjects(migrated, moduleName, datasetNames) + if err != nil { + return kibanaContent{}, errors.Wrapf(err, "extracting kibana dashboards failed") + } + + for objectType, objects := range extracted { + if _, ok := kibana.files[objectType]; !ok { + kibana.files[objectType] = map[string][]byte{} + } + + for k, v := range objects { + kibana.files[objectType][k] = v + } + } + } + return kibana, nil +} + +func convertToKibanaObjects(dashboardFile []byte, moduleName string, datasetNames []string) (map[string]map[string][]byte, error) { + var documents kibanaDocuments + + err := json.Unmarshal(dashboardFile, &documents) + if err != nil { + return nil, errors.Wrapf(err, "unmarshalling migrated dashboard file failed") + } + + extracted := map[string]map[string][]byte{} + for _, object := range documents.Objects { + err = object.delete("updated_at") + if err != nil { + return nil, errors.Wrapf(err, "removing field updated_at failed") + } + + err = object.delete("version") + if err != nil { + return nil, errors.Wrapf(err, "removing field version failed") + } + + object, err = decodeFields(object) + if err != nil { + return nil, errors.Wrapf(err, "decoding fields failed") + } + + object, err = stripReferencesToEventModule(object, moduleName, datasetNames) + if err != nil { + return nil, errors.Wrapf(err, "stripping references to event module failed") + } + + aType, err := object.getValue("type") + if err != nil { + return nil, errors.Wrapf(err, "retrieving type failed") + } + + data, err := json.MarshalIndent(object, "", " ") + if err != nil { + return nil, errors.Wrapf(err, "marshalling object failed") + } + + data = replaceBlacklistedWords( + replaceFieldEventDatasetWithStreamDataset( + data)) + + err = verifyKibanaObjectConvertion(data) + if err != nil { + return nil, errors.Wrapf(err, "Kibana object convertion failed") + } + + id, err := object.getValue("id") + if err != nil { + return nil, errors.Wrapf(err, "retrieving id failed") + } + + if _, ok := extracted[aType.(string)]; !ok { + extracted[aType.(string)] = map[string][]byte{} + } + extracted[aType.(string)][id.(string)+".json"] = data + } + + return extracted, nil +} + +func decodeFields(ms mapStr) (mapStr, error) { + for _, field := range encodedFields { + v, err := ms.getValue(field) + if err == errKeyNotFound { + continue + } else if err != nil { + return nil, errors.Wrapf(err, "retrieving value failed (key: %s)", field) + } + + var target interface{} + var vd mapStr + vStr := v.(string) + err = json.Unmarshal([]byte(vStr), &vd) + if err != nil { + var vda []mapStr + err = json.Unmarshal([]byte(vStr), &vda) + if err != nil { + return nil, errors.Wrapf(err, "unmarshalling value failed (key: %s)", field) + } + target = vda + } else { + target = vd + } + + _, err = ms.put(field, target) + if err != nil { + return nil, errors.Wrapf(err, "putting value failed (key: %s)", field) + } + } + return ms, nil +} + +func stripReferencesToEventModule(object mapStr, moduleName string, datasetNames []string) (mapStr, error) { + key := "attributes.kibanaSavedObjectMeta.searchSourceJSON.filter" + object, err := stripReferencesToEventModuleInFilter(object, key, moduleName) + if err != nil { + return nil, errors.Wrapf(err, "stripping reference in searchSourceJSON.filter failed (moduleName: %s)", moduleName) + } + + key = "attributes.kibanaSavedObjectMeta.searchSourceJSON.query" + object, err = stripReferencesToEventModuleInQuery(object, key, moduleName, datasetNames) + if err != nil { + return nil, errors.Wrapf(err, "stripping reference in searchSourceJSON.query failed (moduleName: %s)", moduleName) + } + + key = "attributes.visState.params.filter" + object, err = stripReferencesToEventModuleInQuery(object, key, moduleName, datasetNames) + if err != nil { + return nil, errors.Wrapf(err, "stripping reference in visState failed (moduleName: %s)", moduleName) + } + + return object, nil +} + +func stripReferencesToEventModuleInFilter(object mapStr, filterKey, moduleName string) (mapStr, error) { + filterValue, err := object.getValue(filterKey) + if err != nil && err != errKeyNotFound { + return nil, fmt.Errorf("retrieving key '%s' failed: %v", filterKey, err) + } else if err == errKeyNotFound { + return object, nil // nothing to adjust + } + + filters, ok := filterValue.([]interface{}) + if !ok { + return object, nil // not an array, ignoring + } + if len(filters) == 0 { + return object, nil // empty array, ignoring + } + + var updatedFilters []mapStr + for _, fi := range filters { + filterObject, err := toMapStr(fi) + if err != nil { + return nil, errors.Wrapf(err, "converting to mapstr failed") + } + + metaKeyObject, err := filterObject.getValue("meta.key") + if err != nil { + return nil, errors.Wrapf(err, "retrieving meta.key failed") + } + + metaKey, ok := metaKeyObject.(string) + if ok && metaKey == "event.module" { + _, err = filterObject.put("meta.key", "query") + if err != nil { + return nil, errors.Wrapf(err, "setting meta.key failed") + } + + _, err = filterObject.put("meta.type", "custom") + if err != nil { + return nil, errors.Wrapf(err, "setting meta.type failed") + } + + _, err = filterObject.put("meta.value", fmt.Sprintf("{\"match_phrase_prefix\":{\"stream.dataset\":{\"query\":\"%s.\"}}}", moduleName)) + if err != nil { + return nil, errors.Wrapf(err, "setting meta.value failed") + } + + err = filterObject.delete("meta.params") + if err != nil { + return nil, errors.Wrapf(err, "removing meta.params failed") + } + + q := map[string]interface{}{ + "match_phrase_prefix": map[string]interface{}{ + "stream.dataset": map[string]interface{}{ + "query": moduleName + ".", + }, + }, + } + _, err = filterObject.put("query", q) + if err != nil { + return nil, errors.Wrapf(err, "setting query failed") + } + } + updatedFilters = append(updatedFilters, filterObject) + } + + _, err = object.put(filterKey, updatedFilters) + if err != nil { + return nil, errors.Wrapf(err, "replacing filters failed (moduleName: %s)", moduleName) + } + return object, nil +} + +func stripReferencesToEventModuleInQuery(object mapStr, objectKey, moduleName string, datasetNames []string) (mapStr, error) { + objectValue, err := object.getValue(objectKey) + if _, ok := objectValue.(map[string]interface{}); !ok { + return object, nil // not a map object + } + + languageKey := objectKey + ".language" + queryKey := objectKey + ".query" + + queryValue, err := object.getValue(queryKey) + if err != nil && err != errKeyNotFound { + return nil, fmt.Errorf("retrieving key '%s' failed: %v", queryKey, err) + } else if err == errKeyNotFound { + return object, nil // nothing to adjust + } + + query, ok := queryValue.(string) + if !ok { + return object, nil // complex query (not a simple string) + } + if query == "" { + return object, nil // empty query field + } + + query = strings.ReplaceAll(query, ": ", ":") + query = strings.ReplaceAll(query, " :", ":") + query = strings.ReplaceAll(query, `"`, "") + if strings.Contains(query, "event.module:"+moduleName) && (strings.Contains(query, "metricset.name:") || strings.Contains(query, "fileset.name:")) { + query = strings.ReplaceAll(query, "event.module:"+moduleName, "") + query = strings.ReplaceAll(query, "metricset.name:", fmt.Sprintf("stream.dataset:%s.", moduleName)) + query = strings.ReplaceAll(query, "fileset.name:", fmt.Sprintf("stream.dataset:%s.", moduleName)) + query = strings.TrimSpace(query) + if strings.HasPrefix(query, "AND ") { + query = query[4:] + } + + _, err := object.put(queryKey, query) + if err != nil { + return nil, fmt.Errorf("replacing key '%s' failed: %v", queryKey, err) + } + } else if strings.Contains(query, "event.module:"+moduleName) { + var eventDatasets []string + for _, datasetName := range datasetNames { + eventDatasets = append(eventDatasets, fmt.Sprintf("stream.dataset:%s.%s", moduleName, datasetName)) + } + + value := " (" + strings.Join(eventDatasets, " OR ") + ") " + query = strings.ReplaceAll(query, "event.module:"+moduleName, value) + query = strings.TrimSpace(query) + + _, err := object.put(queryKey, query) + if err != nil { + return nil, fmt.Errorf("replacing key '%s' failed: %v", queryKey, err) + } + + _, err = object.put(languageKey, "kuery") + if err != nil { + return nil, fmt.Errorf("replacing key '%s' failed: %v", languageKey, err) + } + } + return object, nil +} + +func replaceFieldEventDatasetWithStreamDataset(data []byte) []byte { + return bytes.ReplaceAll(data, []byte("event.dataset"), []byte("stream.dataset")) +} + +func replaceBlacklistedWords(data []byte) []byte { + data = bytes.ReplaceAll(data, []byte("Metricbeat"), []byte("Metrics")) + data = bytes.ReplaceAll(data, []byte("metricbeat"), []byte("metrics")) + data = bytes.ReplaceAll(data, []byte("Filebeat"), []byte("Logs")) + data = bytes.ReplaceAll(data, []byte("filebeat"), []byte("logs")) + data = bytes.ReplaceAll(data, []byte("Module"), []byte("Integration")) + data = bytes.ReplaceAll(data, []byte("module"), []byte("integration")) + return data +} + +func verifyKibanaObjectConvertion(data []byte) error { + i := bytes.Index(data, []byte("event.module")) + if i > 0 { + return fmt.Errorf("event.module spotted at pos. %d", i) + } + + i = bytes.Index(data, []byte("event.dataset")) + if i > 0 { + return fmt.Errorf("event.dataset spotted at pos. %d", i) + } + return nil +} diff --git a/dev/import-beats/main.go b/dev/import-beats/main.go new file mode 100644 index 00000000000..44db25ad50e --- /dev/null +++ b/dev/import-beats/main.go @@ -0,0 +1,140 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "flag" + "log" + "net/url" + "os" + "strings" + + "github.com/pkg/errors" +) + +type importerOptions struct { + // Beats repository directory + beatsDir string + + // Kibana host and port + kibanaHostPort string + // Kibana username + kibanaUsername string + // Kibana password + kibanaPassword string + // Kibana repository directory + kibanaDir string + // Skip storing Kibana objects + skipKibana bool + + // Elastic UI Framework directory + euiDir string + + // Elastic Common Schema directory + ecsDir string + + // Packages selected for the import (comma-delimited list) + packages string + + // Target public directory where the generated packages should end up in + outputDir string +} + +func (o *importerOptions) validate() error { + _, err := os.Stat(o.beatsDir) + if err != nil { + return errors.Wrapf(err, "stat file failed (path: %s)", o.beatsDir) + } + + _, err = url.Parse(o.kibanaHostPort) + if err != nil { + return errors.Wrapf(err, "parsing Kibana's host:port failed (hostPort: %s)", o.kibanaHostPort) + } + + _, err = os.Stat(o.kibanaDir) + if err != nil { + return errors.Wrapf(err, "stat file failed (path: %s)", o.kibanaDir) + } + + _, err = os.Stat(o.euiDir) + if err != nil { + return errors.Wrapf(err, "stat file failed (path: %s)", o.euiDir) + } + + _, err = os.Stat(o.outputDir) + if err != nil { + return errors.Wrapf(err, "stat file failed (path: %s)", o.outputDir) + } + return nil +} + +func (o *importerOptions) selectedPackages() []string { + var selected []string + p := strings.TrimSpace(o.packages) + if len(p) > 0 { + selected = strings.Split(p, ",") + } + return selected +} + +func main() { + var options importerOptions + + flag.StringVar(&options.beatsDir, "beatsDir", "../beats", "Path to the beats repository") + flag.StringVar(&options.kibanaDir, "kibanaDir", "../kibana", "Path to the kibana repository") + flag.StringVar(&options.kibanaHostPort, "kibanaHostPort", "http://localhost:5601", "Kibana host and port") + flag.StringVar(&options.kibanaUsername, "kibanaUsername", "elastic", "Kibana username") + flag.StringVar(&options.kibanaPassword, "kibanaPassword", "changeme", "Kibana password") + flag.BoolVar(&options.skipKibana, "skipKibana", false, "Skip storing Kibana objects") + flag.StringVar(&options.euiDir, "euiDir", "../eui", "Path to the Elastic UI framework repository") + flag.StringVar(&options.ecsDir, "ecsDir", "../ecs", "Path to the Elastic Common Schema repository") + flag.StringVar(&options.packages, "packages", "", "Packages selected for the import") + flag.StringVar(&options.outputDir, "outputDir", "dev/packages/beats", "Path to the output directory") + flag.Parse() + + err := options.validate() + if err != nil { + log.Fatal(err) + } + + if err := build(options); err != nil { + log.Fatal(err) + } +} + +// build method visits all beats in beatsDir to collect configuration data for modules. +// The package-registry groups integrations per target product not per module type. It's opposite to the beats project, +// where logs and metrics are distributed with different beats (oriented either on logs or metrics - metricbeat, +// filebeat, etc.). +func build(options importerOptions) error { + iconRepository, err := newIconRepository(options.euiDir, options.kibanaDir) + if err != nil { + return errors.Wrap(err, "creating icon repository failed") + } + kibanaMigrator := newKibanaMigrator(options.kibanaHostPort, + options.kibanaUsername, + options.kibanaPassword, + options.skipKibana) + ecsFields, err := loadEcsFields(options.ecsDir) + if err != nil { + return errors.Wrap(err, "loading ECS fields failed") + } + + repository := newPackageRepository(iconRepository, kibanaMigrator, ecsFields, options.selectedPackages()) + + for _, beatName := range logSources { + err := repository.createPackagesFromSource(options.beatsDir, beatName, "logs") + if err != nil { + return errors.Wrap(err, "creating from logs source failed") + } + } + for _, beatName := range metricSources { + err := repository.createPackagesFromSource(options.beatsDir, beatName, "metrics") + if err != nil { + return errors.Wrap(err, "creating from metrics source failed") + } + } + return repository.save(options.outputDir) +} diff --git a/dev/import-beats/packages.go b/dev/import-beats/packages.go new file mode 100644 index 00000000000..9f9a05e073d --- /dev/null +++ b/dev/import-beats/packages.go @@ -0,0 +1,499 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "strings" + "text/template" + + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" + + "github.com/elastic/package-registry/util" +) + +var ignoredModules = map[string]bool{"apache2": true} + +var removablePackages = map[string]bool{"system": false} + +type packageContent struct { + manifest util.Package + datasets datasetContentArray + images []imageContent + kibana kibanaContent + docs []docContent + datasource datasourceContent +} + +func newPackageContent(name string) packageContent { + return packageContent{ + manifest: util.Package{ + FormatVersion: "1.0.0", + Name: name, + Version: "0.0.1", // TODO + Type: "integration", + License: "basic", + Removable: determineIfPackageIsRemovable(name), + Release: "experimental", + }, + kibana: kibanaContent{ + files: map[string]map[string][]byte{}, + }, + } +} + +func determineIfPackageIsRemovable(name string) bool { + _, ok := removablePackages[name] + return !ok +} + +func (pc *packageContent) addDatasets(ds []datasetContent) { + for _, dc := range ds { + for i, v := range pc.datasets { + if v.name == dc.name { + if v.beatType != dc.beatType { + pc.datasets[i].name = fmt.Sprintf("%s-%s", pc.datasets[i].name, pc.datasets[i].beatType) + dc.name = fmt.Sprintf("%s-%s", dc.name, dc.beatType) + pc.datasets = append(pc.datasets, dc) + } else { + log.Printf("Resolve naming conflict (packageName: %s, beatType: %s)", dc.name, dc.beatType) + pc.datasets[i] = dc + } + break + } + } + pc.datasets = append(pc.datasets, dc) + } +} + +func (pc *packageContent) addKibanaContent(kc kibanaContent) { + if kc.files != nil { + for objectType, objects := range kc.files { + if _, ok := pc.kibana.files[objectType]; !ok { + pc.kibana.files[objectType] = map[string][]byte{} + } + + for k, v := range objects { + pc.kibana.files[objectType][k] = v + } + } + } +} + +type packageRepository struct { + iconRepository *iconRepository + kibanaMigrator *kibanaMigrator + ecsFields fieldDefinitionArray + selectedPackageNames []string + + packages map[string]packageContent +} + +func newPackageRepository(iconRepository *iconRepository, kibanaMigrator *kibanaMigrator, + ecsFields fieldDefinitionArray, selectedPackageNames []string) *packageRepository { + return &packageRepository{ + iconRepository: iconRepository, + kibanaMigrator: kibanaMigrator, + ecsFields: ecsFields, + selectedPackageNames: selectedPackageNames, + + packages: map[string]packageContent{}, + } +} + +func (r *packageRepository) createPackagesFromSource(beatsDir, beatName, beatType string) error { + beatPath := filepath.Join(beatsDir, beatName) + beatModulesPath := filepath.Join(beatPath, "module") + + moduleDirs, err := ioutil.ReadDir(beatModulesPath) + if err != nil { + return errors.Wrapf(err, "cannot read directory '%s'", beatModulesPath) + } + + for _, moduleDir := range moduleDirs { + if !moduleDir.IsDir() { + continue + } + moduleName := moduleDir.Name() + + if !r.packageSelected(moduleName) { + continue + } + + log.Printf("%s %s: module found\n", beatName, moduleName) + if _, ok := ignoredModules[moduleName]; ok { + log.Printf("%s %s: module skipped\n", beatName, moduleName) + continue + } + modulePath := path.Join(beatModulesPath, moduleName) + + _, ok := r.packages[moduleName] + if !ok { + r.packages[moduleName] = newPackageContent(moduleName) + } + + aPackage := r.packages[moduleName] + manifest := aPackage.manifest + manifest.Categories = append(manifest.Categories, beatType) + + // fields + moduleFields, err := loadModuleFields(modulePath) + if err != nil { + return err + } + moduleFields, filteredEcsModuleFieldNames, err := filterMigratedFields(moduleFields, r.ecsFields.names()) + if err != nil { + return err + } + + // title + maybeTitle := moduleFields[0].Title + if maybeTitle != "" { + manifest.Title = &maybeTitle + manifest.Description = maybeTitle + " Integration" + } + + // img + beatDocsPath := selectDocsPath(beatsDir, beatName) + images, err := createImages(beatDocsPath, modulePath) + if err != nil { + return err + } + aPackage.images = append(aPackage.images, images...) + + // img/icons + // The condition prevents from adding an icon multiple times (e.g. for metricbeat and filebeat). + if len(manifest.Icons) == 0 { + icons, err := createIcons(r.iconRepository, moduleName) + if err != nil { + return err + } + aPackage.images = append(aPackage.images, icons...) + + manifestIcons, err := icons.toManifestImages() + if err != nil { + return err + } + manifest.Icons = append(manifest.Icons, manifestIcons...) + } + + // img/screenshots + screenshots, err := images.toManifestImages() + if err != nil { + return err + } + manifest.Screenshots = append(manifest.Screenshots, screenshots...) + + // docs + if len(aPackage.docs) == 0 { + packageDocsPath := filepath.Join("dev/import-beats-resources", moduleDir.Name(), "docs") + docs, err := createDocTemplates(packageDocsPath) + if err != nil { + return err + } + aPackage.docs = append(aPackage.docs, docs...) + } + + // datasets + var moduleTitle = "TODO" + if manifest.Title != nil { + moduleTitle = *manifest.Title + } + + datasets, err := createDatasets(beatType, modulePath, moduleName, moduleTitle, manifest.Release, moduleFields, filteredEcsModuleFieldNames, r.ecsFields) + if err != nil { + return err + } + datasets, inputVarsPerInputType, err := compactDatasetVariables(datasets) + if err != nil { + return err + } + aPackage.addDatasets(datasets) + + // datasources + aPackage.datasource, err = updateDatasource(aPackage.datasource, updateDatasourcesParameters{ + moduleName: moduleName, + moduleTitle: moduleTitle, + packageType: beatType, + datasetNames: datasets.names(), + inputVars: inputVarsPerInputType, + }) + if err != nil { + return err + } + manifest.Datasources = aPackage.datasource.toMetadataDatasources() + + // kibana + kibana, err := createKibanaContent(r.kibanaMigrator, modulePath, moduleName, datasets.names()) + if err != nil { + return err + } + aPackage.addKibanaContent(kibana) + manifest.Requirement, err = createRequirement(aPackage.kibana, aPackage.datasets) + if err != nil { + return err + } + + aPackage.manifest = manifest + r.packages[moduleDir.Name()] = aPackage + } + return nil +} + +func (r *packageRepository) packageSelected(packageName string) bool { + if len(r.selectedPackageNames) == 0 { + return true + } + + for _, f := range r.selectedPackageNames { + if f == packageName { + return true + } + } + return false +} + +func (r *packageRepository) save(outputDir string) error { + for packageName, content := range r.packages { + manifest := content.manifest + + log.Printf("%s/%s write package content\n", packageName, manifest.Version) + + packagePath := filepath.Join(outputDir, packageName, manifest.Version) + err := os.MkdirAll(packagePath, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory for module: '%s'", packagePath) + } + + m, err := yaml.Marshal(content.manifest) + if err != nil { + return errors.Wrapf(err, "marshaling package manifest failed (packageName: %s)", packageName) + } + + manifestFilePath := filepath.Join(packagePath, "manifest.yml") + err = ioutil.WriteFile(manifestFilePath, m, 0644) + if err != nil { + return errors.Wrapf(err, "writing manifest file failed (path: %s)", manifestFilePath) + } + + // dataset + for _, dataset := range content.datasets { + datasetPath := filepath.Join(packagePath, "dataset", dataset.name) + err := os.MkdirAll(datasetPath, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory for dataset: '%s'", datasetPath) + } + + // dataset/manifest.yml + m, err := yaml.Marshal(dataset.manifest) + if err != nil { + return errors.Wrapf(err, "marshaling dataset manifest failed (datasetName: %s)", dataset.name) + } + + manifestFilePath := filepath.Join(datasetPath, "manifest.yml") + err = ioutil.WriteFile(manifestFilePath, m, 0644) + if err != nil { + return errors.Wrapf(err, "writing dataset manifest file failed (path: %s)", manifestFilePath) + } + + // dataset/fields + if len(dataset.fields.files) > 0 { + datasetFieldsPath := filepath.Join(datasetPath, "fields") + err := os.MkdirAll(datasetFieldsPath, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory for dataset fields: '%s'", datasetPath) + } + + for fieldsFileName, definitions := range dataset.fields.files { + log.Printf("%s: write '%s' file\n", dataset.name, fieldsFileName) + + fieldsFilePath := filepath.Join(datasetFieldsPath, fieldsFileName) + var fieldsFile []byte + + var root fieldDefinitionArray + if isPackageFields(fieldsFileName) { // remove the wrapping layer + root = definitions[0].Fields + } else { + root = definitions + } + + stripped := root.stripped() + fieldsFile, err := yaml.Marshal(&stripped) + if err != nil { + return errors.Wrapf(err, "marshalling fields file failed (path: %s)", fieldsFilePath) + } + err = ioutil.WriteFile(fieldsFilePath, fieldsFile, 0644) + if err != nil { + return errors.Wrapf(err, "writing fields file failed (path: %s)", fieldsFilePath) + } + } + } + + // dataset/elasticsearch + if len(dataset.elasticsearch.ingestPipelines) > 0 { + ingestPipelinesPath := filepath.Join(datasetPath, "elasticsearch", util.DirIngestPipeline) + err := os.MkdirAll(ingestPipelinesPath, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory for dataset ingest pipelines: '%s'", ingestPipelinesPath) + } + + for _, ingestPipeline := range dataset.elasticsearch.ingestPipelines { + ingestPipelinePath := filepath.Join(ingestPipelinesPath, ingestPipeline.targetFileName) + log.Printf("write ingest pipeline file '%s'", ingestPipelinePath) + + err := ioutil.WriteFile(ingestPipelinePath, ingestPipeline.body, 0644) + if err != nil { + return errors.Wrapf(err, "writing ingest pipeline failed") + } + } + } + + // dataset/agent/stream + if len(dataset.agent.streams) > 0 { + agentStreamPath := filepath.Join(datasetPath, "agent", "stream") + err := os.MkdirAll(agentStreamPath, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory for dataset agent stream: '%s'", agentStreamPath) + } + + for _, agentStream := range dataset.agent.streams { + err := ioutil.WriteFile(path.Join(agentStreamPath, agentStream.targetFileName), agentStream.body, 0644) + if err != nil { + return errors.Wrapf(err, "writing agent stream file failed") + } + } + } + } + + // img + imgDstDir := path.Join(packagePath, "img") + for _, image := range content.images { + log.Printf("copy image file '%s' to '%s'", image.source, imgDstDir) + err := copyFile(image.source, imgDstDir) + if err != nil { + return errors.Wrapf(err, "copying file failed") + } + } + + // kibana + if len(content.kibana.files) > 0 { + kibanaPath := filepath.Join(packagePath, "kibana") + + for objectType, objects := range content.kibana.files { + resourcePath := filepath.Join(kibanaPath, objectType) + + err := os.MkdirAll(resourcePath, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory for dashboard files: '%s'", resourcePath) + } + + for fileName, body := range objects { + resourceFilePath := filepath.Join(resourcePath, fileName) + + log.Printf("create resource file: %s", resourceFilePath) + err = ioutil.WriteFile(resourceFilePath, body, 0644) + if err != nil { + return errors.Wrapf(err, "writing resource file failed (path: %s)", resourceFilePath) + } + } + } + } + + // docs + if len(content.docs) > 0 { + docsPath := filepath.Join(packagePath, "docs") + err := os.MkdirAll(docsPath, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory for docs: '%s'", docsPath) + } + + for _, doc := range content.docs { + err = writeDoc(docsPath, doc, content) + if err != nil { + return errors.Wrapf(err, "cannot write docs (docsPath: %s, fileName: %s)", docsPath, + doc.fileName) + } + } + } + } + return nil +} + +func writeDoc(docsPath string, doc docContent, aPackage packageContent) error { + log.Printf("write '%s' file\n", doc.fileName) + + docFilePath := filepath.Join(docsPath, doc.fileName) + f, err := os.OpenFile(docFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + defer f.Close() + + if err != nil { + return errors.Wrapf(err, "opening doc file failed (path: %s)", docFilePath) + } + t := template.New(doc.fileName) + if doc.templatePath == "" { + t = template.Must(t.Parse("TODO")) + } else { + t, err = t.Funcs(template.FuncMap{ + "fields": func(dataset string) (string, error) { + return renderExportedFields(dataset, aPackage.datasets) + }, + }).ParseFiles(doc.templatePath) + if err != nil { + return errors.Wrapf(err, "parsing doc template failed (path: %s)", doc.templatePath) + } + } + err = t.Execute(f, nil) + if err != nil { + return errors.Wrapf(err, "rendering doc file failed (path: %s)", docFilePath) + } + return nil +} + +func copyFile(src, dstDir string) error { + i := strings.LastIndex(src, "/") + sourceFileName := src[i:] + + return copyFileToTarget(src, dstDir, sourceFileName) +} + +func copyFileToTarget(src, dstDir, targetFileName string) error { + sourceFile, err := os.Open(src) + if err != nil { + return errors.Wrapf(err, "opening file failed (src: %s)", src) + } + defer sourceFile.Close() + + dst := path.Join(dstDir, targetFileName) + err = os.MkdirAll(dstDir, 0755) + if err != nil { + return errors.Wrapf(err, "cannot make directory: '%s'", dst) + } + + dstFile, err := os.Create(dst) + if err != nil { + return errors.Wrapf(err, "creating target file failed (dst: %s)", dst) + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, sourceFile) + if err != nil { + return errors.Wrapf(err, "copying file failed (src: %s, dst: %s)", src, dst) + } + return nil +} + +func selectDocsPath(beatsDir, beatName string) string { + if strings.HasPrefix(beatName, "x-pack/") { + return path.Join(beatsDir, beatName[7:], "docs") + } + return path.Join(beatsDir, beatName, "docs") +} diff --git a/dev/import-beats/requirement.go b/dev/import-beats/requirement.go new file mode 100644 index 00000000000..e130380a26a --- /dev/null +++ b/dev/import-beats/requirement.go @@ -0,0 +1,101 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "encoding/json" + "fmt" + + "github.com/blang/semver" + "github.com/pkg/errors" + + "github.com/elastic/package-registry/util" +) + +var zeroVersion = semver.MustParse("0.0.0") + +func createRequirement(kibanaContent kibanaContent, datasetContent []datasetContent) (util.Requirement, error) { + kibanaRequirement, err := findRequiredKibanaVersion(kibanaContent) + if err != nil { + return util.Requirement{}, errors.Wrapf(err, "finding required Kibana version failed") + } + return util.Requirement{ + Elasticsearch: findRequiredElasticsearchVersion(datasetContent), + Kibana: kibanaRequirement, + }, nil +} + +func findRequiredElasticsearchVersion(datasetContent []datasetContent) util.ProductRequirement { + for _, dc := range datasetContent { + if len(dc.elasticsearch.ingestPipelines) > 0 { + return util.ProductRequirement{ + Versions: ">7.0.1", + } + } + } + return util.ProductRequirement{} +} + +func findRequiredKibanaVersion(kibanaContent kibanaContent) (util.ProductRequirement, error) { + dashboards, ok := kibanaContent.files["dashboard"] + if !ok { + return util.ProductRequirement{}, nil // no dashboards available, no version requirement + } + + currentVersion := zeroVersion + + for _, dashboardFile := range dashboards { + var dashboard mapStr + err := json.Unmarshal(dashboardFile, &dashboard) + if err != nil { + return util.ProductRequirement{}, errors.Wrap(err, "unmarshalling dashboard filed") + } + + panels, err := dashboard.getValue("attributes.panelsJSON") + if err == errKeyNotFound { + continue // panelsJSON is missing, skip this dashboard + } + if err != nil { + return util.ProductRequirement{}, errors.Wrap(err, "retrieving key 'attributes.panelsJSON' failed") + } + + panelsValue := panels.([]interface{}) + if len(panelsValue) == 0 { + continue // panelsJSON is present, but empty, skip this dashboard + } + + for _, panel := range panelsValue { + panelValue, err := toMapStr(panel) + if err != nil { + return util.ProductRequirement{}, errors.Wrap(err, "converting to mapstr failed") + } + + version, err := panelValue.getValue("version") + if err == errKeyNotFound { + continue // no version tag, skip this panel + } + if err != nil { + return util.ProductRequirement{}, errors.Wrap(err, "retrieving key 'version' failed") + } + versionValue := version.(string) + parsed, err := semver.Parse(versionValue) + if err != nil { + return util.ProductRequirement{}, errors.Wrapf(err, "parsing version failed (value: %s)", versionValue) + } + + if currentVersion.LT(parsed) { + currentVersion = parsed + } + } + } + + if currentVersion.EQ(zeroVersion) { + return util.ProductRequirement{}, nil // no version requirement found, even if all files were visited. + } + + return util.ProductRequirement{ + Versions: fmt.Sprintf(">=%s <8.0.0", currentVersion), + }, nil +} diff --git a/dev/import-beats/sources.go b/dev/import-beats/sources.go new file mode 100644 index 00000000000..ab38cc78c39 --- /dev/null +++ b/dev/import-beats/sources.go @@ -0,0 +1,15 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +var metricSources = []string{ + "metricbeat", + "x-pack/metricbeat", +} + +var logSources = []string{ + "filebeat", + "x-pack/filebeat", +} diff --git a/dev/import-beats/streams.go b/dev/import-beats/streams.go new file mode 100644 index 00000000000..9978e27f3f6 --- /dev/null +++ b/dev/import-beats/streams.go @@ -0,0 +1,167 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + + "github.com/elastic/package-registry/util" +) + +// createStreams method builds a set of stream inputs including configuration variables. +// Stream definitions depend on a beat type - log or metric. +// At the moment, the array returns only one stream. +func createStreams(modulePath, moduleName, moduleTitle, datasetName, beatType string) ([]util.Stream, agentContent, error) { + var streams []util.Stream + var agent agentContent + var err error + + switch beatType { + case "logs": + streams, agent, err = createLogStreams(modulePath, moduleTitle, datasetName) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "creating log streams failed (modulePath: %s, datasetName: %s)", + modulePath, datasetName) + } + case "metrics": + streams, agent, err = createMetricStreams(modulePath, moduleName, moduleTitle, datasetName) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "creating metric streams failed (modulePath: %s, datasetName: %s)", + modulePath, datasetName) + } + default: + return nil, agentContent{}, fmt.Errorf("invalid beat type: %s", beatType) + } + return streams, agent, nil +} + +// createLogStreams method builds a set of stream inputs for logs oriented dataset. +// The method unmarshals "manifest.yml" file and picks all configuration variables. +func createLogStreams(modulePath, moduleTitle, datasetName string) ([]util.Stream, agentContent, error) { + manifestPath := filepath.Join(modulePath, datasetName, "manifest.yml") + manifestFile, err := ioutil.ReadFile(manifestPath) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "reading manifest file failed (path: %s)", manifestPath) + } + + vars, err := createLogStreamVariables(manifestFile) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "creating log stream variables failed (path: %s)", manifestPath) + } + + configFilePaths, err := filepath.Glob(filepath.Join(modulePath, datasetName, "config", "*.*")) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "locating config files failed (modulePath: %s, datasetName: %s)", modulePath, datasetName) + } + + if len(configFilePaths) == 0 { + return nil, agentContent{}, fmt.Errorf("expected at least one config file (modulePath: %s, datasetName: %s)", modulePath, datasetName) + } + + var streams []util.Stream + var agent agentContent + for _, configFilePath := range configFilePaths { + fileName := extractInputConfigFilename(configFilePath) + fileContent, err := ioutil.ReadFile(configFilePath) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "reading file from config directory failed (filePath: %s)", configFilePath) + } + + if strings.HasSuffix(configFilePath, ".js") { + agent.streams = append(agent.streams, streamContent{ + targetFileName: fileName, + body: fileContent, + }) + continue + } + + root, err := parseStreamConfig(fileContent) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "parsing stream config failed") + } + + for _, inputType := range root.inputTypes() { + aType := inputType + if inputType == "log" { + aType = "logs" + } + targetFileName := inputType + ".yml.hbs" + + inputConfig := root.configForInput(inputType) + agent.streams = append(agent.streams, streamContent{ + targetFileName: targetFileName, + body: inputConfig, + }) + + streams = append(streams, util.Stream{ + Input: aType, + Title: fmt.Sprintf("%s %s logs (%s)", moduleTitle, datasetName, inputType), + Description: fmt.Sprintf("Collect %s %s logs using %s input", moduleTitle, datasetName, inputType), + TemplatePath: targetFileName, + Vars: root.filterVarsForInput(inputType, vars), + }) + } + } + return streams, agent, nil +} + +// wrapVariablesWithDefault method builds a set of stream inputs for metrics oriented dataset. +// The method combines all config files in module's _meta directory, unmarshals all configuration entries and selects +// ones related to the particular metricset (first seen, first occurrence, next occurrences skipped). +// +// The method skips commented variables, but keeps arrays of structures (even if it's not possible to render them using +// UI). +func createMetricStreams(modulePath, moduleName, moduleTitle, datasetName string) ([]util.Stream, agentContent, error) { + merged, err := mergeMetaConfigFiles(modulePath) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "merging config files failed") + } + + vars, err := createMetricStreamVariables(merged, moduleName, datasetName) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "creating metric stream variables failed (modulePath: %s)", modulePath) + } + streams := []util.Stream{ + { + Input: moduleName + "/metrics", + Title: fmt.Sprintf("%s %s metrics", moduleTitle, datasetName), + Description: fmt.Sprintf("Collect %s %s metrics", moduleTitle, datasetName), + Vars: vars, + }, + } + + agent, err := createAgentContentForMetrics(moduleName, datasetName, streams) + if err != nil { + return nil, agentContent{}, errors.Wrapf(err, "creating agent content for logs failed (modulePath: %s, datasetName: %s)", + modulePath, datasetName) + } + return streams, agent, nil +} + +// mergeMetaConfigFiles method visits all configuration YAML files and combines them into single document. +func mergeMetaConfigFiles(modulePath string) ([]byte, error) { + configFilePaths, err := filepath.Glob(filepath.Join(modulePath, "_meta", "config*.yml")) + if err != nil { + return nil, errors.Wrapf(err, "locating config files failed (modulePath: %s)", modulePath) + } + + var mergedConfig bytes.Buffer + for _, configFilePath := range configFilePaths { + configFile, err := ioutil.ReadFile(configFilePath) + if err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "reading config file failed (path: %s)", configFilePath) + } + mergedConfig.Write(configFile) + mergedConfig.WriteString("\n") + } + return mergedConfig.Bytes(), nil +} diff --git a/dev/import-beats/streams_config_parser.go b/dev/import-beats/streams_config_parser.go new file mode 100644 index 00000000000..0229db59b21 --- /dev/null +++ b/dev/import-beats/streams_config_parser.go @@ -0,0 +1,389 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "bytes" + "fmt" + "regexp" + "text/template/parse" + + "github.com/pkg/errors" + + "github.com/elastic/package-registry/util" +) + +type streamConfigParsed struct { + tree *parse.Tree +} + +func parseStreamConfig(content []byte) (*streamConfigParsed, error) { + mapOfParsed, err := parse.Parse("input-config", string(content), "", "", map[string]interface{}{ + "eq": func() {}, + "printf": func() {}, + }) + if err != nil { + return nil, errors.Wrapf(err, "parsing template failed") + } + return &streamConfigParsed{ + tree: mapOfParsed["input-config"], + }, nil +} + +func (scp *streamConfigParsed) inputTypes() []string { + return uniqueStringValues(inputTypesForNode(scp.tree.Root)) +} + +func inputTypesForNode(node parse.Node) []string { + textNode, isTextNode := node.(*parse.TextNode) + if isTextNode { + inputType, ok := extractInputTypeFromTextNode(textNode) + if ok { + return []string{inputType} + } + return nil + } + + listNode, isListNode := node.(*parse.ListNode) + if isListNode { + return inputTypesForListNode(listNode) + } + + ifNode, isIfNode := node.(*parse.IfNode) + if isIfNode { + var inputTypes []string + + if ifNode.List != nil { + inputTypes = append(inputTypes, inputTypesForListNode(ifNode.List)...) + } + if ifNode.ElseList != nil { + inputTypes = append(inputTypes, inputTypesForListNode(ifNode.ElseList)...) + } + return inputTypes + } + return nil +} + +func extractInputTypeFromTextNode(textNode *parse.TextNode) (string, bool) { + i := bytes.Index(textNode.Text, []byte("type: ")) + if i > -1 && (i == 0 || textNode.Text[i-1] == '\n') { + aType := textNode.Text[i+6:] + j := bytes.IndexByte(aType, '\n') + if j < 0 { + j = len(textNode.Text) + } + aType = aType[:j] + return string(aType), true + } + return "", false +} + +func inputTypesForListNode(listNode *parse.ListNode) []string { + var inputTypes []string + for _, listedNode := range listNode.Nodes { + it := inputTypesForNode(listedNode) + inputTypes = append(inputTypes, it...) + } + return inputTypes +} + +func (scp *streamConfigParsed) configForInput(inputType string) []byte { + if inputType == "log" { + inputType = "file" + } + + config := configForInputForNode(scp.tree.Root, inputType) + r := regexp.MustCompile("(\n)+") + return bytes.TrimSpace(r.ReplaceAll(config, []byte{'\n'})) +} + +func configForInputForNode(node parse.Node, inputType string) []byte { + textNode, isTextNode := node.(*parse.TextNode) + if isTextNode { + return writeHandlebarsTextNode(textNode) + } + + listNode, isListNode := node.(*parse.ListNode) + if isListNode { + return writeHandlebarsListNode(listNode, inputType) + } + + ifNode, isIfNode := node.(*parse.IfNode) + if isIfNode { + return writeHandlebarsIfNode(ifNode, inputType) + } + + rangeNode, isRangeNode := node.(*parse.RangeNode) + if isRangeNode { + return writeHandlebarsRangeNode(rangeNode, inputType) + } + + actionNode, isActionNode := node.(*parse.ActionNode) + if isActionNode { + return writeHandlebarsActionNode(actionNode) + } + + panic(fmt.Sprintf("unsupported node: %s", node.String())) +} + +func writeHandlebarsTextNode(textNode *parse.TextNode) []byte { + i := bytes.Index(textNode.Text, []byte("type: ")) + if i > -1 && (i == 0 || textNode.Text[i-1] == '\n') { + var buffer bytes.Buffer + buffer.Write(textNode.Text[0:i]) + + j := bytes.Index(textNode.Text[i:], []byte{'\n'}) + if j > 0 { + buffer.Write(textNode.Text[i+j+1:]) + return buffer.Bytes() + } + } + return textNode.Text +} + +func writeHandlebarsListNode(listNode *parse.ListNode, inputType string) []byte { + var buffer bytes.Buffer + for _, listedNode := range listNode.Nodes { + buf := configForInputForNode(listedNode, inputType) + buffer.Write(buf) + } + return buffer.Bytes() +} + +func writeHandlebarsIfNode(ifNode *parse.IfNode, inputType string) []byte { + var buffer bytes.Buffer + if isIfNodeEqInput(ifNode) { + if isIfNodeEqInputInputType(ifNode, inputType) { + if ifNode.List != nil { + buffer.Write(configForInputForNode(ifNode.List, inputType)) + } + } else { + if ifNode.ElseList != nil { + buffer.Write(configForInputForNode(ifNode.ElseList, inputType)) + } + } + } else { + if len(ifNode.Pipe.Cmds[0].Args) == 1 { + var1 := ifNode.Pipe.Cmds[0].Args[0].String()[1:] + buffer.WriteString(fmt.Sprintf("{{#if %s}}", var1)) + } else { + buffer.WriteString(fmt.Sprintf("{{#if %s}}", ifNode.Pipe.String())) + } + + if ifNode.List != nil { + buffer.Write(configForInputForNode(ifNode.List, inputType)) + } + if ifNode.ElseList != nil { + buffer.WriteString("{{else}}") + buffer.Write(configForInputForNode(ifNode.ElseList, inputType)) + } + buffer.WriteString("{{/if}}") + } + return buffer.Bytes() +} + +func isIfNodeEqInput(ifNode *parse.IfNode) bool { + if len(ifNode.Pipe.Cmds[0].Args) > 1 { + op := ifNode.Pipe.Cmds[0].Args[0].String() + var1 := ifNode.Pipe.Cmds[0].Args[1].String() + + if op == "eq" && var1 == ".input" { + return true + } + } + return false +} + +func isIfNodeEqInputInputType(ifNode *parse.IfNode, inputType string) bool { + if len(ifNode.Pipe.Cmds[0].Args) > 1 { + op := ifNode.Pipe.Cmds[0].Args[0].String() + var1 := ifNode.Pipe.Cmds[0].Args[1].String() + var2 := ifNode.Pipe.Cmds[0].Args[2].String() + + if op == "eq" && var1 == ".input" && var2 == fmt.Sprintf(`"%s"`, inputType) { + return true + } + } + return false +} + +func writeHandlebarsActionNode(actionNode *parse.ActionNode) []byte { + var buffer bytes.Buffer + if len(actionNode.Pipe.Cmds) > 0 { + cmdArgs := writeHandlebarsCmdArgs(actionNode.Pipe.Cmds[0].Args) + buffer.WriteString("{{") + buffer.Write(cmdArgs) + buffer.WriteString("}}") + } + return buffer.Bytes() +} + +func writeHandlebarsRangeNode(rangeNode *parse.RangeNode, inputType string) []byte { + var buffer bytes.Buffer + + cmdArgs := writeHandlebarsCmdArgs(rangeNode.Pipe.Cmds[0].Args) + decl := writeHandlebarsCmdDecl(rangeNode.Pipe.Decl) + buffer.WriteString("{{#each ") + buffer.Write(cmdArgs) + buffer.Write(decl) + buffer.WriteString("}}") + buffer.Write(writeHandlebarsListNode(rangeNode.List, inputType)) + buffer.WriteString("{{/each}}") + return buffer.Bytes() +} + +func writeHandlebarsCmdArgs(args []parse.Node) []byte { + var buffer bytes.Buffer + for i, arg := range args { + argWithoutDot := arg.String()[1:] + if len(argWithoutDot) == 0 { + argWithoutDot = "this" + } + buffer.WriteString(argWithoutDot) + if i != (len(args) - 1) { + buffer.WriteString(" ") + } + } + return buffer.Bytes() +} + +func writeHandlebarsCmdDecl(decl []*parse.VariableNode) []byte { + var buffer bytes.Buffer + + if len(decl) > 0 { + buffer.WriteString(" as |") + } + + for i := len(decl) - 1; i >= 0; i-- { + aVar := decl[i].String()[1:] + buffer.WriteString(aVar) + + if i != 0 { + buffer.WriteByte(' ') + } + } + + if len(decl) > 0 { + buffer.WriteString("|") + } + return buffer.Bytes() +} + +func (scp *streamConfigParsed) filterVarsForInput(inputType string, vars []util.Variable) []util.Variable { + variableNamesForInput := scp.variableNamesForInput(inputType) + var filtered []util.Variable + for _, aVar := range vars { + var found bool + for _, variableName := range variableNamesForInput { + if aVar.Name == variableName { + found = true + break + } + } + + if found { + filtered = append(filtered, aVar) + } + } + return filtered +} + +func (scp *streamConfigParsed) variableNamesForInput(inputType string) []string { + if inputType == "log" { + inputType = "file" + } + + var variables []string + + variables = variableNamesForInputForNode(scp.tree.Root, inputType, variables) + return uniqueStringValues(variables) +} + +func variableNamesForInputForNode(node parse.Node, inputType string, variables []string) []string { + _, isTextNode := node.(*parse.TextNode) + if isTextNode { + return variables // do nothing, there are no variables + } + + listNode, isListNode := node.(*parse.ListNode) + if isListNode { + return variableNamesListNode(listNode, inputType, variables) + } + + ifNode, isIfNode := node.(*parse.IfNode) + if isIfNode { + return variableNamesIfNode(ifNode, inputType, variables) + } + + rangeNode, isRangeNode := node.(*parse.RangeNode) + if isRangeNode { + return variableNamesRangeNode(rangeNode, inputType, variables) + } + + actionNode, isActionNode := node.(*parse.ActionNode) + if isActionNode { + return variableNamesForNodeArgs(actionNode.Pipe.Cmds[0].Args, variables) + } + + panic(fmt.Sprintf("unsupported node: %s", node.String())) +} + +func variableNamesListNode(listNode *parse.ListNode, inputType string, vars []string) []string { + var variables []string + variables = append(variables, vars...) + + for _, listedNode := range listNode.Nodes { + variables = uniqueStringValues(append(variables, variableNamesForInputForNode(listedNode, inputType, variables)...)) + } + return variables +} + +func variableNamesIfNode(ifNode *parse.IfNode, inputType string, vars []string) []string { + var variables []string + variables = append(variables, vars...) + + if isIfNodeEqInput(ifNode) { + if isIfNodeEqInputInputType(ifNode, inputType) { + if ifNode.List != nil { + variables = uniqueStringValues(append(variableNamesForInputForNode(ifNode.List, inputType, variables))) + } + } else { + if ifNode.ElseList != nil { + variables = uniqueStringValues(append(variableNamesForInputForNode(ifNode.ElseList, inputType, variables))) + } + } + } else { + if ifNode.List != nil { + variables = uniqueStringValues(append(variableNamesForInputForNode(ifNode.List, inputType, variables))) + } + if ifNode.ElseList != nil { + variables = uniqueStringValues(append(variableNamesForInputForNode(ifNode.ElseList, inputType, variables))) + } + + variables = uniqueStringValues(append(variables, variableNamesForNodeArgs(ifNode.Pipe.Cmds[0].Args, variables)...)) + } + return variables +} + +func variableNamesRangeNode(rangeNode *parse.RangeNode, inputType string, vars []string) []string { + var variables []string + variables = append(variables, vars...) + + variables = uniqueStringValues(append(variables, variableNamesListNode(rangeNode.List, inputType, variables)...)) + variables = uniqueStringValues(append(variables, variableNamesForNodeArgs(rangeNode.Pipe.Cmds[0].Args, variables)...)) + return variables +} + +func variableNamesForNodeArgs(args []parse.Node, vars []string) []string { + var variables []string + variables = append(variables, vars...) + + if len(args) > 0 { + for _, arg := range args { + variables = append(variables, arg.String()[1:]) + } + } + return variables +} diff --git a/dev/import-beats/strings.go b/dev/import-beats/strings.go new file mode 100644 index 00000000000..022b8d409c1 --- /dev/null +++ b/dev/import-beats/strings.go @@ -0,0 +1,17 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +func uniqueStringValues(fieldNames []string) []string { + t := make(map[string]bool) + var unique []string + for _, f := range fieldNames { + if _, ok := t[f]; !ok { + t[f] = true + unique = append(unique, f) + } + } + return unique +} diff --git a/dev/import-beats/variables.go b/dev/import-beats/variables.go new file mode 100644 index 00000000000..878eb3799dc --- /dev/null +++ b/dev/import-beats/variables.go @@ -0,0 +1,263 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "fmt" + "sort" + "strings" + + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" + + "github.com/elastic/package-registry/util" +) + +type manifestWithVars struct { + Vars []util.Variable `yaml:"var"` +} + +type varWithDefault struct { + Default interface{} `yaml:"default"` +} + +type manifestWithVarsOsFlattened struct { + Vars []variableWithOsFlattened `yaml:"var"` +} + +type variableWithOsFlattened struct { + OsDarwin interface{} `yaml:"os.darwin,omitempty"` + OsWindows interface{} `yaml:"os.windows,omitempty"` +} + +var ignoredConfigOptions = []string{ + "module", + "metricsets", + "enabled", +} + +func createLogStreamVariables(manifestFile []byte) ([]util.Variable, error) { + var mwv manifestWithVars + err := yaml.Unmarshal(manifestFile, &mwv) + if err != nil { + return nil, errors.Wrap(err, "unmarshalling manifest file failed") + } + + var mwvos manifestWithVarsOsFlattened + err = yaml.Unmarshal(manifestFile, &mwvos) + if err != nil { + return nil, errors.Wrap(err, "unmarshalling flattened OS failed") + } + adjusted, err := adjustVariablesFormat(mwvos, mwv) + if err != nil { + return nil, errors.Wrap(err, "adjusting log stream variables failed") + } + return adjusted.Vars, nil +} + +func createMetricStreamVariables(configFileContent []byte, moduleName, datasetName string) ([]util.Variable, error) { + var vars []util.Variable + if len(configFileContent) == 0 { + return vars, nil + } + + var moduleConfig []mapStr + err := yaml.Unmarshal(configFileContent, &moduleConfig) + if err != nil { + return nil, errors.Wrap(err, "unmarshalling module config failed") + } + + foundConfigEntries := map[string]bool{} + + for _, moduleConfigEntry := range moduleConfig { + flatEntry := moduleConfigEntry.flatten() + related, err := isConfigEntryRelatedToMetricset(flatEntry, moduleName, datasetName) + if err != nil { + return nil, errors.Wrapf(err, "checking if config entry is related failed") + } + + for name, value := range flatEntry { + if shouldConfigOptionBeIgnored(name, value) { + continue + } + + if _, ok := foundConfigEntries[name]; ok { + continue // already processed this config option + } + + if related || strings.HasPrefix(name, fmt.Sprintf("%s.", datasetName)) { + var isArray bool + variableType := determineInputVariableType(name, value) + if variableType == "yaml" { + m, err := yaml.Marshal(value) + if err != nil { + return nil, errors.Wrapf(err, "marshalling object configuration variable failed") + } + value = string(m) + } else { + _, isArray = value.([]interface{}) + } + aVar := util.Variable{ + Name: name, + Type: variableType, + Title: toVariableTitle(name), + Multi: isArray, + Required: determineInputVariableIsRequired(value), + ShowUser: true, + Default: value, + } + + vars = append(vars, aVar) + foundConfigEntries[name] = true + } + } + } + + // sort variables to keep them in order while using version control. + sort.Slice(vars, func(i, j int) bool { + return sort.StringsAreSorted([]string{vars[i].Name, vars[j].Name}) + }) + return vars, nil +} + +// adjustVariablesFormat method adjusts the format of variables defined in manifest: +// - ensure that all variable values are wrapped with a "default" field, even if they are defined for particular +// operating systems (prefix: os.) +// - add field "multi: true" if value is an array +func adjustVariablesFormat(mwvos manifestWithVarsOsFlattened, mwvs manifestWithVars) (manifestWithVars, error) { + var withDefaults manifestWithVars + for i, aVar := range mwvs.Vars { + var isArray bool + variableType := determineInputVariableType(aVar.Name, aVar.Default) + if variableType == "yaml" { + m, err := yaml.Marshal(aVar.Default) + if err != nil { + return manifestWithVars{}, errors.Wrapf(err, "marshalling object configuration variable failed") + } + aVar.Default = string(m) + } else { + _, isArray = aVar.Default.([]interface{}) + } + + aVarWithDefaults := aVar + aVarWithDefaults.Title = toVariableTitle(aVar.Name) + aVarWithDefaults.Type = variableType + aVarWithDefaults.Required = determineInputVariableIsRequired(aVar.Default) + aVarWithDefaults.ShowUser = true + aVarWithDefaults.Multi = isArray + aVarWithDefaults.Os = unwrapOsVars(mwvos.Vars[i]) + + if aVarWithDefaults.Os != nil { + if aVarWithDefaults.Os.Darwin != nil { + aVarWithDefaults.Os.Darwin = varWithDefault{ + Default: aVarWithDefaults.Os.Darwin, + } + } + + if aVarWithDefaults.Os.Windows != nil { + aVarWithDefaults.Os.Windows = varWithDefault{ + Default: aVarWithDefaults.Os.Windows, + } + } + } + withDefaults.Vars = append(withDefaults.Vars, aVarWithDefaults) + } + return withDefaults, nil +} + +func unwrapOsVars(flattened variableWithOsFlattened) *util.Os { + var anOs *util.Os + if flattened.OsDarwin != nil || flattened.OsWindows != nil { + anOs = new(util.Os) + anOs.Darwin = flattened.OsDarwin + anOs.Windows = flattened.OsWindows + } + return anOs +} + +// shouldConfigOptionBeIgnored method checks if the configuration option name should be skipped (not used, duplicate, etc.) +func shouldConfigOptionBeIgnored(optionName string, value interface{}) bool { + if value == nil { + return true + } + + for _, ignored := range ignoredConfigOptions { + if ignored == optionName { + return true + } + } + return false +} + +// isConfigEntryRelatedToMetricset method checks if the configuration entry may affect the dataset settings, +// in other words, checks if the "metricsets" field is present and contains the given datasetName. +func isConfigEntryRelatedToMetricset(entry mapStr, moduleName, datasetName string) (bool, error) { + var metricsetRelated bool + if metricsets, ok := entry["metricsets"]; ok { + metricsetsMapped, ok := metricsets.([]interface{}) + if !ok { + return false, fmt.Errorf("mapping metricsets failed (moduleName: %s, datasetName: %s)", + moduleName, datasetName) + } + if len(metricsetsMapped) == 0 { + return false, fmt.Errorf("no metricsets defined (moduleName: %s, datasetName: %s)", moduleName, + datasetName) + } + + for _, metricset := range metricsetsMapped { + if metricset.(string) == datasetName { + metricsetRelated = true + break + } + } + } + return metricsetRelated, nil +} + +// determineInputVariableIsRequired method determines is the configuration variable should be marked as "required". +// If the variable is string and its default value is empty, it can be assumed that isn't required. +func determineInputVariableIsRequired(v interface{}) bool { + if v == nil { + return false + } + + val, isString := v.(string) + if isString && val == "" { + return false + } + return true +} + +// determineInputVariableType method determines the most appropriate type of the value or the value in array. +// Support types: text, password, bool, integer +func determineInputVariableType(name, v interface{}) string { + if arr, isArray := v.([]interface{}); isArray { + if len(arr) == 0 { + return "text" // array doesn't contain any items, assuming default type + } + return determineInputVariableType(name, arr[0]) + } + + if _, isBool := v.(bool); isBool { + return "bool" + } else if _, isInt := v.(int); isInt { + return "integer" + } + + if name == "password" { + return "password" + } + + if _, isString := v.(string); isString || v == nil { + return "text" + } + return "yaml" +} + +func toVariableTitle(name string) string { + name = strings.ReplaceAll(name, "_", " ") + name = strings.ReplaceAll(name, ".", " ") + return strings.Title(name) +} diff --git a/dev/import-beats/variables_compact.go b/dev/import-beats/variables_compact.go new file mode 100644 index 00000000000..6bc8441ac6f --- /dev/null +++ b/dev/import-beats/variables_compact.go @@ -0,0 +1,109 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package main + +import ( + "strings" + + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" + + "github.com/elastic/package-registry/util" +) + +func compactDatasetVariables(datasets datasetContentArray) (datasetContentArray, map[string][]util.Variable, error) { // map[inputType][]util.Variable + varsPerInputType := map[string][]util.Variable{} + var compacted datasetContentArray + + for _, dataset := range datasets { + for i, stream := range dataset.manifest.Streams { + var notCompactedVars []util.Variable + for _, aVar := range stream.Vars { + isAlreadyCompacted := isVariableAlreadyCompacted(varsPerInputType, aVar, stream.Input) + if !isAlreadyCompacted { + canBeCompacted, err := canVariableBeCompacted(datasets, aVar, stream.Input) + if err != nil { + return nil, nil, errors.Wrap(err, "checking compactibility failed") + } + if canBeCompacted { + varsPerInputType[stream.Input] = append(varsPerInputType[stream.Input], aVar) + } else { + notCompactedVars = append(notCompactedVars, aVar) + } + } + } + stream.Vars = notCompactedVars + dataset.manifest.Streams[i] = stream + } + compacted = append(compacted, dataset) + } + return compacted, varsPerInputType, nil +} + +func isVariableAlreadyCompacted(varsPerInputType map[string][]util.Variable, aVar util.Variable, inputType string) bool { + if vars, ok := varsPerInputType[inputType]; ok { + for _, v := range vars { + if v.Name == aVar.Name { + return true // variable already compacted + } + } + } + return false +} + +func canVariableBeCompacted(datasets datasetContentArray, aVar util.Variable, inputType string) (bool, error) { + for _, dataset := range datasets { + var varUsed bool + + for _, stream := range dataset.manifest.Streams { + if stream.Input != inputType { + break // input is not related with this var + } + + for _, streamVar := range stream.Vars { + if isNonCompactableVariable(aVar) { + continue + } + + equal, err := areVariablesEqual(streamVar, aVar) + if err != nil { + return false, errors.Wrap(err, "comparing variables failed") + } + if equal { + varUsed = true + break + } + } + } + + if !varUsed { + return false, nil // variable not present in this dataset + } + } + return true, nil +} + +func areVariablesEqual(first util.Variable, second util.Variable) (bool, error) { + if first.Name != second.Name || first.Type != second.Type { + return false, nil + } + + firstValue, err := yaml.Marshal(first.Default) + if err != nil { + return false, errors.Wrap(err, "marshalling first value failed") + } + secondValue, err := yaml.Marshal(second.Default) + if err != nil { + return false, errors.Wrap(err, "marshalling second value failed") + } + + firstValueStr := strings.TrimSpace(string(firstValue)) + secondValueStr := strings.TrimSpace(string(secondValue)) + return firstValueStr == secondValueStr, nil +} + +func isNonCompactableVariable(aVar util.Variable) bool { + return aVar.Name == "period" || aVar.Name == "paths" +} diff --git a/dev/packages/beats/.empty b/dev/packages/beats/.empty new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go.mod b/go.mod new file mode 100644 index 00000000000..93c251d9395 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/elastic/integrations + +go 1.12 + +require ( + github.com/blang/semver v3.5.1+incompatible + github.com/elastic/package-registry v0.2.1-0.20200505085109-20f433bd57ec + github.com/magefile/mage v1.9.0 + github.com/pkg/errors v0.9.1 + gopkg.in/yaml.v2 v2.2.8 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000000..0ad16805e0c --- /dev/null +++ b/go.sum @@ -0,0 +1,26 @@ +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-ucfg v0.8.4-0.20200415140258-1232bd4774a6 h1:Ehbr7du4rSSEypR8zePr0XRbMhO4PJgcHC9f8fDbgAg= +github.com/elastic/go-ucfg v0.8.4-0.20200415140258-1232bd4774a6/go.mod h1:iaiY0NBIYeasNgycLyTvhJftQlQEUO2hpF+FX0JKxzo= +github.com/elastic/package-registry v0.2.1-0.20200505085109-20f433bd57ec h1:hzQad3csxKcdp62jHNPCTrc0EFqIMS4/cljzlMzkV8Q= +github.com/elastic/package-registry v0.2.1-0.20200505085109-20f433bd57ec/go.mod h1:GixuT1hgq1gT2wXmbe4ENyXmzzgu5SAdpYEpiYZZMmc= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= +github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/magefile.go b/magefile.go new file mode 100644 index 00000000000..67081372bd3 --- /dev/null +++ b/magefile.go @@ -0,0 +1,233 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build mage + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/magefile/mage/mg" + "github.com/magefile/mage/sh" +) + +var ( + // GoImportsImportPath controls the import path used to install goimports. + GoImportsImportPath = "golang.org/x/tools/cmd/goimports" + + // GoImportsLocalPrefix is a string prefix matching imports that should be + // grouped after third-party packages. + GoImportsLocalPrefix = "github.com/elastic" + + // GoLicenserImportPath controls the import path used to install go-licenser. + GoLicenserImportPath = "github.com/elastic/go-licenser" + + publicDir = "./public" + buildDir = "./build" + packagePaths = []string{"./dev/packages/alpha/", "./dev/packages/example/", "./dev/packages/beats/"} + tarGz = true +) + +func Build() error { + + err := os.RemoveAll(publicDir) + if err != nil { + return err + } + + err = os.MkdirAll(publicDir, 0755) + if err != nil { + return err + } + + for _, p := range packagePaths { + err := sh.Run("go", "run", "github.com/elastic/package-registry/dev/generator/", "-sourceDir="+p, "-publicDir="+publicDir, "-tarGz="+strconv.FormatBool(tarGz)) + if err != nil { + return err + } + } + + err = BuildRootFile() + if err != nil { + return err + } + return nil +} + +func ImportBeats() error { + args := []string{"run", "./dev/import-beats/"} + if os.Getenv("SKIP_KIBANA") == "true" { + args = append(args, "-skipKibana") + } + if os.Getenv("PACKAGES") != "" { + args = append(args, "-packages", os.Getenv("PACKAGES")) + } + args = append(args, "*.go") + return sh.Run("go", args...) +} + +// Creates the `index.json` file +// For now only containing the version. +func BuildRootFile() error { + rootData := map[string]string{ + "version": "0.3.0", + "service.name": "package-registry", + } + + return writeJsonFile(rootData, publicDir+"/index.json") +} + +func writeJsonFile(v interface{}, path string) error { + data, err := json.MarshalIndent(v, "", " ") + if err != nil { + return err + } + + return ioutil.WriteFile(path, data, 0644) +} + +func Check() error { + Format() + + // Setup the variables for the tests and not create tarGz files + publicDir = "./testdata/public" + packagePaths = []string{"testdata/package"} + tarGz = false + + err := Build() + if err != nil { + return err + } + + err = Vendor() + if err != nil { + return err + } + + err = PrepareTest() + if err != nil { + return err + } + + // Check if no changes are shown + err = sh.RunV("git", "update-index", "--refresh") + if err != nil { + return err + } + return sh.RunV("git", "diff-index", "--exit-code", "HEAD", "--") +} + +func PrepareTest() error { + return sh.RunV("go", "get", "-v", "-u", "github.com/jstemmer/go-junit-report") +} + +func Test() error { + err := PrepareTest() + if err != nil { + return err + } + return sh.RunV("go", "test", "./...", "-v", "2>&1", "|", "go-junit-report", ">", "junit-report.xml") +} + +// Format adds license headers, formats .go files with goimports, and formats +// .py files with autopep8. +func Format() { + // Don't run AddLicenseHeaders and GoImports concurrently because they + // both can modify the same files. + mg.Deps(AddLicenseHeaders) + mg.Deps(GoImports) +} + +// GoImports executes goimports against all .go files in and below the CWD. It +// ignores vendor/ directories. +func GoImports() error { + goFiles, err := FindFilesRecursive(func(path string, _ os.FileInfo) bool { + return filepath.Ext(path) == ".go" && !strings.Contains(path, "vendor/") + }) + if err != nil { + return err + } + if len(goFiles) == 0 { + return nil + } + + fmt.Println(">> fmt - goimports: Formatting Go code") + args := append( + []string{"-local", GoImportsLocalPrefix, "-l", "-w"}, + goFiles..., + ) + + return sh.RunV("goimports", args...) +} + +// AddLicenseHeaders adds license headers to .go files. It applies the +// appropriate license header based on the value of mage.BeatLicense. +func AddLicenseHeaders() error { + fmt.Println(">> fmt - go-licenser: Adding missing headers") + return sh.RunV("go-licenser", "-license", "Elastic") +} + +// FindFilesRecursive recursively traverses from the CWD and invokes the given +// match function on each regular file to determine if the given path should be +// returned as a match. +func FindFilesRecursive(match func(path string, info os.FileInfo) bool) ([]string, error) { + var matches []string + err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.Mode().IsRegular() { + // continue + return nil + } + + if match(filepath.ToSlash(path), info) { + matches = append(matches, path) + } + return nil + }) + return matches, err +} + +func Clean() error { + err := os.RemoveAll(buildDir) + if err != nil { + return err + } + + err = os.RemoveAll(publicDir) + if err != nil { + return err + } + + return os.Remove("package-registry") +} + +func Vendor() error { + fmt.Println(">> mod - updating vendor directory") + + err := sh.RunV("go", "mod", "tidy") + if err != nil { + return err + } + + sh.RunV("go", "mod", "vendor") + if err != nil { + return err + } + + sh.RunV("go", "mod", "verify") + if err != nil { + return err + } + return nil +} diff --git a/repository/README.md b/repository/README.md deleted file mode 100644 index 7fa10b90d5a..00000000000 --- a/repository/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Integrations repository (ONLY EXPERIMENTAL USE) - -Please see https://github.com/elastic/integrations-registry for details. - diff --git a/specs/README.md b/specs/README.md deleted file mode 100644 index 9fdb9ef5bb3..00000000000 --- a/specs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Integration Specifications (EXPERIMENTAL USE) - -This folder contains specifications for the different integrations to develop. They are based on the provided [template](SPEC_TEMPLATE.md). - diff --git a/specs/SPEC_TEMPLATE.md b/specs/SPEC_TEMPLATE.md deleted file mode 100644 index bc60e84c8f8..00000000000 --- a/specs/SPEC_TEMPLATE.md +++ /dev/null @@ -1,61 +0,0 @@ -# Integration Specification: - -``` -Code blocks used for template description -- to be removed when filled - -For each integration, create a specification document based on this template. -Use the integration name as a file name: e.g. kafka.md - -Use this section for any generic information relevant to the integration -``` - -## Scope - -``` -Describe of the expected scope of the integration: logging, metrics, etc. In case the surface area is too big, scope can be narrowed to certain areas and / or split into different milestones. -``` - -### Milestone 1 - -``` -If more than one milestone is required, just repeat the section. Remove the subsections that do not apply. -``` - -#### Target - -``` -Describe the target system versions to support and in which operating systems. -``` - -#### Logs - -``` -Whether log capabilities are included. If the target system provides multiple log streams, specify which of them should be included, as well as any special requirement related to log handling. -``` - -#### Metric sets - -``` -For each metricset: goal a brief description and metrics to include. -``` - -#### Dashboards - -``` -For each dashboard: goal, description, and a rough description of the information to include. -``` - -## Technical Approach - -``` -This section describes the technical approach to use to implement the integration. -``` - -## Decision Log - -``` -Specification documents are live. This section must contain the record of every decision taken changing the scope or technical approach of the integration (as the section above should always contain the current agreed upon scope). E.g., removing some metrics because they cannot be collected in an efficient way, moving some features to a later milestone because they depend on other developments, etc. - -Conversations that can impact the technical approach and / or scope may happen in PRs to the document or offline, but the final decision and rationale should be recorded here. -``` - diff --git a/testing/environments/README.md b/testing/environments/README.md new file mode 100644 index 00000000000..aea2dd344ca --- /dev/null +++ b/testing/environments/README.md @@ -0,0 +1,11 @@ +Firstly, refresh docker images: + +```bash +$ docker-compose -f snapshot.yml pull +``` + +Run docker containers: + +```bash +$ docker-compose -f snapshot.yml -f local.yml up --force-recreate +``` diff --git a/testing/environments/kibana.config.yml b/testing/environments/kibana.config.yml new file mode 100644 index 00000000000..8498c164b5d --- /dev/null +++ b/testing/environments/kibana.config.yml @@ -0,0 +1,12 @@ +server.name: kibana +server.host: "0" + +elasticsearch.hosts: [ "http://elasticsearch:9200" ] +elasticsearch.username: elastic +elasticsearch.password: changeme +xpack.monitoring.ui.container.elasticsearch.enabled: true + +xpack.ingestManager.enabled: true +xpack.ingestManager.epm.enabled: true +xpack.ingestManager.fleet.enabled: true +xpack.ingestManager.epm.registryUrl: "http://integrations-registry:8080" diff --git a/testing/environments/local.yml b/testing/environments/local.yml new file mode 100644 index 00000000000..2bbf7b92ea1 --- /dev/null +++ b/testing/environments/local.yml @@ -0,0 +1,21 @@ +# Defines if ports should be exported. +# This is useful for testing locally with a full elastic stack setup. +# All services can be reached through localhost like localhost:5601 for Kibana +# This is not used for CI as otherwise ports conflicts could happen. +version: '2.3' +services: + kibana: + ports: + - "127.0.0.1:5601:5601" + depends_on: + elasticsearch: + condition: service_healthy + + elasticsearch: + ports: + - "127.0.0.1:9200:9200" + + integrations-registry: + ports: + - "127.0.0.1:8080:8080" + diff --git a/testing/environments/snapshot.yml b/testing/environments/snapshot.yml new file mode 100644 index 00000000000..cd20c88d35f --- /dev/null +++ b/testing/environments/snapshot.yml @@ -0,0 +1,38 @@ +# This should start the environment with the latest snapshots. + +version: '2.3' +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.0.0-SNAPSHOT + healthcheck: + test: ["CMD", "curl", "-f", "-u", "elastic:changeme", "http://127.0.0.1:9200/"] + retries: 300 + interval: 1s + environment: + - "ES_JAVA_OPTS=-Xms1g -Xmx1g" + - "network.host=" + - "transport.host=127.0.0.1" + - "http.host=0.0.0.0" + - "indices.id_field_data.enabled=true" + - "xpack.license.self_generated.type=trial" + - "xpack.security.enabled=true" + - "xpack.security.authc.api_key.enabled=true" + - "ELASTIC_PASSWORD=changeme" + + kibana: + image: docker.elastic.co/kibana/kibana:8.0.0-SNAPSHOT + healthcheck: + test: "curl -f http://localhost:5601/login | grep kbn-injected-metadata 2>&1 >/dev/null" + retries: 600 + interval: 1s + volumes: + - ./kibana.config.yml:/usr/share/kibana/config/kibana.yml + + integrations-registry: + image: docker.elastic.co/package-registry/package-registry:master + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080"] + retries: 300 + interval: 1s + volumes: + - ../../public:/go/src/github.com/elastic/package-registry/public diff --git a/vendor/github.com/blang/semver/.travis.yml b/vendor/github.com/blang/semver/.travis.yml new file mode 100644 index 00000000000..102fb9a691b --- /dev/null +++ b/vendor/github.com/blang/semver/.travis.yml @@ -0,0 +1,21 @@ +language: go +matrix: + include: + - go: 1.4.3 + - go: 1.5.4 + - go: 1.6.3 + - go: 1.7 + - go: tip + allow_failures: + - go: tip +install: +- go get golang.org/x/tools/cmd/cover +- go get github.com/mattn/goveralls +script: +- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci + -repotoken $COVERALLS_TOKEN +- echo "Build examples" ; cd examples && go build +- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .) +env: + global: + secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw= diff --git a/vendor/github.com/blang/semver/LICENSE b/vendor/github.com/blang/semver/LICENSE new file mode 100644 index 00000000000..5ba5c86fcb0 --- /dev/null +++ b/vendor/github.com/blang/semver/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Benedikt Lang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/github.com/blang/semver/README.md b/vendor/github.com/blang/semver/README.md new file mode 100644 index 00000000000..08b2e4a3d76 --- /dev/null +++ b/vendor/github.com/blang/semver/README.md @@ -0,0 +1,194 @@ +semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master) +====== + +semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`. + +Usage +----- +```bash +$ go get github.com/blang/semver +``` +Note: Always vendor your dependencies or fix on a specific version tag. + +```go +import github.com/blang/semver +v1, err := semver.Make("1.0.0-beta") +v2, err := semver.Make("2.0.0-beta") +v1.Compare(v2) +``` + +Also check the [GoDocs](http://godoc.org/github.com/blang/semver). + +Why should I use this lib? +----- + +- Fully spec compatible +- No reflection +- No regex +- Fully tested (Coverage >99%) +- Readable parsing/validation errors +- Fast (See [Benchmarks](#benchmarks)) +- Only Stdlib +- Uses values instead of pointers +- Many features, see below + + +Features +----- + +- Parsing and validation at all levels +- Comparator-like comparisons +- Compare Helper Methods +- InPlace manipulation +- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1` +- Wildcards `>=1.x`, `<=2.5.x` +- Sortable (implements sort.Interface) +- database/sql compatible (sql.Scanner/Valuer) +- encoding/json compatible (json.Marshaler/Unmarshaler) + +Ranges +------ + +A `Range` is a set of conditions which specify which versions satisfy the range. + +A condition is composed of an operator and a version. The supported operators are: + +- `<1.0.0` Less than `1.0.0` +- `<=1.0.0` Less than or equal to `1.0.0` +- `>1.0.0` Greater than `1.0.0` +- `>=1.0.0` Greater than or equal to `1.0.0` +- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0` +- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`. + +Note that spaces between the operator and the version will be gracefully tolerated. + +A `Range` can link multiple `Ranges` separated by space: + +Ranges can be linked by logical AND: + + - `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0` + - `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2` + +Ranges can also be linked by logical OR: + + - `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x` + +AND has a higher precedence than OR. It's not possible to use brackets. + +Ranges can be combined by both AND and OR + + - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` + +Range usage: + +``` +v, err := semver.Parse("1.2.3") +range, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0") +if range(v) { + //valid +} + +``` + +Example +----- + +Have a look at full examples in [examples/main.go](examples/main.go) + +```go +import github.com/blang/semver + +v, err := semver.Make("0.0.1-alpha.preview+123.github") +fmt.Printf("Major: %d\n", v.Major) +fmt.Printf("Minor: %d\n", v.Minor) +fmt.Printf("Patch: %d\n", v.Patch) +fmt.Printf("Pre: %s\n", v.Pre) +fmt.Printf("Build: %s\n", v.Build) + +// Prerelease versions array +if len(v.Pre) > 0 { + fmt.Println("Prerelease versions:") + for i, pre := range v.Pre { + fmt.Printf("%d: %q\n", i, pre) + } +} + +// Build meta data array +if len(v.Build) > 0 { + fmt.Println("Build meta data:") + for i, build := range v.Build { + fmt.Printf("%d: %q\n", i, build) + } +} + +v001, err := semver.Make("0.0.1") +// Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE +v001.GT(v) == true +v.LT(v001) == true +v.GTE(v) == true +v.LTE(v) == true + +// Or use v.Compare(v2) for comparisons (-1, 0, 1): +v001.Compare(v) == 1 +v.Compare(v001) == -1 +v.Compare(v) == 0 + +// Manipulate Version in place: +v.Pre[0], err = semver.NewPRVersion("beta") +if err != nil { + fmt.Printf("Error parsing pre release version: %q", err) +} + +fmt.Println("\nValidate versions:") +v.Build[0] = "?" + +err = v.Validate() +if err != nil { + fmt.Printf("Validation failed: %s\n", err) +} +``` + + +Benchmarks +----- + + BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op + BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op + BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op + BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op + BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op + BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op + BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op + BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op + BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op + BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op + BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op + BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op + BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op + BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op + BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op + BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op + BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op + BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op + BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op + BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op + +See benchmark cases at [semver_test.go](semver_test.go) + + +Motivation +----- + +I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like. + + +Contribution +----- + +Feel free to make a pull request. For bigger changes create a issue first to discuss about it. + + +License +----- + +See [LICENSE](LICENSE) file. diff --git a/vendor/github.com/blang/semver/json.go b/vendor/github.com/blang/semver/json.go new file mode 100644 index 00000000000..a74bf7c4494 --- /dev/null +++ b/vendor/github.com/blang/semver/json.go @@ -0,0 +1,23 @@ +package semver + +import ( + "encoding/json" +) + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *Version) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + *v, err = Parse(versionString) + + return +} diff --git a/vendor/github.com/blang/semver/package.json b/vendor/github.com/blang/semver/package.json new file mode 100644 index 00000000000..1cf8ebdd9c1 --- /dev/null +++ b/vendor/github.com/blang/semver/package.json @@ -0,0 +1,17 @@ +{ + "author": "blang", + "bugs": { + "URL": "https://github.com/blang/semver/issues", + "url": "https://github.com/blang/semver/issues" + }, + "gx": { + "dvcsimport": "github.com/blang/semver" + }, + "gxVersion": "0.10.0", + "language": "go", + "license": "MIT", + "name": "semver", + "releaseCmd": "git commit -a -m \"gx publish $VERSION\"", + "version": "3.5.1" +} + diff --git a/vendor/github.com/blang/semver/range.go b/vendor/github.com/blang/semver/range.go new file mode 100644 index 00000000000..fca406d4793 --- /dev/null +++ b/vendor/github.com/blang/semver/range.go @@ -0,0 +1,416 @@ +package semver + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +type wildcardType int + +const ( + noneWildcard wildcardType = iota + majorWildcard wildcardType = 1 + minorWildcard wildcardType = 2 + patchWildcard wildcardType = 3 +) + +func wildcardTypefromInt(i int) wildcardType { + switch i { + case 1: + return majorWildcard + case 2: + return minorWildcard + case 3: + return patchWildcard + default: + return noneWildcard + } +} + +type comparator func(Version, Version) bool + +var ( + compEQ comparator = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 0 + } + compNE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) != 0 + } + compGT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 1 + } + compGE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) >= 0 + } + compLT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == -1 + } + compLE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) <= 0 + } +) + +type versionRange struct { + v Version + c comparator +} + +// rangeFunc creates a Range from the given versionRange. +func (vr *versionRange) rangeFunc() Range { + return Range(func(v Version) bool { + return vr.c(v, vr.v) + }) +} + +// Range represents a range of versions. +// A Range can be used to check if a Version satisfies it: +// +// range, err := semver.ParseRange(">1.0.0 <2.0.0") +// range(semver.MustParse("1.1.1") // returns true +type Range func(Version) bool + +// OR combines the existing Range with another Range using logical OR. +func (rf Range) OR(f Range) Range { + return Range(func(v Version) bool { + return rf(v) || f(v) + }) +} + +// AND combines the existing Range with another Range using logical AND. +func (rf Range) AND(f Range) Range { + return Range(func(v Version) bool { + return rf(v) && f(v) + }) +} + +// ParseRange parses a range and returns a Range. +// If the range could not be parsed an error is returned. +// +// Valid ranges are: +// - "<1.0.0" +// - "<=1.0.0" +// - ">1.0.0" +// - ">=1.0.0" +// - "1.0.0", "=1.0.0", "==1.0.0" +// - "!1.0.0", "!=1.0.0" +// +// A Range can consist of multiple ranges separated by space: +// Ranges can be linked by logical AND: +// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" +// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 +// +// Ranges can also be linked by logical OR: +// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" +// +// AND has a higher precedence than OR. It's not possible to use brackets. +// +// Ranges can be combined by both AND and OR +// +// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` +func ParseRange(s string) (Range, error) { + parts := splitAndTrim(s) + orParts, err := splitORParts(parts) + if err != nil { + return nil, err + } + expandedParts, err := expandWildcardVersion(orParts) + if err != nil { + return nil, err + } + var orFn Range + for _, p := range expandedParts { + var andFn Range + for _, ap := range p { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + vr, err := buildVersionRange(opStr, vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) + } + rf := vr.rangeFunc() + + // Set function + if andFn == nil { + andFn = rf + } else { // Combine with existing function + andFn = andFn.AND(rf) + } + } + if orFn == nil { + orFn = andFn + } else { + orFn = orFn.OR(andFn) + } + + } + return orFn, nil +} + +// splitORParts splits the already cleaned parts by '||'. +// Checks for invalid positions of the operator and returns an +// error if found. +func splitORParts(parts []string) ([][]string, error) { + var ORparts [][]string + last := 0 + for i, p := range parts { + if p == "||" { + if i == 0 { + return nil, fmt.Errorf("First element in range is '||'") + } + ORparts = append(ORparts, parts[last:i]) + last = i + 1 + } + } + if last == len(parts) { + return nil, fmt.Errorf("Last element in range is '||'") + } + ORparts = append(ORparts, parts[last:]) + return ORparts, nil +} + +// buildVersionRange takes a slice of 2: operator and version +// and builds a versionRange, otherwise an error. +func buildVersionRange(opStr, vStr string) (*versionRange, error) { + c := parseComparator(opStr) + if c == nil { + return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) + } + v, err := Parse(vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) + } + + return &versionRange{ + v: v, + c: c, + }, nil + +} + +// inArray checks if a byte is contained in an array of bytes +func inArray(s byte, list []byte) bool { + for _, el := range list { + if el == s { + return true + } + } + return false +} + +// splitAndTrim splits a range string by spaces and cleans whitespaces +func splitAndTrim(s string) (result []string) { + last := 0 + var lastChar byte + excludeFromSplit := []byte{'>', '<', '='} + for i := 0; i < len(s); i++ { + if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { + if last < i-1 { + result = append(result, s[last:i]) + } + last = i + 1 + } else if s[i] != ' ' { + lastChar = s[i] + } + } + if last < len(s)-1 { + result = append(result, s[last:]) + } + + for i, v := range result { + result[i] = strings.Replace(v, " ", "", -1) + } + + // parts := strings.Split(s, " ") + // for _, x := range parts { + // if s := strings.TrimSpace(x); len(s) != 0 { + // result = append(result, s) + // } + // } + return +} + +// splitComparatorVersion splits the comparator from the version. +// Input must be free of leading or trailing spaces. +func splitComparatorVersion(s string) (string, string, error) { + i := strings.IndexFunc(s, unicode.IsDigit) + if i == -1 { + return "", "", fmt.Errorf("Could not get version from string: %q", s) + } + return strings.TrimSpace(s[0:i]), s[i:], nil +} + +// getWildcardType will return the type of wildcard that the +// passed version contains +func getWildcardType(vStr string) wildcardType { + parts := strings.Split(vStr, ".") + nparts := len(parts) + wildcard := parts[nparts-1] + + possibleWildcardType := wildcardTypefromInt(nparts) + if wildcard == "x" { + return possibleWildcardType + } + + return noneWildcard +} + +// createVersionFromWildcard will convert a wildcard version +// into a regular version, replacing 'x's with '0's, handling +// special cases like '1.x.x' and '1.x' +func createVersionFromWildcard(vStr string) string { + // handle 1.x.x + vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) + vStr2 = strings.Replace(vStr2, ".x", ".0", 1) + parts := strings.Split(vStr2, ".") + + // handle 1.x + if len(parts) == 2 { + return vStr2 + ".0" + } + + return vStr2 +} + +// incrementMajorVersion will increment the major version +// of the passed version +func incrementMajorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[0]) + if err != nil { + return "", err + } + parts[0] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// incrementMajorVersion will increment the minor version +// of the passed version +func incrementMinorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[1]) + if err != nil { + return "", err + } + parts[1] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// expandWildcardVersion will expand wildcards inside versions +// following these rules: +// +// * when dealing with patch wildcards: +// >= 1.2.x will become >= 1.2.0 +// <= 1.2.x will become < 1.3.0 +// > 1.2.x will become >= 1.3.0 +// < 1.2.x will become < 1.2.0 +// != 1.2.x will become < 1.2.0 >= 1.3.0 +// +// * when dealing with minor wildcards: +// >= 1.x will become >= 1.0.0 +// <= 1.x will become < 2.0.0 +// > 1.x will become >= 2.0.0 +// < 1.0 will become < 1.0.0 +// != 1.x will become < 1.0.0 >= 2.0.0 +// +// * when dealing with wildcards without +// version operator: +// 1.2.x will become >= 1.2.0 < 1.3.0 +// 1.x will become >= 1.0.0 < 2.0.0 +func expandWildcardVersion(parts [][]string) ([][]string, error) { + var expandedParts [][]string + for _, p := range parts { + var newParts []string + for _, ap := range p { + if strings.Index(ap, "x") != -1 { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + + versionWildcardType := getWildcardType(vStr) + flatVersion := createVersionFromWildcard(vStr) + + var resultOperator string + var shouldIncrementVersion bool + switch opStr { + case ">": + resultOperator = ">=" + shouldIncrementVersion = true + case ">=": + resultOperator = ">=" + case "<": + resultOperator = "<" + case "<=": + resultOperator = "<" + shouldIncrementVersion = true + case "", "=", "==": + newParts = append(newParts, ">="+flatVersion) + resultOperator = "<" + shouldIncrementVersion = true + case "!=", "!": + newParts = append(newParts, "<"+flatVersion) + resultOperator = ">=" + shouldIncrementVersion = true + } + + var resultVersion string + if shouldIncrementVersion { + switch versionWildcardType { + case patchWildcard: + resultVersion, _ = incrementMinorVersion(flatVersion) + case minorWildcard: + resultVersion, _ = incrementMajorVersion(flatVersion) + } + } else { + resultVersion = flatVersion + } + + ap = resultOperator + resultVersion + } + newParts = append(newParts, ap) + } + expandedParts = append(expandedParts, newParts) + } + + return expandedParts, nil +} + +func parseComparator(s string) comparator { + switch s { + case "==": + fallthrough + case "": + fallthrough + case "=": + return compEQ + case ">": + return compGT + case ">=": + return compGE + case "<": + return compLT + case "<=": + return compLE + case "!": + fallthrough + case "!=": + return compNE + } + + return nil +} + +// MustParseRange is like ParseRange but panics if the range cannot be parsed. +func MustParseRange(s string) Range { + r, err := ParseRange(s) + if err != nil { + panic(`semver: ParseRange(` + s + `): ` + err.Error()) + } + return r +} diff --git a/vendor/github.com/blang/semver/semver.go b/vendor/github.com/blang/semver/semver.go new file mode 100644 index 00000000000..8ee0842e6ac --- /dev/null +++ b/vendor/github.com/blang/semver/semver.go @@ -0,0 +1,418 @@ +package semver + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + numbers string = "0123456789" + alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + alphanum = alphas + numbers +) + +// SpecVersion is the latest fully supported spec version of semver +var SpecVersion = Version{ + Major: 2, + Minor: 0, + Patch: 0, +} + +// Version represents a semver compatible version +type Version struct { + Major uint64 + Minor uint64 + Patch uint64 + Pre []PRVersion + Build []string //No Precendence +} + +// Version to string +func (v Version) String() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + + if len(v.Pre) > 0 { + b = append(b, '-') + b = append(b, v.Pre[0].String()...) + + for _, pre := range v.Pre[1:] { + b = append(b, '.') + b = append(b, pre.String()...) + } + } + + if len(v.Build) > 0 { + b = append(b, '+') + b = append(b, v.Build[0]...) + + for _, build := range v.Build[1:] { + b = append(b, '.') + b = append(b, build...) + } + } + + return string(b) +} + +// Equals checks if v is equal to o. +func (v Version) Equals(o Version) bool { + return (v.Compare(o) == 0) +} + +// EQ checks if v is equal to o. +func (v Version) EQ(o Version) bool { + return (v.Compare(o) == 0) +} + +// NE checks if v is not equal to o. +func (v Version) NE(o Version) bool { + return (v.Compare(o) != 0) +} + +// GT checks if v is greater than o. +func (v Version) GT(o Version) bool { + return (v.Compare(o) == 1) +} + +// GTE checks if v is greater than or equal to o. +func (v Version) GTE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// GE checks if v is greater than or equal to o. +func (v Version) GE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// LT checks if v is less than o. +func (v Version) LT(o Version) bool { + return (v.Compare(o) == -1) +} + +// LTE checks if v is less than or equal to o. +func (v Version) LTE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// LE checks if v is less than or equal to o. +func (v Version) LE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// Compare compares Versions v to o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v Version) Compare(o Version) int { + if v.Major != o.Major { + if v.Major > o.Major { + return 1 + } + return -1 + } + if v.Minor != o.Minor { + if v.Minor > o.Minor { + return 1 + } + return -1 + } + if v.Patch != o.Patch { + if v.Patch > o.Patch { + return 1 + } + return -1 + } + + // Quick comparison if a version has no prerelease versions + if len(v.Pre) == 0 && len(o.Pre) == 0 { + return 0 + } else if len(v.Pre) == 0 && len(o.Pre) > 0 { + return 1 + } else if len(v.Pre) > 0 && len(o.Pre) == 0 { + return -1 + } + + i := 0 + for ; i < len(v.Pre) && i < len(o.Pre); i++ { + if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { + continue + } else if comp == 1 { + return 1 + } else { + return -1 + } + } + + // If all pr versions are the equal but one has further prversion, this one greater + if i == len(v.Pre) && i == len(o.Pre) { + return 0 + } else if i == len(v.Pre) && i < len(o.Pre) { + return -1 + } else { + return 1 + } + +} + +// Validate validates v and returns error in case +func (v Version) Validate() error { + // Major, Minor, Patch already validated using uint64 + + for _, pre := range v.Pre { + if !pre.IsNum { //Numeric prerelease versions already uint64 + if len(pre.VersionStr) == 0 { + return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) + } + if !containsOnly(pre.VersionStr, alphanum) { + return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) + } + } + } + + for _, build := range v.Build { + if len(build) == 0 { + return fmt.Errorf("Build meta data can not be empty %q", build) + } + if !containsOnly(build, alphanum) { + return fmt.Errorf("Invalid character(s) found in build meta data %q", build) + } + } + + return nil +} + +// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error +func New(s string) (vp *Version, err error) { + v, err := Parse(s) + vp = &v + return +} + +// Make is an alias for Parse, parses version string and returns a validated Version or error +func Make(s string) (Version, error) { + return Parse(s) +} + +// ParseTolerant allows for certain version specifications that do not strictly adhere to semver +// specs to be parsed by this library. It does so by normalizing versions before passing them to +// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions +// with only major and minor components specified +func ParseTolerant(s string) (Version, error) { + s = strings.TrimSpace(s) + s = strings.TrimPrefix(s, "v") + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) < 3 { + if strings.ContainsAny(parts[len(parts)-1], "+-") { + return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") + } + for len(parts) < 3 { + parts = append(parts, "0") + } + s = strings.Join(parts, ".") + } + + return Parse(s) +} + +// Parse parses version string and returns a validated Version or error +func Parse(s string) (Version, error) { + if len(s) == 0 { + return Version{}, errors.New("Version string empty") + } + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) != 3 { + return Version{}, errors.New("No Major.Minor.Patch elements found") + } + + // Major + if !containsOnly(parts[0], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) + } + if hasLeadingZeroes(parts[0]) { + return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) + } + major, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return Version{}, err + } + + // Minor + if !containsOnly(parts[1], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) + } + if hasLeadingZeroes(parts[1]) { + return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) + } + minor, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return Version{}, err + } + + v := Version{} + v.Major = major + v.Minor = minor + + var build, prerelease []string + patchStr := parts[2] + + if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { + build = strings.Split(patchStr[buildIndex+1:], ".") + patchStr = patchStr[:buildIndex] + } + + if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { + prerelease = strings.Split(patchStr[preIndex+1:], ".") + patchStr = patchStr[:preIndex] + } + + if !containsOnly(patchStr, numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) + } + if hasLeadingZeroes(patchStr) { + return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) + } + patch, err := strconv.ParseUint(patchStr, 10, 64) + if err != nil { + return Version{}, err + } + + v.Patch = patch + + // Prerelease + for _, prstr := range prerelease { + parsedPR, err := NewPRVersion(prstr) + if err != nil { + return Version{}, err + } + v.Pre = append(v.Pre, parsedPR) + } + + // Build meta data + for _, str := range build { + if len(str) == 0 { + return Version{}, errors.New("Build meta data is empty") + } + if !containsOnly(str, alphanum) { + return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) + } + v.Build = append(v.Build, str) + } + + return v, nil +} + +// MustParse is like Parse but panics if the version cannot be parsed. +func MustParse(s string) Version { + v, err := Parse(s) + if err != nil { + panic(`semver: Parse(` + s + `): ` + err.Error()) + } + return v +} + +// PRVersion represents a PreRelease Version +type PRVersion struct { + VersionStr string + VersionNum uint64 + IsNum bool +} + +// NewPRVersion creates a new valid prerelease version +func NewPRVersion(s string) (PRVersion, error) { + if len(s) == 0 { + return PRVersion{}, errors.New("Prerelease is empty") + } + v := PRVersion{} + if containsOnly(s, numbers) { + if hasLeadingZeroes(s) { + return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) + } + num, err := strconv.ParseUint(s, 10, 64) + + // Might never be hit, but just in case + if err != nil { + return PRVersion{}, err + } + v.VersionNum = num + v.IsNum = true + } else if containsOnly(s, alphanum) { + v.VersionStr = s + v.IsNum = false + } else { + return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) + } + return v, nil +} + +// IsNumeric checks if prerelease-version is numeric +func (v PRVersion) IsNumeric() bool { + return v.IsNum +} + +// Compare compares two PreRelease Versions v and o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v PRVersion) Compare(o PRVersion) int { + if v.IsNum && !o.IsNum { + return -1 + } else if !v.IsNum && o.IsNum { + return 1 + } else if v.IsNum && o.IsNum { + if v.VersionNum == o.VersionNum { + return 0 + } else if v.VersionNum > o.VersionNum { + return 1 + } else { + return -1 + } + } else { // both are Alphas + if v.VersionStr == o.VersionStr { + return 0 + } else if v.VersionStr > o.VersionStr { + return 1 + } else { + return -1 + } + } +} + +// PreRelease version to string +func (v PRVersion) String() string { + if v.IsNum { + return strconv.FormatUint(v.VersionNum, 10) + } + return v.VersionStr +} + +func containsOnly(s string, set string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(set, r) + }) == -1 +} + +func hasLeadingZeroes(s string) bool { + return len(s) > 1 && s[0] == '0' +} + +// NewBuildVersion creates a new valid build version +func NewBuildVersion(s string) (string, error) { + if len(s) == 0 { + return "", errors.New("Buildversion is empty") + } + if !containsOnly(s, alphanum) { + return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) + } + return s, nil +} diff --git a/vendor/github.com/blang/semver/sort.go b/vendor/github.com/blang/semver/sort.go new file mode 100644 index 00000000000..e18f880826a --- /dev/null +++ b/vendor/github.com/blang/semver/sort.go @@ -0,0 +1,28 @@ +package semver + +import ( + "sort" +) + +// Versions represents multiple versions. +type Versions []Version + +// Len returns length of version collection +func (s Versions) Len() int { + return len(s) +} + +// Swap swaps two versions inside the collection by its indices +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less checks if version at index i is less than version at index j +func (s Versions) Less(i, j int) bool { + return s[i].LT(s[j]) +} + +// Sort sorts a slice of versions +func Sort(versions []Version) { + sort.Sort(Versions(versions)) +} diff --git a/vendor/github.com/blang/semver/sql.go b/vendor/github.com/blang/semver/sql.go new file mode 100644 index 00000000000..eb4d802666e --- /dev/null +++ b/vendor/github.com/blang/semver/sql.go @@ -0,0 +1,30 @@ +package semver + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements the database/sql.Scanner interface. +func (v *Version) Scan(src interface{}) (err error) { + var str string + switch src := src.(type) { + case string: + str = src + case []byte: + str = string(src) + default: + return fmt.Errorf("Version.Scan: cannot convert %T to string.", src) + } + + if t, err := Parse(str); err == nil { + *v = t + } + + return +} + +// Value implements the database/sql/driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +} diff --git a/vendor/github.com/elastic/go-ucfg/.editorconfig b/vendor/github.com/elastic/go-ucfg/.editorconfig new file mode 100644 index 00000000000..a92dc2185bd --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/.editorconfig @@ -0,0 +1,27 @@ +# See: http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.json] +indent_size = 4 +indent_style = space + +[*.py] +indent_style = space +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[Vagrantfile] +indent_size = 2 +indent_style = space diff --git a/vendor/github.com/elastic/go-ucfg/.gitignore b/vendor/github.com/elastic/go-ucfg/.gitignore new file mode 100644 index 00000000000..485dee64bcf --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/github.com/elastic/go-ucfg/.travis.yml b/vendor/github.com/elastic/go-ucfg/.travis.yml new file mode 100644 index 00000000000..bff3d3c08ed --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - '1.10' + - '1.11' + - '1.12' + - '1.13' + - 'tip' + +script: + - go test -race -coverprofile=coverage.txt -covermode=atomic ./... + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/elastic/go-ucfg/CHANGELOG.md b/vendor/github.com/elastic/go-ucfg/CHANGELOG.md new file mode 100644 index 00000000000..9c3af53784e --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/CHANGELOG.md @@ -0,0 +1,304 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed +- Fixed panic on zero Value while processing a collection of interfaces. #159 + +## [0.8.3] + +### Added +- Added ability to adjust merging behavior based on field names in configuration. Using `ucfg.FieldMergeValues`, `ucfg.FieldReplaceValues`, `ucfg.FieldAppendValues`, and `ucfg.FieldPrependValues`. #151 + +## [0.8.2] + +### Fixed +- Fixed nonzero validator to not fail on nil array or slice. #147 +- Fixed nonzero validator to validate maps. +- Fixed required validator to validate maps. + +## [0.8.1] + +### Fixed +- Prevent Validate from being called when value is a pointer or interface and is nil. #144 + +## [0.8.0] + +### Added +- Add support for HJSON. #131 +- Add new parse.Config to adjust parsing of varibles returned by a Resolve. #139 +- Add call to InitDefaults when map, primitives, or structs implement Initializer interface during Unpack. #104 + +### Changed +- Moved internal/parse to parse module. #139 +- Add parse.Config to resolvers return. #139 + +### Fixed +- Call Validate on custom slice types. #133 +- Call Validate on custom map types. #136 +- Disabled object parsing of environment variables. #139 +- Apply validation to defaults passed into Unpack when Config doesn't contain a value. #42 + +## [0.7.0] + +### Added +- Add (*Config).Has. #127 +- Add (*Config).Remove. #126 + +### Removed +- Remove CI and support for go versions <1.10. #128 + +## [0.6.5] + +### Added +- Added a NOOP Resolver that will return the key wrapped in the field reference syntax. #122 + +## [0.6.4] + +### Fixed +- Do not treat $ as escape char in plain strings/regexes #120 + +## [0.6.3] + +### Changed +- Remove UUID lib and use pseudo-random IDs instead. #118 + +## [0.6.2] + +### Changed +- New UUID lib: github.com/gofrs/uuid. #116 + +### Fixed +- Fix escape character not removed from escaped string #115 + +## [0.6.1] + +### Fixed +- Ignore flag keys with missing values. #111 + +## [0.6.0] + +### Added +- Add *Config merging options merge, append, prepend, replace. #107 + +### Fixed +- Fix: do not treat ucfg.Config (or castable type) as Unpacker. #106 + +## [0.5.1] + +### Fixed +- Fix: an issue with the Cyclic reference algorithm when a direct reference was pointing + to another reference. #100 + +## [0.5.0] + +### Added +- Detect cyclic reference and allow to search top level key with the other resolvers. #97 +- Allow to diff keys of two different configuration #93 + +## [0.4.6] + +### Added +- Introduce ,ignore struct tag option to optionally ignore exported fields. #89 +- Add support for custom Unpacker method with `*Config` being convertible to first parameter. The custom method must be compatible to `ConfigUnpacker`. #90 + +### Fixed +- Ignore private struct fields when merging a struct into a config. #89 + +## [0.4.5] + +### Changed +- merging sub-configs enforces strict variable expansion #85 + +### Fixed +- fix merging nil sub-configs #85 + +## [0.4.4] + +### Added +- Add support for pure array config files #82 + +### Changed +- Invalid top-level types return non-critical error (no stack-trace) on merge #82 + +### Fixed +- Fix panic when merging or creating a config from nil interface value #82 + +## [0.4.3] + +### Changed +- Add per element type stop set for handling unquoted strings (reduces need for quoting strings in environment variables) #80 + +### Fixed +- fix issue unpacking array from environment variable into struct array fields #80 +- fix unparsed values being used for unpacking #80 + +## [0.4.2] + +### Fixed +- Treat `,` character as only special character in non quoted top-level strings. #78 + +## [0.4.1] + +### Fixed +- Fix parsing empty string or nil objects from environment variables. #76 + +## [0.4.0] + +### Added +- Syntax for passing lists and dictionaries to flags. #72 +- Add Unpacker interface specializations for primitive types. #73 +- Variable expansion parsing lists and dictionaries with parser introduced in + #72. #74 + +### Fixed +- Fix Unpacker interface not applied if some 'old' value is already present on + target and is struct implementing Unpack. #73 + +## [0.3.7] + +### Fixed +- Fix int/uint to float type conversation. #68 +- Fix primitive type unpacking for variables expanded from environment variables + or strings read/created by config file parsers. #67 + +## [0.3.6] + +### Fixed +- Fix duplicate key error when normalizing tables. #63 + +## [0.3.5] + +### Fixed +- Fix merging array values. #59 +- Fix initializing empty array values. #58 + +## [0.3.4] + +### Fixed +- Fix error message if Unpack returns error. #56 + +## [0.3.3] + +### Fixed +- Fix `(*FlagValue).String` panic with go 1.7 #54 + +## [0.3.2] + +### Changed +- Turn '$' into universal escape character, so '}' in default values can be escaped with '$'. #52 + +### Fixed +- Fix parsing ':' in expansion default value. #51, #52 + +## [0.3.1] + +### Added +- Add `(*Config).IsArray` and `(*Config).IsDict`. #44 + +### Fixed +- Fix (*Config).CountField returning 1 for arrays of any size. #43 +- Fix unpacking into slice/array top-level or if `inline`-tag is used. #45 + +## [0.3.0] + +### Added +- Added CLI flag support. #15 +- Added variable expansion support. #14 + +### Changed +- Report error message from regexp.Compile if compilation fails #21 + +### Fixed +- Nil values become merge-able with concrete types. #26 +- Fix merging types `time.Duration` and `*regexp.Regexp`. #25 +- Fix Validate-method not being run for structs. #32 +- Fix field validation errors on structs fields does not contain missing or failed configuration variable. #31 + +## [0.2.1] + +### Changed +- Report error message from regexp.Compile if compilation fails #21 + +### Fixed +- Handle empty slices, strings, regular expression by nonzero,required validation tags #20, #23 + +## [0.2.0] + +### Added +- Support for validation via Validator interface. #16 +- Added direct support for uint values. #8, #16 +- Support for simple validators via struct tags (e.g. min, max, nonzero, required). #16 +- Add support for validating time.Duration. #9, #16 +- Added Unpacker interface for customer unpackers. #17 +- Support for numeric indices for accessing/writing array elements. #12 #19 + +### Changed +- Set/Get methods require index of -1 if value is not supposed to be in an array. #19 +- Configurations can be arrays and/or objects at the same time. #19 +- Access elements with empty path and index in array based Configuration nodes. #19 + +### Fixed +- Check for integer overflow when unpacking into int/uint. #8, #16 + +## [0.1.1] + +### Fixed +- Fixed unpacking *regexp.Regexp +- Fixed unpacking empty config as *Config object + +## [0.1.0] + +### Added +- add support for unpacking *regexp.Regexp via regexp.Compile +- Parse time.Duration from int/float values in seconds +- Improve error messages +- Add options and PathSep support to low level option setters/getters +- Added support for _rebranding_ `*ucfg.Config` via `type MyConfig ucfg.Config` using + casts between pointer types in Unpack and Merge. +- Introduced CHANGELOG.md for documenting changes to ucfg. + + +[Unreleased]: https://github.com/elastic/go-ucfg/compare/v0.8.3...HEAD +[0.8.3]: https://github.com/elastic/go-ucfg/compare/v0.8.2...v0.8.3 +[0.8.2]: https://github.com/elastic/go-ucfg/compare/v0.8.1...v0.8.2 +[0.8.1]: https://github.com/elastic/go-ucfg/compare/v0.8.0...v0.8.1 +[0.8.0]: https://github.com/elastic/go-ucfg/compare/v0.7.0...v0.8.0 +[0.7.0]: https://github.com/elastic/go-ucfg/compare/v0.6.5...v0.7.0 +[0.6.5]: https://github.com/elastic/go-ucfg/compare/v0.6.4...v0.6.5 +[0.6.4]: https://github.com/elastic/go-ucfg/compare/v0.6.3...v0.6.4 +[0.6.3]: https://github.com/elastic/go-ucfg/compare/v0.6.2...v0.6.3 +[0.6.2]: https://github.com/elastic/go-ucfg/compare/v0.6.1...v0.6.2 +[0.6.1]: https://github.com/elastic/go-ucfg/compare/v0.6.0...v0.6.1 +[0.6.0]: https://github.com/elastic/go-ucfg/compare/v0.5.1...v0.6.0 +[0.5.1]: https://github.com/elastic/go-ucfg/compare/v0.5.0...v0.5.1 +[0.5.0]: https://github.com/elastic/go-ucfg/compare/v0.4.6...v0.5.0 +[0.4.6]: https://github.com/elastic/go-ucfg/compare/v0.4.5...v0.4.6 +[0.4.5]: https://github.com/elastic/go-ucfg/compare/v0.4.4...v0.4.5 +[0.4.4]: https://github.com/elastic/go-ucfg/compare/v0.4.3...v0.4.4 +[0.4.3]: https://github.com/elastic/go-ucfg/compare/v0.4.2...v0.4.3 +[0.4.2]: https://github.com/elastic/go-ucfg/compare/v0.4.1...v0.4.2 +[0.4.1]: https://github.com/elastic/go-ucfg/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/elastic/go-ucfg/compare/v0.3.7...v0.4.0 +[0.3.7]: https://github.com/elastic/go-ucfg/compare/v0.3.6...v0.3.7 +[0.3.6]: https://github.com/elastic/go-ucfg/compare/v0.3.5...v0.3.6 +[0.3.5]: https://github.com/elastic/go-ucfg/compare/v0.3.4...v0.3.5 +[0.3.4]: https://github.com/elastic/go-ucfg/compare/v0.3.3...v0.3.4 +[0.3.3]: https://github.com/elastic/go-ucfg/compare/v0.3.2...v0.3.3 +[0.3.2]: https://github.com/elastic/go-ucfg/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/elastic/go-ucfg/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/elastic/go-ucfg/compare/v0.2.1...v0.3.0 +[0.2.1]: https://github.com/elastic/go-ucfg/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/elastic/go-ucfg/compare/v0.1.1...v0.2.0 +[0.1.1]: https://github.com/elastic/go-ucfg/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/elastic/go-ucfg/compare/v0.0.0...v0.1.0 diff --git a/vendor/github.com/elastic/go-ucfg/LICENSE b/vendor/github.com/elastic/go-ucfg/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/elastic/go-ucfg/README.md b/vendor/github.com/elastic/go-ucfg/README.md new file mode 100644 index 00000000000..1042f426339 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/README.md @@ -0,0 +1,93 @@ +[![Build +Status](https://travis-ci.org/elastic/go-ucfg.svg?branch=master)](https://travis-ci.org/elastic/go-ucfg) +[![Go Report +Card](https://goreportcard.com/badge/github.com/elastic/go-ucfg)](https://goreportcard.com/report/github.com/elastic/go-ucfg) +[![codecov](https://codecov.io/gh/elastic/go-ucfg/branch/master/graph/badge.svg)](https://codecov.io/gh/elastic/go-ucfg) + + +# ucfg - Universal Configuration + +`ucfg` is a Golang library to handle hjson, json, and yaml configuration files in your Golang project. It was developed for the [libbeat framework](https://github.com/elastic/beats/tree/master/libbeat) and used by all [beats](https://github.com/elastic/beats). + + +## API Documentation + +The full API Documentation can be found [here](https://godoc.org/github.com/elastic/go-ucfg). + +## Examples + +A few examples on how ucfg can be used. All examples below assume, that the following packages are imported: + +```golang +import ( + "github.com/elastic/go-ucfg" + "github.com/elastic/go-ucfg/yaml" +) +``` + +### Dot notations + +ufcg allows you to load yaml configuration files using dots instead of indentation. For example instead of having: + +```yaml +config: + user: name +``` + +with ucfg you can write: + +```yaml +config.user: name +``` + +This makes configurations easier and simpler. + +To load such a config file in Golang, use the following command: + +```golang +config, err := yaml.NewConfigWithFile(path, ucfg.PathSep(".")) +``` + + + +### Validation and Defaults + +ucfg allows to automatically validate fields and set defaults for fields in case they are not defined. + + +```golang +// Defines struct to read config from +type ExampleConfig struct { + Counter int `config:"counter" validate:"min=0, max=9"` +} + +// Defines default config option +var ( + defaultConfig = ExampleConfig{ + Counter: 4, + } +) + +func main() { + appConfig := defaultConfig // copy default config so it's not overwritten + config, err := yaml.NewConfigWithFile(path, ucfg.PathSep(".")) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = config.Unpack(&appConfig) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} +``` + +The above uses `Counter` as the config variable. ucfg assures that the value is between 0 and 9 and will return an error if this is not the case. In addition, if the value is not set, it will default to 4. + + +## Requirements + +ucfg has the following requirements: + +* Golang 1.10+ diff --git a/vendor/github.com/elastic/go-ucfg/doc.go b/vendor/github.com/elastic/go-ucfg/doc.go new file mode 100644 index 00000000000..7d549876bef --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/doc.go @@ -0,0 +1,27 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Package ucfg provides a common representation for hierarchical configurations. +// +// The common representation provided by the Config type can be used with different +// configuration file formats like XML, JSON, HSJSON, YAML, or TOML. +// +// Config provides a low level and a high level interface for reading settings +// with additional features like custom unpackers, validation and capturing +// sub-configurations for deferred interpretation, lazy intra-configuration +// variable expansion, and OS environment variable expansion. +package ucfg diff --git a/vendor/github.com/elastic/go-ucfg/error.go b/vendor/github.com/elastic/go-ucfg/error.go new file mode 100644 index 00000000000..d944bac66d9 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/error.go @@ -0,0 +1,349 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "errors" + "fmt" + "reflect" + "runtime/debug" +) + +// Error type returned by all public functions in go-ucfg. +type Error interface { + error + + // error class, one of ErrConfig, ErrImplementation, ErrUnknown + Class() error + + // The internal reason error code like ErrMissing, ErrRequired, + // ErrTypeMismatch and others. + Reason() error + + // The error message. + Message() string + + // [optional] path of config element error occurred for + Path() string + + // [optional] stack trace + Trace() string +} + +type baseError struct { + reason error + class error + message string + path string +} + +type criticalError struct { + baseError + trace string +} + +// Error Reasons +var ( + ErrMissing = errors.New("missing field") + + ErrNoParse = errors.New("parsing dynamic configs is disabled") + + ErrCyclicReference = errors.New("cyclic reference detected") + + ErrDuplicateValidator = errors.New("validator already registered") + + ErrTypeNoArray = errors.New("field is no array") + + ErrTypeMismatch = errors.New("type mismatch") + + ErrKeyTypeNotString = errors.New("key must be a string") + + ErrIndexOutOfRange = errors.New("out of range index") + + ErrPointerRequired = errors.New("pointer required for unpacking configurations") + + ErrArraySizeMistach = errors.New("Array size mismatch") + + ErrExpectedObject = errors.New("expected object") + + ErrNilConfig = errors.New("config is nil") + + ErrNilValue = errors.New("nil value is invalid") + + ErrTODO = errors.New("TODO - implement me") + + ErrDuplicateKey = errors.New("duplicate key") + + ErrOverflow = errors.New("integer overflow") + + ErrNegative = errors.New("negative value") + + ErrZeroValue = errors.New("zero value") + + ErrRequired = errors.New("missing required field") + + ErrEmpty = errors.New("empty field") + + ErrArrayEmpty = errors.New("empty array") + + ErrMapEmpty = errors.New("empty map") + + ErrRegexEmpty = errors.New("regex value is not set") + + ErrStringEmpty = errors.New("string value is not set") +) + +// Error Classes +var ( + ErrConfig = errors.New("Configuration error") + ErrImplementation = errors.New("Implementation error") + ErrUnknown = errors.New("Unspecified") +) + +func (e baseError) Error() string { return e.Message() } +func (e baseError) Reason() error { return e.reason } +func (e baseError) Class() error { return e.class } +func (e baseError) Trace() string { return "" } +func (e baseError) Path() string { return e.path } + +func (e baseError) Message() string { + if e.message == "" { + return e.reason.Error() + } + return e.message +} + +func (e criticalError) Trace() string { return e.trace } + +func (e criticalError) Error() string { + return fmt.Sprintf("%s\nTrace:%v\n", e.baseError.Message(), e.trace) +} + +func raiseErr(reason error, message string) Error { + return baseError{ + reason: reason, + message: message, + class: ErrConfig, + } +} + +func raiseImplErr(reason error) Error { + return baseError{ + reason: reason, + class: ErrImplementation, + } +} + +func raiseCritical(reason error, message string) Error { + if message == "" { + message = reason.Error() + } + if message != "" { + message = fmt.Sprintf("(assert) %v", message) + } + return criticalError{ + baseError{reason, ErrImplementation, message, ""}, + string(debug.Stack()), + } +} + +func raisePathErr(reason error, meta *Meta, message, path string) Error { + message = messagePath(reason, meta, message, path) + return baseError{reason, ErrConfig, message, path} +} + +func messageMeta(message string, meta *Meta) string { + if meta == nil || meta.Source == "" { + return message + } + return fmt.Sprintf("%v (source:'%v')", message, meta.Source) +} + +func messagePath(reason error, meta *Meta, message, path string) string { + if path == "" { + path = "config" + } else { + path = fmt.Sprintf("'%v'", path) + } + + if message == "" { + message = reason.Error() + } + + message = fmt.Sprintf("%v accessing %v", message, path) + return messageMeta(message, meta) +} + +func raiseDuplicateKey(cfg *Config, name string) Error { + return raisePathErr(ErrDuplicateKey, cfg.metadata, "", cfg.PathOf(name, ".")) +} + +func raiseCyclicErr(field string) Error { + message := fmt.Sprintf("cyclic reference detected for key: '%s'", field) + + return baseError{ + reason: ErrCyclicReference, + class: ErrConfig, + message: message, + } +} + +func raiseMissing(c *Config, field string) Error { + // error reading field from config, as missing in c + return raiseMissingMsg(c, field, "") +} + +func raiseMissingMsg(c *Config, field string, message string) Error { + return raisePathErr(ErrMissing, c.metadata, message, c.PathOf(field, ".")) +} + +func raiseMissingArr(ctx context, meta *Meta, idx int) Error { + message := fmt.Sprintf("no value in array at %v", idx) + return raisePathErr(ErrMissing, meta, message, ctx.path(".")) +} + +func raiseIndexOutOfBounds(opts *options, value value, idx int) Error { + reason := ErrIndexOutOfRange + ctx := value.Context() + len, _ := value.Len(opts) + message := fmt.Sprintf("index '%v' out of range (length=%v)", idx, len) + return raisePathErr(reason, value.meta(), message, ctx.path(".")) +} + +func raiseInvalidTopLevelType(v interface{}, meta *Meta) Error { + // could be developers or user fault + t := chaseTypePointers(chaseValue(reflect.ValueOf(v)).Type()) + message := fmt.Sprintf("type '%v' is not supported on top level of config, only dictionary or list", t) + return raiseErr(ErrTypeMismatch, messageMeta(message, meta)) +} + +func raiseKeyInvalidTypeUnpack(t reflect.Type, from *Config) Error { + // most likely developers fault + ctx := from.ctx + reason := ErrKeyTypeNotString + message := fmt.Sprintf("string key required when unpacking into '%v'", t) + return raiseCritical(reason, messagePath(reason, from.metadata, message, ctx.path("."))) +} + +func raiseKeyInvalidTypeMerge(cfg *Config, t reflect.Type) Error { + ctx := cfg.ctx + reason := ErrKeyTypeNotString + message := fmt.Sprintf("string key required when merging into '%v'", t) + return raiseCritical(reason, messagePath(reason, cfg.metadata, message, ctx.path("."))) +} + +func raiseSquashNeedsObject(cfg *Config, opts *options, f string, t reflect.Type) Error { + reason := ErrTypeMismatch + message := fmt.Sprintf("require map or struct when squash merging '%v' (%v)", f, t) + + return raiseCritical(reason, messagePath(reason, opts.meta, message, cfg.Path("."))) +} + +func raiseInlineNeedsObject(cfg *Config, f string, t reflect.Type) Error { + reason := ErrTypeMismatch + message := fmt.Sprintf("require map or struct when inling '%v' (%v)", f, t) + return raiseCritical(reason, + messagePath(reason, cfg.metadata, message, cfg.Path("."))) +} + +func raiseUnsupportedInputType(ctx context, meta *Meta, v reflect.Value) Error { + reason := ErrTypeMismatch + message := fmt.Sprintf("unspported input type (%v) with value '%#v'", + v.Type(), v) + + return raiseCritical(reason, messagePath(reason, meta, message, ctx.path("."))) +} + +func raiseNoParse(ctx context, meta *Meta) Error { + reason := ErrNoParse + return raisePathErr(reason, meta, "", ctx.path(".")) +} + +func raiseNil(reason error) Error { + // programmers error (passed unexpected nil pointer) + return raiseCritical(reason, "") +} + +func raisePointerRequired(v reflect.Value) Error { + // developer did not pass pointer, unpack target is not settable + return raiseCritical(ErrPointerRequired, "") +} + +func raiseToTypeNotSupported(opts *options, v value, goT reflect.Type) Error { + reason := ErrTypeMismatch + t, _ := v.typ(opts) + message := fmt.Sprintf("value of type '%v' not convertible into unsupported go type '%v'", + t.name, goT) + ctx := v.Context() + + return raiseCritical(reason, messagePath(reason, v.meta(), message, ctx.path("."))) +} + +func raiseArraySize(ctx context, meta *Meta, n int, to int) Error { + reason := ErrArraySizeMistach + message := fmt.Sprintf("array of length %v does not meet required length %v", + n, to) + + return raisePathErr(reason, meta, message, ctx.path(".")) +} + +func raiseConversion(opts *options, v value, err error, to string) Error { + ctx := v.Context() + path := ctx.path(".") + t, _ := v.typ(opts) + message := fmt.Sprintf("can not convert '%v' into '%v'", t.name, to) + return raisePathErr(err, v.meta(), message, path) +} + +func raiseExpectedObject(opts *options, v value) Error { + ctx := v.Context() + path := ctx.path(".") + t, _ := v.typ(opts) + message := fmt.Sprintf("required 'object', but found '%v' in field '%v'", + t.name, path) + + return raiseErr(ErrExpectedObject, messageMeta(message, v.meta())) +} + +func raiseInvalidDuration(v value, err error) Error { + ctx := v.Context() + path := ctx.path(".") + return raisePathErr(err, v.meta(), "", path) +} + +func raiseValidation(ctx context, meta *Meta, field string, err error) Error { + path := "" + if field == "" { + path = ctx.path(".") + } else { + path = ctx.pathOf(field, ".") + } + return raiseErr(err, messagePath(err, meta, err.Error(), path)) +} + +func raiseInvalidRegexp(v value, err error) Error { + ctx := v.Context() + path := ctx.path(".") + message := fmt.Sprintf("Failed to compile regular expression with '%v'", err) + return raisePathErr(err, v.meta(), message, path) +} + +func raiseParseSplice(ctx context, meta *Meta, err error) Error { + message := fmt.Sprintf("%v parsing splice", err) + return raisePathErr(err, meta, message, ctx.path(".")) +} diff --git a/vendor/github.com/elastic/go-ucfg/errpred.go b/vendor/github.com/elastic/go-ucfg/errpred.go new file mode 100644 index 00000000000..e849e6c9fd1 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/errpred.go @@ -0,0 +1,41 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +func isCyclicError(err error) bool { + switch v := err.(type) { + case Error: + return v.Reason() == ErrCyclicReference + } + return false +} + +func isMissingError(err error) bool { + switch v := err.(type) { + case Error: + return v.Reason() == ErrMissing + } + return false +} + +func criticalResolveError(err error) bool { + if err == nil { + return false + } + return !(isCyclicError(err) || isMissingError(err)) +} diff --git a/vendor/github.com/elastic/go-ucfg/fieldset.go b/vendor/github.com/elastic/go-ucfg/fieldset.go new file mode 100644 index 00000000000..e797f18417d --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/fieldset.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +type fieldSet struct { + fields map[string]struct{} + parent *fieldSet +} + +func newFieldSet(parent *fieldSet) *fieldSet { + return &fieldSet{ + fields: map[string]struct{}{}, + parent: parent, + } +} + +func (s *fieldSet) Has(name string) (exists bool) { + if _, exists = s.fields[name]; !exists && s.parent != nil { + exists = s.parent.Has(name) + } + return +} + +func (s *fieldSet) Add(name string) { + s.fields[name] = struct{}{} +} + +func (s *fieldSet) AddNew(name string) (ok bool) { + if ok = !s.Has(name); ok { + s.Add(name) + } + return +} + +func (s *fieldSet) Names() []string { + var names []string + for k := range s.fields { + names = append(names, k) + } + + if s.parent != nil { + names = append(names, s.parent.Names()...) + } + return names +} diff --git a/vendor/github.com/elastic/go-ucfg/getset.go b/vendor/github.com/elastic/go-ucfg/getset.go new file mode 100644 index 00000000000..a0a71f99bf9 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/getset.go @@ -0,0 +1,281 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +// ****************************************************************************** +// Low level getters and setters +// ****************************************************************************** + +func convertErr(opts *options, v value, err error, to string) Error { + if err == nil { + return nil + } + return raiseConversion(opts, v, err, to) +} + +// CountField returns number of entries in a table or 1 if entry is a primitive value. +// Primitives settings can be handled like a list with 1 entry. +// +// If name is empty, the total number of top-level settings is returned. +// +// CountField supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) CountField(name string, opts ...Option) (int, error) { + if name == "" { + return len(c.fields.array()) + len(c.fields.dict()), nil + } + + if v, ok := c.fields.get(name); ok { + return v.Len(makeOptions(opts)) + } + return -1, raiseMissing(c, name) +} + +// Bool reads a boolean setting returning an error if the setting has no +// boolean value. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Bool supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) Bool(name string, idx int, opts ...Option) (bool, error) { + O := makeOptions(opts) + v, err := c.getField(name, idx, O) + if err != nil { + return false, err + } + b, fail := v.toBool(O) + return b, convertErr(O, v, fail, "bool") +} + +// Strings reads a string setting returning an error if the setting has +// no string or primitive value convertible to string. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// String supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) String(name string, idx int, opts ...Option) (string, error) { + O := makeOptions(opts) + v, err := c.getField(name, idx, O) + if err != nil { + return "", err + } + s, fail := v.toString(O) + return s, convertErr(O, v, fail, "string") +} + +// Int reads an int64 value returning an error if the setting is +// not integer value, the primitive value is not convertible to int or a conversion +// would create an integer overflow. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Int supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) Int(name string, idx int, opts ...Option) (int64, error) { + O := makeOptions(opts) + v, err := c.getField(name, idx, O) + if err != nil { + return 0, err + } + + i, fail := v.toInt(O) + return i, convertErr(O, v, fail, "int") +} + +// Uint reads an uint64 value returning an error if the setting is +// not unsigned value, the primitive value is not convertible to uint64 or a conversion +// would create an integer overflow. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Uint supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) Uint(name string, idx int, opts ...Option) (uint64, error) { + O := makeOptions(opts) + v, err := c.getField(name, idx, O) + if err != nil { + return 0, err + } + u, fail := v.toUint(O) + return u, convertErr(O, v, fail, "uint") +} + +// Float reads a float64 value returning an error if the setting is +// not a float value or the primitive value is not convertible to float. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Float supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) Float(name string, idx int, opts ...Option) (float64, error) { + O := makeOptions(opts) + v, err := c.getField(name, idx, O) + if err != nil { + return 0, err + } + f, fail := v.toFloat(O) + return f, convertErr(O, v, fail, "float") +} + +// Child returns a child configuration or an error if the setting requested is a +// primitive value only. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// Child supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) Child(name string, idx int, opts ...Option) (*Config, error) { + O := makeOptions(opts) + v, err := c.getField(name, idx, O) + if err != nil { + return nil, err + } + c, fail := v.toConfig(O) + return c, convertErr(O, v, fail, "object") +} + +// SetBool sets a boolean primitive value. An error is returned if the new name +// is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetBool supports the options: PathSep, MetaData +func (c *Config) SetBool(name string, idx int, value bool, opts ...Option) error { + return c.setField(name, idx, &cfgBool{b: value}, opts) +} + +// SetInt sets an integer primitive value. An error is returned if the new +// name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetInt supports the options: PathSep, MetaData +func (c *Config) SetInt(name string, idx int, value int64, opts ...Option) error { + return c.setField(name, idx, &cfgInt{i: value}, opts) +} + +// SetUint sets an unsigned integer primitive value. An error is returned if +// the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetUint supports the options: PathSep, MetaData +func (c *Config) SetUint(name string, idx int, value uint64, opts ...Option) error { + return c.setField(name, idx, &cfgUint{u: value}, opts) +} + +// SetFloat sets an floating point primitive value. An error is returned if +// the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetFloat supports the options: PathSep, MetaData +func (c *Config) SetFloat(name string, idx int, value float64, opts ...Option) error { + return c.setField(name, idx, &cfgFloat{f: value}, opts) +} + +// SetString sets string value. An error is returned if the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetString supports the options: PathSep, MetaData +func (c *Config) SetString(name string, idx int, value string, opts ...Option) error { + return c.setField(name, idx, &cfgString{s: value}, opts) +} + +// SetChild adds a sub-configuration. An error is returned if the name is invalid. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. The number of entries in a named list can be read +// using CountField. +// +// SetChild supports the options: PathSep, MetaData +func (c *Config) SetChild(name string, idx int, value *Config, opts ...Option) error { + return c.setField(name, idx, cfgSub{c: value}, opts) +} + +// getField supports the options: PathSep, Env, Resolve, ResolveEnv +func (c *Config) getField(name string, idx int, opts *options) (value, Error) { + p := parsePathIdx(name, opts.pathSep, idx) + v, err := p.GetValue(c, opts) + if err != nil { + return v, err + } + + if v == nil { + return nil, raiseMissing(c, p.String()) + } + return v, nil +} + +// setField supports the options: PathSep, MetaData +func (c *Config) setField(name string, idx int, v value, options []Option) Error { + opts := makeOptions(options) + p := parsePathIdx(name, opts.pathSep, idx) + + err := p.SetValue(c, opts, v) + if err != nil { + return err + } + + if opts.meta != nil { + v.setMeta(opts.meta) + } + return nil +} diff --git a/vendor/github.com/elastic/go-ucfg/initializer.go b/vendor/github.com/elastic/go-ucfg/initializer.go new file mode 100644 index 00000000000..3614f3fd90c --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/initializer.go @@ -0,0 +1,59 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "reflect" +) + +// Initializer interface provides initialization of default values support to Unpack. +// The InitDefaults method will be executed for any type passed directly or indirectly to +// Unpack. +type Initializer interface { + InitDefaults() +} + +func tryInitDefaults(val reflect.Value) reflect.Value { + t := val.Type() + + var initializer Initializer + if t.Implements(iInitializer) { + initializer = val.Interface().(Initializer) + initializer.InitDefaults() + return val + } else if reflect.PtrTo(t).Implements(iInitializer) { + tmp := pointerize(reflect.PtrTo(t), t, val) + initializer = tmp.Interface().(Initializer) + initializer.InitDefaults() + + // Return the element in the pointer so the value is set into the + // field and not a pointer to the value. + return tmp.Elem() + } + return val +} + +func hasInitDefaults(t reflect.Type) bool { + if t.Implements(iInitializer) { + return true + } + if reflect.PtrTo(t).Implements(iInitializer) { + return true + } + return false +} diff --git a/vendor/github.com/elastic/go-ucfg/merge.go b/vendor/github.com/elastic/go-ucfg/merge.go new file mode 100644 index 00000000000..ff07a97c8f7 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/merge.go @@ -0,0 +1,585 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "fmt" + "reflect" + "regexp" + "time" + "unicode" + "unicode/utf8" +) + +// Merge a map, a slice, a struct or another Config object into c. +// +// Merge traverses the value from recursively copying all values into a hierarchy +// of Config objects plus primitives into c. +// +// Merge supports the options: PathSep, MetaData, StructTag, VarExp, ReplaceValues, AppendValues, PrependValues +// +// Merge uses the type-dependent default encodings: +// - Boolean values are encoded as booleans. +// - Integer are encoded as int64 values, unsigned integer values as uint64 and +// floats as float64 values. +// - Strings are copied into string values. +// If the VarExp is set, string fields will be parsed into +// variable expansion expressions. The expression can reference any +// other setting by absolute name. +// - Array and slices are copied into new Config objects with index accessors only. +// - Struct values and maps with key type string are encoded as Config objects with +// named field accessors. +// - Config objects will be copied and added to the current hierarchy. +// +// The `config` struct tag (configurable via StructTag option) can be used to +// set the field name and enable additional merging settings per field: +// +// // field appears in Config as key "myName" +// Field int `config:"myName"` +// +// // field appears in sub-Config "mySub" as key "myName" (requires PathSep(".")) +// Field int `config:"mySub.myName"` +// +// // field is processed as if keys are part of outer struct (type can be a +// // struct, a slice, an array, a map or of type *Config) +// Field map[string]interface{} `config:",inline"` +// +// // field is ignored by Merge +// Field string `config:",ignore"` +// +// +// Returns an error if merging fails to normalize and validate the from value. +// If duplicate setting names are detected in the input, merging fails as well. +// +// Config cannot represent cyclic structures and Merge does not handle them +// well. Passing cyclic structures to Merge will result in an infinite recursive +// loop. +func (c *Config) Merge(from interface{}, options ...Option) error { + // from is empty in case of empty config file + if from == nil { + return nil + } + + opts := makeOptions(options) + other, err := normalize(opts, from) + + if err != nil { + return err + } + return mergeConfig(opts, c, other) +} + +func mergeConfig(opts *options, to, from *Config) Error { + if err := mergeConfigDict(opts, to, from); err != nil { + return err + } + return mergeConfigArr(opts, to, from) +} + +func mergeConfigDict(opts *options, to, from *Config) Error { + dict := from.fields.dict() + if len(dict) == 0 { + return nil + } + + ok := false + if opts.configValueHandling == cfgReplaceValue { + old := to.fields.dict() + to.fields.d = nil + defer func() { + if !ok { + to.fields.d = old + } + }() + } + + for k, v := range dict { + ctx := context{ + parent: cfgSub{to}, + field: k, + } + + old, _ := to.fields.get(k) + opts, err := fieldOptsOverride(opts, k, -1) + if err != nil { + return err + } + merged, err := mergeValues(opts, old, v) + if err != nil { + return err + } + + to.fields.set(k, merged.cpy(ctx)) + } + + ok = true + return nil +} + +func mergeConfigArr(opts *options, to, from *Config) Error { + currHandling := opts.configValueHandling + opts, err := fieldOptsOverride(opts, "*", -1) + if err != nil { + return err + } + switch currHandling { + case cfgReplaceValue: + return mergeConfigReplaceArr(opts, to, from) + + case cfgArrPrepend: + return mergeConfigPrependArr(opts, to, from) + + case cfgArrAppend: + return mergeConfigAppendArr(opts, to, from) + + case cfgDefaultHandling, cfgMergeValues: + return mergeConfigMergeArr(opts, to, from) + default: + return mergeConfigMergeArr(opts, to, from) + } +} + +func mergeConfigReplaceArr(opts *options, to, from *Config) Error { + a := from.fields.array() + if len(a) == 0 { + return nil + } + + var parent value = cfgSub{to} + var fields = fields{ + d: to.fields.d, + a: make([]value, 0, len(a)), + } + fields.append(parent, a) + *to.fields = fields + return nil +} + +func mergeConfigMergeArr(opts *options, to, from *Config) Error { + l := len(to.fields.array()) + arr := from.fields.array() + if l > len(arr) { + l = len(arr) + } + + var parent value = cfgSub{to} + + // merge array indexes available in to and from + for i := 0; i < l; i++ { + ctx := context{ + parent: parent, + field: fmt.Sprintf("%v", i), + } + + // possible for individual index to be replaced + idxOpts, err := fieldOptsOverride(opts, "", i) + if err != nil { + return err + } + old := to.fields.array()[i] + merged, err := mergeValues(idxOpts, old, arr[i]) + if err != nil { + return err + } + to.fields.setAt(i, parent, merged.cpy(ctx)) + } + + if len(arr) > l { + // add additional array entries not yet in 'to' + to.fields.append(parent, arr[l:]) + } + return nil +} + +func mergeConfigPrependArr(opts *options, to, from *Config) Error { + a1 := to.fields.array() + a2 := from.fields.array() + if len(a2) == 0 { + return nil + } + + var parent value = cfgSub{to} + var fields = fields{ + d: to.fields.d, + a: make([]value, 0, len(a1)+len(a2)), + } + fields.append(parent, a2) + fields.append(parent, a1) + *to.fields = fields + return nil +} + +func mergeConfigAppendArr(opts *options, to, from *Config) Error { + to.fields.append(cfgSub{to}, from.fields.array()) + return nil +} + +func mergeValues(opts *options, old, v value) (value, Error) { + if old == nil { + return v, nil + } + + // check if new and old value evaluate to sub-configurations. If one is no + // sub-configuration, use new value only. + subOld, err := old.toConfig(opts) + if err != nil { + return v, nil + } + subV, err := v.toConfig(opts) + if err != nil { + return v, nil + } + + // merge new and old evaluated sub-configurations and return subOld for + // reassigning to old key in case of subOld being generated dynamically + if err := mergeConfig(opts, subOld, subV); err != nil { + return nil, err + } + return cfgSub{subOld}, nil +} + +// convert from into normalized *Config checking for errors +// before merging generated(normalized) config with current config +func normalize(opts *options, from interface{}) (*Config, Error) { + vFrom := chaseValue(reflect.ValueOf(from)) + + switch vFrom.Type() { + case tConfig: + return vFrom.Addr().Interface().(*Config), nil + case tConfigMap: + return normalizeMap(opts, vFrom) + default: + // try to convert vFrom into Config (rebranding) + if v, ok := tryTConfig(vFrom); ok { + return v.Addr().Interface().(*Config), nil + } + + // normalize given map/struct value + switch vFrom.Kind() { + case reflect.Struct: + return normalizeStruct(opts, vFrom) + case reflect.Map: + return normalizeMap(opts, vFrom) + case reflect.Array, reflect.Slice: + tmp, err := normalizeArray(opts, tagOptions{}, context{}, vFrom) + if err != nil { + return nil, err + } + c, _ := tmp.toConfig(opts) + return c, nil + } + + } + + return nil, raiseInvalidTopLevelType(from, opts.meta) +} + +func normalizeMap(opts *options, from reflect.Value) (*Config, Error) { + cfg := New() + cfg.metadata = opts.meta + if err := normalizeMapInto(cfg, opts, from); err != nil { + return nil, err + } + return cfg, nil +} + +func normalizeMapInto(cfg *Config, opts *options, from reflect.Value) Error { + k := from.Type().Key().Kind() + if k != reflect.String && k != reflect.Interface { + return raiseKeyInvalidTypeMerge(cfg, from.Type()) + } + + for _, k := range from.MapKeys() { + k = chaseValueInterfaces(k) + if k.Kind() != reflect.String { + return raiseKeyInvalidTypeMerge(cfg, from.Type()) + } + + err := normalizeSetField(cfg, opts, noTagOpts, k.String(), from.MapIndex(k)) + if err != nil { + return err + } + } + return nil +} + +func normalizeStruct(opts *options, from reflect.Value) (*Config, Error) { + cfg := New() + cfg.metadata = opts.meta + if err := normalizeStructInto(cfg, opts, from); err != nil { + return nil, err + } + return cfg, nil +} + +func normalizeStructInto(cfg *Config, opts *options, from reflect.Value) Error { + v := chaseValue(from) + numField := v.NumField() + + for i := 0; i < numField; i++ { + var err Error + stField := v.Type().Field(i) + + // ignore non exported fields + if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) { + continue + } + + name, tagOpts := parseTags(stField.Tag.Get(opts.tag)) + if tagOpts.ignore { + continue + } + + if tagOpts.squash { + vField := chaseValue(v.Field(i)) + switch vField.Kind() { + case reflect.Struct: + err = normalizeStructInto(cfg, opts, vField) + case reflect.Map: + err = normalizeMapInto(cfg, opts, vField) + default: + return raiseSquashNeedsObject(cfg, opts, stField.Name, vField.Type()) + } + } else { + name = fieldName(name, stField.Name) + err = normalizeSetField(cfg, opts, tagOpts, name, v.Field(i)) + } + + if err != nil { + return err + } + } + return nil +} + +func normalizeSetField( + cfg *Config, + opts *options, + tagOpts tagOptions, + name string, + v reflect.Value, +) Error { + val, err := normalizeValue(opts, tagOpts, context{}, v) + if err != nil { + return err + } + + p := parsePath(name, opts.pathSep) + old, err := p.GetValue(cfg, opts) + if err != nil { + if err.Reason() != ErrMissing { + return err + } + old = nil + } + + switch { + case !isNil(old) && isNil(val): + return nil + case isNil(old): + return p.SetValue(cfg, opts, val) + case isSub(old) && isSub(val): + cfgOld, _ := old.toConfig(opts) + cfgVal, _ := val.toConfig(opts) + return mergeConfig(opts, cfgOld, cfgVal) + default: + return raiseDuplicateKey(cfg, name) + } +} + +func normalizeStructValue(opts *options, ctx context, from reflect.Value) (value, Error) { + sub, err := normalizeStruct(opts, from) + if err != nil { + return nil, err + } + v := cfgSub{sub} + v.SetContext(ctx) + return v, nil +} + +func normalizeMapValue(opts *options, ctx context, from reflect.Value) (value, Error) { + sub, err := normalizeMap(opts, from) + if err != nil { + return nil, err + } + v := cfgSub{sub} + v.SetContext(ctx) + return v, nil +} + +func normalizeArray( + opts *options, + tagOpts tagOptions, + ctx context, + v reflect.Value, +) (value, Error) { + l := v.Len() + out := make([]value, 0, l) + + cfg := New() + cfg.metadata = opts.meta + cfg.ctx = ctx + val := cfgSub{cfg} + + for i := 0; i < l; i++ { + idx := fmt.Sprintf("%v", i) + ctx := context{ + parent: val, + field: idx, + } + tmp, err := normalizeValue(opts, tagOpts, ctx, v.Index(i)) + if err != nil { + return nil, err + } + out = append(out, tmp) + } + + cfg.fields.a = out + return val, nil +} + +func normalizeValue( + opts *options, + tagOpts tagOptions, + ctx context, + v reflect.Value, +) (value, Error) { + v = chaseValue(v) + + switch v.Type() { + case tDuration: + d := v.Interface().(time.Duration) + return newString(ctx, opts.meta, d.String()), nil + case tRegexp: + r := v.Addr().Interface().(*regexp.Regexp) + return newString(ctx, opts.meta, r.String()), nil + } + + // handle primitives + switch v.Kind() { + case reflect.Bool: + return newBool(ctx, opts.meta, v.Bool()), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i := v.Int() + if i > 0 { + return newUint(ctx, opts.meta, uint64(i)), nil + } + return newInt(ctx, opts.meta, i), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return newUint(ctx, opts.meta, v.Uint()), nil + case reflect.Float32, reflect.Float64: + f := v.Float() + return newFloat(ctx, opts.meta, f), nil + case reflect.String: + return normalizeString(ctx, opts, v.String()) + case reflect.Array, reflect.Slice: + return normalizeArray(opts, tagOpts, ctx, v) + case reflect.Map: + return normalizeMapValue(opts, ctx, v) + case reflect.Struct: + if v, ok := tryTConfig(v); ok { + c := v.Addr().Interface().(*Config) + ret := cfgSub{c} + if ret.Context().parent != ctx.parent { + ret.SetContext(ctx) + } + return ret, nil + } + + return normalizeStructValue(opts, ctx, v) + default: + if v.IsNil() { + return &cfgNil{cfgPrimitive{ctx, opts.meta}}, nil + } + return nil, raiseUnsupportedInputType(ctx, opts.meta, v) + } +} + +func normalizeString(ctx context, opts *options, str string) (value, Error) { + if !opts.varexp { + return newString(ctx, opts.meta, str), nil + } + + varexp, err := parseSplice(str, opts.pathSep) + if err != nil { + return nil, raiseParseSplice(ctx, opts.meta, err) + } + + switch p := varexp.(type) { + case constExp: + return newString(ctx, opts.meta, string(p)), nil + case *reference: + return newRef(ctx, opts.meta, p), nil + } + + return newSplice(ctx, opts.meta, varexp), nil +} + +func fieldOptsOverride(opts *options, fieldName string, idx int) (*options, Error) { + if opts.fieldHandlingTree == nil { + return opts, nil + } + cfgHandling, child, ok := opts.fieldHandlingTree.fieldHandling(fieldName, idx) + child, err := includeWildcard(child, opts.fieldHandlingTree) + if err != nil { + return nil, err + } + if !ok { + // Only return a new `options` when arriving at new nested child. This + // combined with optimizations in `includeWildcard` will ensure that only + // a new opts will be created and returned when absolutely required. + if child != nil && opts.fieldHandlingTree != child { + newOpts := *opts + newOpts.fieldHandlingTree = child + opts = &newOpts + } + return opts, nil + } + // Only return a new `options` if absolutely required. + if opts.configValueHandling != cfgHandling || opts.fieldHandlingTree != child { + newOpts := *opts + newOpts.configValueHandling = cfgHandling + newOpts.fieldHandlingTree = child + opts = &newOpts + } + return opts, nil +} + +func includeWildcard(child *fieldHandlingTree, parent *fieldHandlingTree) (*fieldHandlingTree, Error) { + if parent == nil { + return child, nil + } + wildcard, err := parent.wildcard() + if err != nil { + return child, nil + } + if child == nil && len(parent.fields.dict()) == 1 { + // parent is already config with just wildcard + return parent, nil + } + sub := newFieldHandlingTree() + if child != nil { + if err := sub.merge(child); err != nil { + return nil, err.(Error) + } + } + if err := sub.setWildcard(wildcard); err != nil { + return nil, err.(Error) + } + return sub, nil +} diff --git a/vendor/github.com/elastic/go-ucfg/opts.go b/vendor/github.com/elastic/go-ucfg/opts.go new file mode 100644 index 00000000000..68c23e562c9 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/opts.go @@ -0,0 +1,315 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "fmt" + "os" + "strings" + + "github.com/elastic/go-ucfg/parse" +) + +// Option type implementing additional options to be passed +// to go-ucfg library functions. +type Option func(*options) + +type options struct { + tag string + validatorTag string + pathSep string + meta *Meta + env []*Config + resolvers []func(name string) (string, parse.Config, error) + varexp bool + noParse bool + + configValueHandling configHandling + fieldHandlingTree *fieldHandlingTree + + // temporary cache of parsed splice values for lifetime of call to + // Unpack/Pack/Get/... + parsed valueCache + + activeFields *fieldSet +} + +type valueCache map[string]spliceValue + +// specific API on top of Config to handle adjusting merging behavior per fields +type fieldHandlingTree Config + +// id used to store intermediate parse results in current execution context. +// As parsing results might differ between multiple calls due to: +// splice being shared between multiple configurations, or environment +// changing between calls + lazy nature of cfgSplice, parsing results cannot +// be stored in cfgSplice itself. +type cacheID string + +type spliceValue struct { + err error + value value +} + +// StructTag option sets the struct tag name to use for looking up +// field names and options in `Unpack` and `Merge`. +// The default struct tag in `config`. +func StructTag(tag string) Option { + return func(o *options) { + o.tag = tag + } +} + +// ValidatorTag option sets the struct tag name used to set validators +// on struct fields in `Unpack`. +// The default struct tag in `validate`. +func ValidatorTag(tag string) Option { + return func(o *options) { + o.validatorTag = tag + } +} + +// PathSep sets the path separator used to split up names into a tree like hierarchy. +// If PathSep is not set, field names will not be split. +func PathSep(sep string) Option { + return func(o *options) { + o.pathSep = sep + } +} + +// MetaData option passes additional metadata (currently only source of the +// configuration) to be stored internally (e.g. for error reporting). +func MetaData(meta Meta) Option { + return func(o *options) { + o.meta = &meta + } +} + +// Env option adds another configuration for variable expansion to be used, if +// the path to look up does not exist in the actual configuration. Env can be used +// multiple times in order to add more lookup environments. +func Env(e *Config) Option { + return func(o *options) { + o.env = append(o.env, e) + } +} + +// Resolve option adds a callback used by variable name expansion. The callback +// will be called if a variable can not be resolved from within the actual configuration +// or any of its environments. +func Resolve(fn func(name string) (string, parse.Config, error)) Option { + return func(o *options) { + o.resolvers = append(o.resolvers, fn) + } +} + +// ResolveEnv option adds a look up callback looking up values in the available +// OS environment variables. +var ResolveEnv Option = doResolveEnv + +func doResolveEnv(o *options) { + o.resolvers = append(o.resolvers, func(name string) (string, parse.Config, error) { + value := os.Getenv(name) + if value == "" { + return "", parse.EnvConfig, ErrMissing + } + return value, parse.EnvConfig, nil + }) +} + +// ResolveNOOP option add a resolver that will not search the value but instead will return the +// provided key wrap with the field reference syntax. This is useful if you don't to expose values +// from envionment variable or other resolvers. +// +// Example: "mysecret" => ${mysecret}" +var ResolveNOOP Option = doResolveNOOP + +func doResolveNOOP(o *options) { + o.resolvers = append(o.resolvers, func(name string) (string, parse.Config, error) { + return "${" + name + "}", parse.NoopConfig, nil + }) +} + +var ( + // ReplaceValues option configures all merging and unpacking operations to + // replace old dictionaries and arrays while merging. Value merging can be + // overwritten in unpack by using struct tags. + ReplaceValues = makeOptValueHandling(cfgReplaceValue) + + // AppendValues option configures all merging and unpacking operations to + // merge dictionaries and append arrays to existing arrays while merging. + // Value merging can be overwritten in unpack by using struct tags. + AppendValues = makeOptValueHandling(cfgArrAppend) + + // PrependValues option configures all merging and unpacking operations to + // merge dictionaries and prepend arrays to existing arrays while merging. + // Value merging can be overwritten in unpack by using struct tags. + PrependValues = makeOptValueHandling(cfgArrPrepend) +) + +func makeOptValueHandling(h configHandling) Option { + return func(o *options) { + o.configValueHandling = h + } +} + +var ( + // FieldMergeValues option configures all merging and unpacking operations to use + // the default merging behavior for the specified field. This overrides the any struct + // tags during unpack for the field. Nested field names can be defined using dot + // notation. + FieldMergeValues = makeFieldOptValueHandling(cfgMergeValues) + + // FieldReplaceValues option configures all merging and unpacking operations to + // replace old dictionaries and arrays while merging for the specified field. This + // overrides the any struct tags during unpack for the field. Nested field names + // can be defined using dot notation. + FieldReplaceValues = makeFieldOptValueHandling(cfgReplaceValue) + + // FieldAppendValues option configures all merging and unpacking operations to + // merge dictionaries and append arrays to existing arrays while merging for the + // specified field. This overrides the any struct tags during unpack for the field. + // Nested field names can be defined using dot notation. + FieldAppendValues = makeFieldOptValueHandling(cfgArrAppend) + + // FieldPrependValues option configures all merging and unpacking operations to + // merge dictionaries and prepend arrays to existing arrays while merging for the + // specified field. This overrides the any struct tags during unpack for the field. + // Nested field names can be defined using dot notation. + FieldPrependValues = makeFieldOptValueHandling(cfgArrPrepend) +) + +func makeFieldOptValueHandling(h configHandling) func(...string) Option { + return func(fieldName ...string) Option { + if len(fieldName) == 0 { + return func(_ *options) {} + } + + table := make(map[string]configHandling) + for _, name := range fieldName { + // field value config options are rendered into a Config; the '*' represents the handling method + // for everything nested under this field. + if !strings.HasSuffix(name, ".*") { + name = fmt.Sprintf("%s.*", name) + } + table[name] = h + } + + return func(o *options) { + if o.fieldHandlingTree == nil { + o.fieldHandlingTree = newFieldHandlingTree() + } + o.fieldHandlingTree.merge(table, PathSep(o.pathSep)) + } + } +} + +// VarExp option enables support for variable expansion. Resolve and Env options will only be effective if VarExp is set. +var VarExp Option = doVarExp + +func doVarExp(o *options) { o.varexp = true } + +func makeOptions(opts []Option) *options { + o := options{ + tag: "config", + validatorTag: "validate", + pathSep: "", // no separator by default + parsed: map[string]spliceValue{}, + activeFields: newFieldSet(nil), + } + for _, opt := range opts { + opt(&o) + } + return &o +} + +func (cache valueCache) cachedValue( + id cacheID, + f func() (value, error), +) (value, error) { + if v, ok := cache[string(id)]; ok { + if v.err != nil { + return nil, v.err + } + return v.value, nil + } + + v, err := f() + + // Only primitives can be cached, allowing us to get out of infinite loop + if v != nil && v.canCache() { + cache[string(id)] = spliceValue{err, v} + } + return v, err +} + +func newFieldHandlingTree() *fieldHandlingTree { + return (*fieldHandlingTree)(New()) +} + +func (t *fieldHandlingTree) merge(other interface{}, opts ...Option) error { + cfg := (*Config)(t) + return cfg.Merge(other, opts...) +} + +func (t *fieldHandlingTree) child(fieldName string, idx int) (*fieldHandlingTree, error) { + cfg := (*Config)(t) + child, err := cfg.Child(fieldName, idx) + if err != nil { + return nil, err + } + return (*fieldHandlingTree)(child), nil +} + +func (t *fieldHandlingTree) configHandling(fieldName string, idx int) (configHandling, error) { + cfg := (*Config)(t) + handling, err := cfg.Uint(fieldName, idx) + if err != nil { + return cfgDefaultHandling, err + } + return configHandling(handling), nil +} + +func (t *fieldHandlingTree) wildcard() (*fieldHandlingTree, error) { + return t.child("**", -1) +} + +func (t *fieldHandlingTree) setWildcard(wildcard *fieldHandlingTree) error { + cfg := (*Config)(t) + return cfg.SetChild("**", -1, (*Config)(wildcard)) +} + +func (t *fieldHandlingTree) fieldHandling(fieldName string, idx int) (configHandling, *fieldHandlingTree, bool) { + child, err := t.child(fieldName, idx) + if err == nil { + cfgHandling, err := child.configHandling("*", -1) + if err == nil { + return cfgHandling, child, true + } + } + // try wildcard match + wildcard, err := t.wildcard() + if err != nil { + return cfgDefaultHandling, child, false + } + cfgHandling, cfg, ok := wildcard.fieldHandling(fieldName, idx) + if ok { + return cfgHandling, cfg, ok + } + return cfgDefaultHandling, child, ok +} diff --git a/vendor/github.com/elastic/go-ucfg/parse/parse.go b/vendor/github.com/elastic/go-ucfg/parse/parse.go new file mode 100644 index 00000000000..5fafefd9a30 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/parse/parse.go @@ -0,0 +1,395 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package parse + +import ( + "errors" + "fmt" + "strconv" + "strings" + "unicode" +) + +// Config allows enabling and disabling parser features. +type Config struct { + Array bool + Object bool + StringDQuote bool + StringSQuote bool +} + +// DefaultConfig is the default config with all parser features enabled. +var DefaultConfig = Config{ + Array: true, + Object: true, + StringDQuote: true, + StringSQuote: true, +} + +// EnvConfig is configuration for parser when the value comes from environmental variable. +var EnvConfig = Config{ + Array: true, + Object: false, + StringDQuote: true, + StringSQuote: true, +} + +// NoopConfig is configuration for parser that disables all options. +var NoopConfig = Config{ + Array: false, + Object: false, + StringDQuote: false, + StringSQuote: false, +} + +type flagParser struct { + input string + cfg Config +} + +// stopSet definitions for handling unquoted strings +const ( + toplevelStopSet = "," + arrayElemStopSet = ",]" + objKeyStopSet = ":" + objValueStopSet = ",}" +) + +// Value parses command line arguments, supporting +// boolean, numbers, strings, arrays, objects. +// +// The parser implements a superset of JSON, but only a subset of YAML by +// allowing for arrays and objects having a trailing comma. In addition 3 +// string types are supported: +// +// 1. single quoted string (no unescaping of any characters) +// 2. double quoted strings (characters are escaped) +// 3. strings without quotes. String parsing stops at +// special characters like '[]{},:' +// +// In addition, top-level values can be separated by ',' to build arrays +// without having to use []. +func Value(content string) (interface{}, error) { + return ValueWithConfig(content, DefaultConfig) +} + +// ValueWithConfig parses command line arguments, supporting +// boolean, numbers, strings, arrays, objects when enabled. +// +// The parser implements a superset of JSON, but only a subset of YAML by +// allowing for arrays and objects having a trailing comma. In addition 3 +// string types are supported: +// +// 1. single quoted string (no unescaping of any characters) +// 2. double quoted strings (characters are escaped) +// 3. strings without quotes. String parsing stops at +// special characters like '[]{},:' +// +// In addition, top-level values can be separated by ',' to build arrays +// without having to use []. +func ValueWithConfig(content string, cfg Config) (interface{}, error) { + p := &flagParser{strings.TrimSpace(content), cfg} + if err := p.validateConfig(); err != nil { + return nil, err + } + v, err := p.parse() + if err != nil { + return nil, fmt.Errorf("%v when parsing '%v'", err.Error(), content) + } + return v, nil +} + +func (p *flagParser) validateConfig() error { + if !p.cfg.Array && p.cfg.Object { + return fmt.Errorf("cfg.Array cannot be disabled when cfg.Object is enabled") + } + return nil +} + +func (p *flagParser) parse() (interface{}, error) { + var values []interface{} + + for { + v, err := p.parseValue(toplevelStopSet) + if err != nil { + return nil, err + } + values = append(values, v) + + p.ignoreWhitespace() + if p.input == "" { + break + } + + if err := p.expectChar(','); err != nil { + return nil, err + } + } + + switch len(values) { + case 0: + return nil, nil + case 1: + return values[0], nil + } + return values, nil +} + +func (p *flagParser) parseValue(stopSet string) (interface{}, error) { + p.ignoreWhitespace() + in := p.input + + if in == "" { + return nil, nil + } + + switch in[0] { + case '[': + if p.cfg.Array { + return p.parseArray() + } + return p.parsePrimitive(stopSet) + case '{': + if p.cfg.Object { + return p.parseObj() + } + return p.parsePrimitive(stopSet) + case '"': + if p.cfg.StringDQuote { + return p.parseStringDQuote() + } + return p.parsePrimitive(stopSet) + case '\'': + if p.cfg.StringSQuote { + return p.parseStringSQuote() + } + return p.parsePrimitive(stopSet) + default: + return p.parsePrimitive(stopSet) + } +} + +func (p *flagParser) ignoreWhitespace() { + p.input = strings.TrimLeftFunc(p.input, unicode.IsSpace) +} + +func (p *flagParser) parseArray() (interface{}, error) { + p.input = p.input[1:] + + var values []interface{} +loop: + for { + p.ignoreWhitespace() + if p.input[0] == ']' { + p.input = p.input[1:] + break + } + + v, err := p.parseValue(arrayElemStopSet) + if err != nil { + return nil, err + } + values = append(values, v) + + p.ignoreWhitespace() + if p.input == "" { + return nil, errors.New("array closing ']' missing") + } + + next := p.input[0] + p.input = p.input[1:] + + switch next { + case ']': + break loop + case ',': + continue + default: + return nil, errors.New("array expected ',' or ']'") + } + + } + + if len(values) == 0 { + return nil, nil + } + + return values, nil +} + +func (p *flagParser) parseObj() (interface{}, error) { + p.input = p.input[1:] + + O := map[string]interface{}{} + +loop: + for { + p.ignoreWhitespace() + if p.input[0] == '}' { + p.input = p.input[1:] + break + } + + k, err := p.parseKey() + if err != nil { + return nil, err + } + + p.ignoreWhitespace() + if err := p.expectChar(':'); err != nil { + return nil, err + } + + v, err := p.parseValue(objValueStopSet) + if err != nil { + return nil, err + } + + if p.input == "" { + return nil, errors.New("dictionary expected ',' or '}'") + } + + O[k] = v + next := p.input[0] + p.input = p.input[1:] + + switch next { + case '}': + break loop + case ',': + continue + default: + return nil, errors.New("dictionary expected ',' or '}'") + } + } + + // empty object + if len(O) == 0 { + return nil, nil + } + + return O, nil +} + +func (p *flagParser) parseKey() (string, error) { + in := p.input + if in == "" { + return "", errors.New("expected key") + } + + switch in[0] { + case '"': + return p.parseStringDQuote() + case '\'': + return p.parseStringSQuote() + default: + return p.parseNonQuotedString(objKeyStopSet) + } +} + +func (p *flagParser) parseStringDQuote() (string, error) { + in := p.input + off := 1 + var i int + for { + i = strings.IndexByte(in[off:], '"') + if i < 0 { + return "", errors.New("Missing \" to close string ") + } + + i += off + if in[i-1] != '\\' { + break + } + off = i + 1 + } + + p.input = in[i+1:] + return strconv.Unquote(in[:i+1]) +} + +func (p *flagParser) parseStringSQuote() (string, error) { + in := p.input + i := strings.IndexByte(in[1:], '\'') + if i < 0 { + return "", errors.New("missing ' to close string") + } + + p.input = in[i+2:] + return in[1 : 1+i], nil +} + +func (p *flagParser) parseNonQuotedString(stopSet string) (string, error) { + in := p.input + idx := strings.IndexAny(in, stopSet) + if idx == 0 { + return "", fmt.Errorf("unexpected '%v'", string(in[idx])) + } + + content, in := in, "" + if idx > 0 { + content, in = content[:idx], content[idx:] + } + p.input = in + + return strings.TrimSpace(content), nil +} + +func (p *flagParser) parsePrimitive(stopSet string) (interface{}, error) { + content, err := p.parseNonQuotedString(stopSet) + if err != nil { + return nil, err + } + + if content == "null" { + return nil, nil + } + if b, ok := parseBoolValue(content); ok { + return b, nil + } + if n, err := strconv.ParseUint(content, 0, 64); err == nil { + return n, nil + } + if n, err := strconv.ParseInt(content, 0, 64); err == nil { + return n, nil + } + if n, err := strconv.ParseFloat(content, 64); err == nil { + return n, nil + } + + return content, nil +} + +func (p *flagParser) expectChar(c byte) error { + if p.input == "" || p.input[0] != c { + return fmt.Errorf("expected '%v'", string(c)) + } + + p.input = p.input[1:] + return nil +} + +func parseBoolValue(str string) (value bool, ok bool) { + switch str { + case "t", "T", "true", "TRUE", "True", "on", "ON": + return true, true + case "f", "F", "false", "FALSE", "False", "off", "OFF": + return false, true + } + return false, false +} diff --git a/vendor/github.com/elastic/go-ucfg/path.go b/vendor/github.com/elastic/go-ucfg/path.go new file mode 100644 index 00000000000..ff7e6301423 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/path.go @@ -0,0 +1,309 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "fmt" + "strconv" + "strings" +) + +type cfgPath struct { + fields []field + sep string +} + +type field interface { + String() string + SetValue(opt *options, elem value, v value) Error + GetValue(opt *options, elem value) (value, Error) + Remove(opt *options, elem value) (bool, Error) +} + +type namedField struct { + name string +} + +type idxField struct { + i int +} + +func parsePathIdx(in, sep string, idx int) cfgPath { + if in == "" { + return cfgPath{ + sep: sep, + fields: []field{idxField{idx}}, + } + } + + p := parsePath(in, sep) + if idx >= 0 { + p.fields = append(p.fields, idxField{idx}) + } + + return p +} + +func parsePath(in, sep string) cfgPath { + if sep == "" { + return cfgPath{ + sep: sep, + fields: []field{parseField(in)}, + } + } + + elems := strings.Split(in, sep) + fields := make([]field, 0, len(elems)) + for _, elem := range elems { + fields = append(fields, parseField(elem)) + } + return cfgPath{fields: fields, sep: sep} +} + +func parseField(in string) field { + if idx, err := strconv.ParseInt(in, 0, 64); err == nil { + return idxField{int(idx)} + } + return namedField{in} +} + +func (p cfgPath) String() string { + if len(p.fields) == 0 { + return "" + } + + if len(p.fields) == 1 { + return p.fields[0].String() + } + + s := make([]string, 0, len(p.fields)) + for _, f := range p.fields { + s = append(s, f.String()) + } + + sep := p.sep + if sep == "" { + sep = "." + } + return strings.Join(s, sep) +} + +func (n namedField) String() string { + return n.name +} + +func (i idxField) String() string { + return fmt.Sprintf("%d", i.i) +} + +func (p cfgPath) Has(cfg *Config, opt *options) (bool, Error) { + fields := p.fields + + cur := value(cfgSub{cfg}) + for ; len(fields) > 0; fields = fields[1:] { + field := fields[0] + next, err := field.GetValue(opt, cur) + if err != nil { + // has checks if a value is missing -> ErrMissing is no error but a valid + // outcome + if err.Reason() == ErrMissing { + err = nil + } + return false, err + } + + if next == nil { + return false, nil + } + + cur = next + } + + return true, nil +} + +func (p cfgPath) GetValue(cfg *Config, opt *options) (value, Error) { + fields := p.fields + + cur := value(cfgSub{cfg}) + for ; len(fields) > 1; fields = fields[1:] { + field := fields[0] + next, err := field.GetValue(opt, cur) + if err != nil { + return nil, err + } + + if next == nil { + return nil, raiseMissing(cfg, field.String()) + } + + cur = next + } + + field := fields[0] + v, err := field.GetValue(opt, cur) + if err != nil { + return nil, raiseMissing(cfg, field.String()) + } + return v, nil +} + +func (n namedField) GetValue(opts *options, elem value) (value, Error) { + cfg, err := elem.toConfig(opts) + if err != nil { + return nil, raiseExpectedObject(opts, elem) + } + + v, _ := cfg.fields.get(n.name) + return v, nil +} + +func (i idxField) GetValue(opts *options, elem value) (value, Error) { + cfg, err := elem.toConfig(opts) + if err != nil { + if i.i == 0 { + return elem, nil + } + + return nil, raiseExpectedObject(opts, elem) + } + + arr := cfg.fields.array() + if i.i >= len(arr) { + return nil, raiseMissing(cfg, i.String()) + } + return arr[i.i], nil +} + +func (p cfgPath) SetValue(cfg *Config, opt *options, val value) Error { + fields := p.fields + node := value(cfgSub{cfg}) + + // 1. iterate until intermediate node not having some required child node + for ; len(fields) > 1; fields = fields[1:] { + field := fields[0] + v, err := field.GetValue(opt, node) + if err != nil { + if err.Reason() == ErrMissing { + break + } + return err + } + + if isNil(v) { + break + } + node = v + } + + // 2. build intermediate nodes from bottom up + + for ; len(fields) > 1; fields = fields[:len(fields)-1] { + field := fields[len(fields)-1] + + next := New() + next.metadata = val.meta() + v := cfgSub{next} + if err := field.SetValue(opt, v, val); err != nil { + return err + } + val = v + } + + // 3. insert new sub-tree into config + return fields[0].SetValue(opt, node, val) +} + +func (n namedField) SetValue(opts *options, elem value, v value) Error { + sub, ok := elem.(cfgSub) + if !ok { + return raiseExpectedObject(opts, elem) + } + + sub.c.fields.set(n.name, v) + v.SetContext(context{parent: elem, field: n.name}) + return nil +} + +func (i idxField) SetValue(opts *options, elem value, v value) Error { + sub, ok := elem.(cfgSub) + if !ok { + return raiseExpectedObject(opts, elem) + } + + sub.c.fields.setAt(i.i, elem, v) + v.SetContext(context{parent: elem, field: i.String()}) + return nil +} + +func (p cfgPath) Remove(cfg *Config, opt *options) (bool, error) { + fields := p.fields + + // Loop over intermediate objects. Returns an error if any intermediate is + // actually no object. + cur := value(cfgSub{cfg}) + for ; len(fields) > 1; fields = fields[1:] { + field := fields[0] + next, err := field.GetValue(opt, cur) + if err != nil { + // Ignore ErrMissing when walking down a config tree. If intermediary is + // missing we can't remove our setting. + if err.Reason() == ErrMissing { + err = nil + } + + return false, err + } + + if next == nil { + return false, err + } + + cur = next + } + + // resolve config object in case we deal with references + tmp, err := cur.toConfig(opt) + if err != nil { + return false, err + } + cur = cfgSub{tmp} + + field := fields[0] + return field.Remove(opt, cur) +} + +func (n namedField) Remove(opts *options, elem value) (bool, Error) { + sub, ok := elem.(cfgSub) + if !ok { + return false, raiseExpectedObject(opts, elem) + } + + removed := sub.c.fields.del(n.name) + return removed, nil +} + +func (i idxField) Remove(opts *options, elem value) (bool, Error) { + sub, ok := elem.(cfgSub) + if !ok { + return false, raiseExpectedObject(opts, elem) + } + + removed := sub.c.fields.delAt(i.i) + return removed, nil +} diff --git a/vendor/github.com/elastic/go-ucfg/reify.go b/vendor/github.com/elastic/go-ucfg/reify.go new file mode 100644 index 00000000000..29c0e5dd5fe --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/reify.go @@ -0,0 +1,888 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "reflect" + "regexp" + "time" +) + +// Unpack unpacks c into a struct, a map, or a slice allocating maps, slices, +// and pointers as necessary. +// +// Unpack supports the options: PathSep, StructTag, ValidatorTag, Env, Resolve, +// ResolveEnv, ReplaceValues, AppendValues, PrependValues. +// +// When unpacking into a value, Unpack first will try to call Unpack if the +// value implements the Unpacker interface. Otherwise, Unpack tries to convert +// the internal value into the target type: +// +// # Primitive types +// +// bool: requires setting of type bool or string which parses into a +// boolean value (true, false, on, off) +// int(8, 16, 32, 64): requires any number type convertible to int or a string +// parsing to int. Fails if the target value would overflow. +// uint(8, 16, 32, 64): requires any number type convertible to int or a string +// parsing to int. Fails if the target value is negative or would overflow. +// float(32, 64): requires any number type convertible to float or a string +// parsing to float. Fails if the target value is negative or would overflow. +// string: requires any primitive value which is serialized into a string. +// +// # Special types: +// +// time.Duration: requires a number setting converted to seconds or a string +// parsed into time.Duration via time.ParseDuration. +// *regexp.Regexp: requires a string being compiled into a regular expression +// using regexp.Compile. +// *Config: requires a Config object to be stored by pointer into the target +// value. Can be used to capture a sub-Config without interpreting +// the settings yet. +// +// # Arrays/Slices: +// +// Requires a Config object with indexed entries. Named entries will not be +// unpacked into the Array/Slice. Primitive values will be handled like arrays +// of length 1. +// +// # Map +// +// Requires a Config object with all named top-level entries being unpacked into +// the map. +// +// # Struct +// +// Requires a Config object. All named values in the Config object will be unpacked +// into the struct its fields, if the name is available in the struct. +// A field its name is set using the `config` struct tag (configured by StructTag) +// If tag is missing or no field name is configured in the tag, the field name +// itself will be used. +// If the tag sets the `,ignore` flag, the field will not be overwritten. +// If the tag sets the `,inline` or `,squash` flag, Unpack will apply the current +// configuration namespace to the fields. +// If the tag option `replace` is configured, arrays and *ucfg.Config +// convertible fields are replaced by the new values. +// If the tag options `append` or `prepend` is used, arrays will be merged by +// appending/prepending the new array contents. +// The struct tag options `replace`, `append`, and `prepend` overwrites the +// global value merging strategy (e.g. ReplaceValues, AppendValues, ...) for all sub-fields. +// +// When unpacking into a map, primitive, or struct Unpack will call InitDefaults if +// the type implements the Initializer interface. The Initializer interface is not supported +// on arrays or slices. InitDefaults is initialized top-down, meaning that if struct contains +// a map, struct, or primitive that also implements the Initializer interface the contained +// type will be initialized after the struct that contains it. (e.g. if we have +// type A struct { B B }, with both A, and B implementing InitDefaults, then A.InitDefaults +// is called before B.InitDefaults). In the case that a struct contains a pointer to +// a type that implements the Initializer interface and the configuration doesn't contain a +// value for that field then the pointer will not be initialized and InitDefaults will not +// be called. +// +// Fields available in a struct or a map, but not in the Config object, will not +// be touched by Unpack unless they are initialized from InitDefaults. Those values will +// be validated using the same rules below just as if the values came from the configuration. +// This gives the requirement that pre-filled in values or defaults must also validate. +// +// Type aliases like "type myTypeAlias T" are unpacked using Unpack if the alias +// implements the Unpacker interface. Otherwise unpacking rules for type T will be used. +// +// When unpacking a value, the Validate method will be called if the value +// implements the Validator interface. Unpacking a struct field the validator +// options will be applied to the unpacked value as well. +// +// Struct field validators are set using the `validate` tag (configurable by +// ValidatorTag). Default validators options are: +// +// required: check value is set and not empty +// nonzero: check numeric value != 0 or string/slice not being empty +// positive: check numeric value >= 0 +// min=: check numeric value >= . If target type is time.Duration, +// can be a duration. +// max=: check numeric value <= . If target type is time.Duration, +// can be a duration. +// +// If a config value is not the convertible to the target type, or overflows the +// target type, Unpack will abort immediately and return the appropriate error. +// +// If validator tags or validation provided by Validate or Unmarshal fails, +// Unpack will abort immediately and return the validate error. +// +// When unpacking into an interface{} value, Unpack will store a value of one of +// these types in the value: +// +// bool for boolean values +// int64 for signed integer values +// uint64 for unsigned integer values +// float64 for floating point values +// string for string values +// []interface{} for list-only Config objects +// map[string]interface{} for Config objects +// nil for pointers if key has a nil value +func (c *Config) Unpack(to interface{}, options ...Option) error { + opts := makeOptions(options) + + if c == nil { + return raiseNil(ErrNilConfig) + } + if to == nil { + return raiseNil(ErrNilValue) + } + + vTo := reflect.ValueOf(to) + + k := vTo.Kind() + isValid := k == reflect.Ptr || k == reflect.Map + if !isValid { + return raisePointerRequired(vTo) + } + + return reifyInto(opts, vTo, c) +} + +// UnpackWithoutOptions method calls the Unpack method without any options provided. +func (c *Config) UnpackWithoutOptions(to interface{}) error { + return c.Unpack(to) +} + +func reifyInto(opts *options, to reflect.Value, from *Config) Error { + to = chaseValuePointers(to) + + if to, ok := tryTConfig(to); ok { + return mergeConfig(opts, to.Addr().Interface().(*Config), from) + } + + tTo := chaseTypePointers(to.Type()) + k := tTo.Kind() + + switch k { + case reflect.Map: + return reifyMap(opts, to, from, nil) + case reflect.Struct: + return reifyStruct(opts, to, from) + case reflect.Slice, reflect.Array: + fopts := fieldOptions{opts: opts, tag: tagOptions{}, validators: nil} + v, err := reifyMergeValue(fopts, to, cfgSub{from}) + if err != nil { + return err + } + to.Set(v) + return nil + } + + return raiseInvalidTopLevelType(to.Interface(), opts.meta) +} + +func reifyMap(opts *options, to reflect.Value, from *Config, validators []validatorTag) Error { + parentFields := opts.activeFields + defer func() { opts.activeFields = parentFields }() + + if to.Type().Key().Kind() != reflect.String { + return raiseKeyInvalidTypeUnpack(to.Type(), from) + } + + if to.IsNil() { + to.Set(reflect.MakeMap(to.Type())) + } + tryInitDefaults(to) + + fields := from.fields.dict() + if len(fields) == 0 { + if err := tryRecursiveValidate(to, opts, validators); err != nil { + return raiseValidation(from.ctx, from.metadata, "", err) + } + return nil + } + + for k, value := range fields { + opts.activeFields = newFieldSet(parentFields) + key := reflect.ValueOf(k) + + old := to.MapIndex(key) + var v reflect.Value + var err Error + + if !old.IsValid() { + v, err = reifyValue(fieldOptions{opts: opts}, to.Type().Elem(), value) + } else { + v, err = reifyMergeValue(fieldOptions{opts: opts}, old, value) + } + + if err != nil { + return err + } + if v.IsValid() { + to.SetMapIndex(key, v) + } + } + + if err := runValidators(to.Interface(), validators); err != nil { + return raiseValidation(from.ctx, from.metadata, "", err) + } + if err := tryValidate(to); err != nil { + return raiseValidation(from.ctx, from.metadata, "", err) + } + + return nil +} + +func reifyStruct(opts *options, orig reflect.Value, cfg *Config) Error { + parentFields := opts.activeFields + defer func() { opts.activeFields = parentFields }() + + orig = chaseValuePointers(orig) + + to := chaseValuePointers(reflect.New(chaseTypePointers(orig.Type()))) + if orig.Kind() == reflect.Struct { // if orig is has been allocated copy into to + to.Set(orig) + } + + if v, ok := valueIsUnpacker(to); ok { + err := unpackWith(opts, v, cfgSub{cfg}) + if err != nil { + return err + } + } else { + tryInitDefaults(to) + numField := to.NumField() + for i := 0; i < numField; i++ { + fInfo, skip, err := accessField(to, i, opts) + if err != nil { + return err + } + if skip { + continue + } + + if fInfo.tagOptions.squash { + vField := chaseValue(fInfo.value) + switch vField.Kind() { + case reflect.Struct, reflect.Map: + if err := reifyInto(fInfo.options, fInfo.value, cfg); err != nil { + return err + } + case reflect.Slice, reflect.Array: + fopts := fieldOptions{opts: fInfo.options, tag: fInfo.tagOptions, validators: fInfo.validatorTags} + v, err := reifyMergeValue(fopts, fInfo.value, cfgSub{cfg}) + if err != nil { + return err + } + vField.Set(v) + + default: + return raiseInlineNeedsObject(cfg, fInfo.name, fInfo.value.Type()) + } + } else { + fopts := fieldOptions{opts: fInfo.options, tag: fInfo.tagOptions, validators: fInfo.validatorTags} + if err := reifyGetField(cfg, fopts, fInfo.name, fInfo.value, fInfo.ftype); err != nil { + return err + } + } + } + } + + if err := tryValidate(to); err != nil { + return raiseValidation(cfg.ctx, cfg.metadata, "", err) + } + + orig.Set(pointerize(orig.Type(), to.Type(), to)) + return nil +} + +func reifyGetField( + cfg *Config, + opts fieldOptions, + name string, + to reflect.Value, + fieldType reflect.Type, +) Error { + p := parsePath(name, opts.opts.pathSep) + value, err := p.GetValue(cfg, opts.opts) + if err != nil { + if err.Reason() != ErrMissing { + return err + } + value = nil + } + + if isNil(value) { + // When fieldType is a pointer and the value is nil, return nil as the + // underlying type should not be allocated. + if fieldType.Kind() == reflect.Ptr { + if err := tryRecursiveValidate(to, opts.opts, opts.validators); err != nil { + return raiseValidation(cfg.ctx, cfg.metadata, name, err) + } + return nil + } + + // Primitive types return early when it doesn't implement the Initializer interface. + if fieldType.Kind() != reflect.Struct && !hasInitDefaults(fieldType) { + if err := tryRecursiveValidate(to, opts.opts, opts.validators); err != nil { + return raiseValidation(cfg.ctx, cfg.metadata, name, err) + } + return nil + } + + // None primitive types always get initialized even if it doesn't implement the + // Initializer interface, because nested types might implement the Initializer interface. + if value == nil { + value = &cfgNil{cfgPrimitive{cfg.ctx, cfg.metadata}} + } + } + + v, err := reifyMergeValue(opts, to, value) + if err != nil { + return err + } + + if v.IsValid() { + to.Set(pointerize(to.Type(), v.Type(), v)) + } + return nil +} + +func reifyValue( + opts fieldOptions, + t reflect.Type, + val value, +) (reflect.Value, Error) { + if t.Kind() == reflect.Interface && t.NumMethod() == 0 { + reified, err := val.reify(opts.opts) + if err != nil { + ctx := val.Context() + return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path(".")) + } + return reflect.ValueOf(reified), nil + } + + baseType := chaseTypePointers(t) + if tConfig.ConvertibleTo(baseType) { + cfg, err := val.toConfig(opts.opts) + if err != nil { + return reflect.Value{}, raiseExpectedObject(opts.opts, val) + } + + v := reflect.ValueOf(cfg).Convert(reflect.PtrTo(baseType)) + if t == baseType { // copy config + v = v.Elem() + } else { + v = pointerize(t, baseType, v) + } + return v, nil + } + + if baseType.Kind() == reflect.Struct { + sub, err := val.toConfig(opts.opts) + if err != nil { + return reifyPrimitive(opts, val, t, baseType) + } + + newSt := reflect.New(baseType) + if err := reifyInto(opts.opts, newSt, sub); err != nil { + return reflect.Value{}, err + } + + if t.Kind() != reflect.Ptr { + return newSt.Elem(), nil + } + return pointerize(t, baseType, newSt), nil + } + + switch baseType.Kind() { + case reflect.Map: + sub, err := val.toConfig(opts.opts) + if err != nil { + return reflect.Value{}, raiseExpectedObject(opts.opts, val) + } + + if baseType.Key().Kind() != reflect.String { + return reflect.Value{}, raiseKeyInvalidTypeUnpack(baseType, sub) + } + + newMap := reflect.MakeMap(baseType) + if err := reifyInto(opts.opts, newMap, sub); err != nil { + return reflect.Value{}, err + } + return newMap, nil + + case reflect.Slice: + v, err := reifySlice(opts, baseType, val) + if err != nil { + return reflect.Value{}, err + } + return pointerize(t, baseType, v), nil + } + + return reifyPrimitive(opts, val, t, baseType) +} + +func reifyMergeValue( + opts fieldOptions, + oldValue reflect.Value, val value, +) (reflect.Value, Error) { + old := chaseValueInterfaces(oldValue) + t := old.Type() + old = chaseValuePointers(old) + if (old.Kind() == reflect.Ptr || old.Kind() == reflect.Interface) && old.IsNil() { + return reifyValue(opts, t, val) + } + + baseType := chaseTypePointers(old.Type()) + + if tConfig.ConvertibleTo(baseType) { + sub, err := val.toConfig(opts.opts) + if err != nil { + return reflect.Value{}, raiseExpectedObject(opts.opts, val) + } + + if t == baseType { + // no pointer -> return type mismatch + return reflect.Value{}, raisePointerRequired(oldValue) + } + + // check if old is nil -> copy reference only + if old.Kind() == reflect.Ptr && old.IsNil() { + v, err := val.reflect(opts.opts) + if err != nil { + ctx := val.Context() + return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path(".")) + } + + v = v.Convert(reflect.PtrTo(baseType)) + return pointerize(t, baseType, v), nil + } + + // check if old == value + subOld := chaseValuePointers(old).Addr().Convert(tConfigPtr).Interface().(*Config) + if sub == subOld { + return oldValue, nil + } + + // old != value -> merge value into old + return oldValue, mergeFieldConfig(opts, subOld, sub) + } + + if v, ok := valueIsUnpacker(old); ok { + err := unpackWith(opts.opts, v, val) + if err != nil { + return reflect.Value{}, err + } + return old, nil + } + + switch baseType.Kind() { + case reflect.Map: + sub, err := val.toConfig(opts.opts) + if err != nil { + return reflect.Value{}, raiseExpectedObject(opts.opts, val) + } + return old, reifyMap(opts.opts, old, sub, opts.validators) + + case reflect.Struct: + sub, err := val.toConfig(opts.opts) + if err != nil { + return reflect.Value{}, raiseExpectedObject(opts.opts, val) + } + return oldValue, reifyStruct(opts.opts, old, sub) + + case reflect.Array: + return reifyArray(opts, old, baseType, val) + + case reflect.Slice: + return reifySliceMerge(opts, old, baseType, val) + } + + return reifyPrimitive(opts, val, t, baseType) +} + +func mergeFieldConfig(opts fieldOptions, to, from *Config) Error { + return mergeConfig(opts.opts, to, from) +} + +func reifyArray( + opts fieldOptions, + to reflect.Value, tTo reflect.Type, + val value, +) (reflect.Value, Error) { + arr, err := castArr(opts.opts, val) + if err != nil { + return reflect.Value{}, err + } + + if len(arr) != tTo.Len() { + ctx := val.Context() + return reflect.Value{}, raiseArraySize(ctx, val.meta(), len(arr), tTo.Len()) + } + return reifyDoArray(opts, to, tTo.Elem(), 0, val, arr) +} + +func reifySlice( + opts fieldOptions, + tTo reflect.Type, + val value, +) (reflect.Value, Error) { + return reifySliceMerge(opts, reflect.Value{}, tTo, val) +} + +func reifySliceMerge( + opts fieldOptions, + old reflect.Value, + tTo reflect.Type, + val value, +) (reflect.Value, Error) { + arr, err := castArr(opts.opts, val) + if err != nil { + return reflect.Value{}, err + } + + arrMergeCfg := opts.configHandling() + + l := len(arr) + start := 0 + cpyStart := 0 + + withOld := old.IsValid() && !old.IsNil() + if withOld { + ol := old.Len() + + switch arrMergeCfg { + case cfgReplaceValue: + // do nothing + + case cfgArrAppend: + l += ol + start = ol + + case cfgArrPrepend: + cpyStart = l + l += ol + + default: + if l < ol { + l = ol + } + } + } + tmp := reflect.MakeSlice(tTo, l, l) + + if withOld { + reflect.Copy(tmp.Slice(cpyStart, tmp.Len()), old) + } + return reifyDoArray(opts, tmp, tTo.Elem(), start, val, arr) +} + +func reifyDoArray( + opts fieldOptions, + to reflect.Value, elemT reflect.Type, + start int, + val value, + arr []value, +) (reflect.Value, Error) { + aLen := len(arr) + tLen := to.Len() + for idx := 0; idx < tLen; idx++ { + if idx >= start && idx < start+aLen { + v, err := reifyMergeValue(opts, to.Index(idx), arr[idx-start]) + if err != nil { + return reflect.Value{}, err + } + if v.IsValid() { + to.Index(idx).Set(v) + } + } else { + if err := tryRecursiveValidate(to.Index(idx), opts.opts, nil); err != nil { + return reflect.Value{}, raiseValidation(val.Context(), val.meta(), "", err) + } + } + } + + if err := runValidators(to.Interface(), opts.validators); err != nil { + ctx := val.Context() + return reflect.Value{}, raiseValidation(ctx, val.meta(), "", err) + } + + if err := tryValidate(to); err != nil { + ctx := val.Context() + return reflect.Value{}, raiseValidation(ctx, val.meta(), "", err) + } + + return to, nil +} + +func castArr(opts *options, v value) ([]value, Error) { + if sub, ok := v.(cfgSub); ok { + return sub.c.fields.array(), nil + } + if ref, ok := v.(*cfgDynamic); ok { + unrefed, err := ref.getValue(opts) + if err != nil { + return nil, raiseMissingMsg(ref.ctx.getParent(), ref.ctx.field, err.Error()) + } + + if sub, ok := unrefed.(cfgSub); ok { + return sub.c.fields.array(), nil + } + } + + l, err := v.Len(opts) + if err != nil { + ctx := v.Context() + return nil, raisePathErr(err, v.meta(), "", ctx.path(".")) + } + + if l == 0 { + return nil, nil + } + + return []value{v}, nil +} + +func reifyPrimitive( + opts fieldOptions, + val value, + t, baseType reflect.Type, +) (reflect.Value, Error) { + // zero initialize value if val==nil + if isNil(val) { + v := pointerize(t, baseType, reflect.Zero(baseType)) + return tryInitDefaults(v), nil + } + + var v reflect.Value + var err Error + var ok bool + + if v, ok = typeIsUnpacker(baseType); ok { + err := unpackWith(opts.opts, v, val) + if err != nil { + return reflect.Value{}, err + } + } else { + v, err = doReifyPrimitive(opts, val, baseType) + if err != nil { + return v, err + } + } + + if err := runValidators(v.Interface(), opts.validators); err != nil { + return reflect.Value{}, raiseValidation(val.Context(), val.meta(), "", err) + } + + if err := tryValidate(v); err != nil { + return reflect.Value{}, raiseValidation(val.Context(), val.meta(), "", err) + } + + return pointerize(t, baseType, chaseValuePointers(v)), nil +} + +func doReifyPrimitive( + opts fieldOptions, + val value, + baseType reflect.Type, +) (reflect.Value, Error) { + extras := map[reflect.Type]func(fieldOptions, value, reflect.Type) (reflect.Value, Error){ + tDuration: reifyDuration, + tRegexp: reifyRegexp, + } + + previous := opts.opts.activeFields + opts.opts.activeFields = newFieldSet(previous) + valT, err := val.typ(opts.opts) + if err != nil { + ctx := val.Context() + return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path(".")) + } + opts.opts.activeFields = previous + + // try primitive conversion + kind := baseType.Kind() + switch { + case valT.gotype == baseType: + v, err := val.reflect(opts.opts) + if err != nil { + ctx := val.Context() + return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path(".")) + } + return v, nil + + case kind == reflect.String: + s, err := val.toString(opts.opts) + if err != nil { + return reflect.Value{}, raiseConversion(opts.opts, val, err, "string") + } + return reflect.ValueOf(s), nil + + case extras[baseType] != nil: + v, err := extras[baseType](opts, val, baseType) + if err != nil { + return v, err + } + return v, nil + + case isInt(kind): + v, err := reifyInt(opts, val, baseType) + if err != nil { + return v, err + } + return v, nil + + case isUint(kind): + v, err := reifyUint(opts, val, baseType) + if err != nil { + return v, err + } + return v, nil + + case isFloat(kind): + v, err := reifyFloat(opts, val, baseType) + if err != nil { + return v, err + } + return v, nil + + case kind == reflect.Bool: + v, err := reifyBool(opts, val, baseType) + if err != nil { + return v, err + } + return v, nil + + case valT.gotype.ConvertibleTo(baseType): + v, err := val.reflect(opts.opts) + if err != nil { + ctx := val.Context() + return reflect.Value{}, raisePathErr(err, val.meta(), "", ctx.path(".")) + } + return v.Convert(baseType), nil + } + + return reflect.Value{}, raiseToTypeNotSupported(opts.opts, val, baseType) +} + +func reifyDuration( + opts fieldOptions, + val value, + _ reflect.Type, +) (reflect.Value, Error) { + var d time.Duration + var err error + + switch v := val.(type) { + case *cfgInt: + d = time.Duration(v.i) * time.Second + case *cfgUint: + d = time.Duration(v.u) * time.Second + case *cfgFloat: + d = time.Duration(v.f * float64(time.Second)) + case *cfgString: + d, err = time.ParseDuration(v.s) + default: + var s string + s, err = val.toString(opts.opts) + if err != nil { + return reflect.Value{}, raiseInvalidDuration(val, err) + } + + d, err = time.ParseDuration(s) + } + + if err != nil { + return reflect.Value{}, raiseInvalidDuration(val, err) + } + return reflect.ValueOf(d), nil +} + +func reifyRegexp( + opts fieldOptions, + val value, + _ reflect.Type, +) (reflect.Value, Error) { + s, err := val.toString(opts.opts) + if err != nil { + return reflect.Value{}, raiseConversion(opts.opts, val, err, "regex") + } + + r, err := regexp.Compile(s) + if err != nil { + return reflect.Value{}, raiseInvalidRegexp(val, err) + } + return reflect.ValueOf(r).Elem(), nil +} + +func reifyInt( + opts fieldOptions, + val value, + t reflect.Type, +) (reflect.Value, Error) { + i, err := val.toInt(opts.opts) + if err != nil { + return reflect.Value{}, raiseConversion(opts.opts, val, err, "int") + } + + tmp := reflect.Zero(t) + if tmp.OverflowInt(i) { + return reflect.Value{}, raiseConversion(opts.opts, val, ErrOverflow, "int") + } + return reflect.ValueOf(i).Convert(t), nil +} + +func reifyUint( + opts fieldOptions, + val value, + t reflect.Type, +) (reflect.Value, Error) { + u, err := val.toUint(opts.opts) + if err != nil { + return reflect.Value{}, raiseConversion(opts.opts, val, err, "uint") + } + + tmp := reflect.Zero(t) + if tmp.OverflowUint(u) { + return reflect.Value{}, raiseConversion(opts.opts, val, ErrOverflow, "uint") + } + return reflect.ValueOf(u).Convert(t), nil +} + +func reifyFloat( + opts fieldOptions, + val value, + t reflect.Type, +) (reflect.Value, Error) { + f, err := val.toFloat(opts.opts) + if err != nil { + return reflect.Value{}, raiseConversion(opts.opts, val, err, "float") + } + + tmp := reflect.Zero(t) + if tmp.OverflowFloat(f) { + return reflect.Value{}, raiseConversion(opts.opts, val, ErrOverflow, "float") + } + return reflect.ValueOf(f).Convert(t), nil +} + +func reifyBool( + opts fieldOptions, + val value, + t reflect.Type, +) (reflect.Value, Error) { + b, err := val.toBool(opts.opts) + if err != nil { + return reflect.Value{}, raiseConversion(opts.opts, val, err, "bool") + } + return reflect.ValueOf(b).Convert(t), nil +} diff --git a/vendor/github.com/elastic/go-ucfg/types.go b/vendor/github.com/elastic/go-ucfg/types.go new file mode 100644 index 00000000000..13d601c9d86 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/types.go @@ -0,0 +1,609 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "fmt" + "math" + "reflect" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/elastic/go-ucfg/parse" +) + +type value interface { + typ(opts *options) (typeInfo, error) + + cpy(c context) value + + Context() context + SetContext(c context) + + meta() *Meta + setMeta(m *Meta) + + Len(opts *options) (int, error) + + reflect(opts *options) (reflect.Value, error) + reify(opts *options) (interface{}, error) + + toBool(opts *options) (bool, error) + toString(opts *options) (string, error) + toInt(opts *options) (int64, error) + toUint(opts *options) (uint64, error) + toFloat(opts *options) (float64, error) + toConfig(opts *options) (*Config, error) + canCache() bool +} + +type typeInfo struct { + name string + gotype reflect.Type +} + +type context struct { + parent value + field string +} + +type cfgBool struct { + cfgPrimitive + b bool +} + +type cfgInt struct { + cfgPrimitive + i int64 +} + +type cfgUint struct { + cfgPrimitive + u uint64 +} + +type cfgFloat struct { + cfgPrimitive + f float64 +} + +type cfgString struct { + cfgPrimitive + s string +} + +type cfgSub struct { + c *Config +} + +type cfgNil struct{ cfgPrimitive } + +type cfgPrimitive struct { + ctx context + metadata *Meta +} + +type cfgDynamic struct { + cfgPrimitive + id cacheID + dyn dynValue +} + +type dynValue interface { + getValue(p *cfgPrimitive, opts *options) (value, error) + String() string +} + +type refDynValue reference + +type spliceDynValue struct { + e varEvaler +} + +var spliceSeq int32 + +func (c *context) empty() bool { + return c.parent == nil +} + +func (c *context) getParent() *Config { + if c.parent == nil { + return nil + } + + if cfg, ok := c.parent.(cfgSub); ok { + return cfg.c + } + return nil +} + +func (c *context) path(sep string) string { + if c.field == "" { + return "" + } + + if c.parent != nil { + p := c.parent.Context() + if parent := p.path(sep); parent != "" { + return fmt.Sprintf("%v%v%v", parent, sep, c.field) + } + } + + return c.field +} + +func (c *context) pathOf(field, sep string) string { + if p := c.path(sep); p != "" { + return fmt.Sprintf("%v%v%v", p, sep, field) + } + return field +} + +func newBool(ctx context, m *Meta, b bool) *cfgBool { + return &cfgBool{cfgPrimitive{ctx, m}, b} +} + +func newInt(ctx context, m *Meta, i int64) *cfgInt { + return &cfgInt{cfgPrimitive{ctx, m}, i} +} + +func newUint(ctx context, m *Meta, u uint64) *cfgUint { + return &cfgUint{cfgPrimitive{ctx, m}, u} +} + +func newFloat(ctx context, m *Meta, f float64) *cfgFloat { + return &cfgFloat{cfgPrimitive{ctx, m}, f} +} + +func newString(ctx context, m *Meta, s string) *cfgString { + return &cfgString{cfgPrimitive{ctx, m}, s} +} + +func newRef(ctx context, m *Meta, ref *reference) *cfgDynamic { + return newDyn(ctx, m, (*refDynValue)(ref)) +} + +func newSplice(ctx context, m *Meta, s varEvaler) *cfgDynamic { + return newDyn(ctx, m, spliceDynValue{s}) +} + +func newDyn(ctx context, m *Meta, val dynValue) *cfgDynamic { + seq := atomic.AddInt32(&spliceSeq, 1) + dyn := &cfgDynamic{cfgPrimitive: cfgPrimitive{ctx, m}, dyn: val} + dyn.id = cacheID(fmt.Sprintf("%8X-%4X-%p", time.Now().Unix(), seq, dyn)) + return dyn +} + +func (p *cfgPrimitive) Context() context { return p.ctx } +func (p *cfgPrimitive) SetContext(c context) { p.ctx = c } +func (p *cfgPrimitive) meta() *Meta { return p.metadata } +func (p *cfgPrimitive) setMeta(m *Meta) { p.metadata = m } +func (cfgPrimitive) Len(*options) (int, error) { return 1, nil } +func (cfgPrimitive) toBool(*options) (bool, error) { return false, ErrTypeMismatch } +func (cfgPrimitive) toString(*options) (string, error) { return "", ErrTypeMismatch } +func (cfgPrimitive) toInt(*options) (int64, error) { return 0, ErrTypeMismatch } +func (cfgPrimitive) toUint(*options) (uint64, error) { return 0, ErrTypeMismatch } +func (cfgPrimitive) toFloat(*options) (float64, error) { return 0, ErrTypeMismatch } +func (cfgPrimitive) toConfig(*options) (*Config, error) { return nil, ErrTypeMismatch } +func (cfgPrimitive) canCache() bool { return true } + +func (c *cfgNil) cpy(ctx context) value { return &cfgNil{cfgPrimitive{ctx, c.metadata}} } +func (*cfgNil) Len(*options) (int, error) { return 0, nil } +func (*cfgNil) toString(*options) (string, error) { return "null", nil } +func (*cfgNil) toInt(*options) (int64, error) { return 0, ErrTypeMismatch } +func (*cfgNil) toUint(*options) (uint64, error) { return 0, ErrTypeMismatch } +func (*cfgNil) toFloat(*options) (float64, error) { return 0, ErrTypeMismatch } +func (*cfgNil) reify(*options) (interface{}, error) { return nil, nil } +func (*cfgNil) typ(*options) (typeInfo, error) { return typeInfo{"any", reflect.PtrTo(tConfig)}, nil } +func (c *cfgNil) meta() *Meta { return c.metadata } +func (c *cfgNil) setMeta(m *Meta) { c.metadata = m } + +func (c *cfgNil) reflect(opts *options) (reflect.Value, error) { + cfg, _ := c.toConfig(opts) + return reflect.ValueOf(cfg), nil +} + +func (c *cfgNil) toConfig(*options) (*Config, error) { + n := New() + n.ctx = c.ctx + return n, nil +} + +func (c *cfgBool) cpy(ctx context) value { return newBool(ctx, c.meta(), c.b) } +func (c *cfgBool) toBool(*options) (bool, error) { return c.b, nil } +func (c *cfgBool) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.b), nil } +func (c *cfgBool) reify(*options) (interface{}, error) { return c.b, nil } +func (c *cfgBool) toString(*options) (string, error) { return fmt.Sprintf("%t", c.b), nil } +func (c *cfgBool) typ(*options) (typeInfo, error) { return typeInfo{"bool", tBool}, nil } + +func (c *cfgInt) cpy(ctx context) value { return newInt(ctx, c.meta(), c.i) } +func (c *cfgInt) toInt(*options) (int64, error) { return c.i, nil } +func (c *cfgInt) toFloat(*options) (float64, error) { return float64(c.i), nil } +func (c *cfgInt) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.i), nil } +func (c *cfgInt) reify(*options) (interface{}, error) { return c.i, nil } +func (c *cfgInt) toString(*options) (string, error) { return fmt.Sprintf("%d", c.i), nil } +func (c *cfgInt) typ(*options) (typeInfo, error) { return typeInfo{"int", tInt64}, nil } +func (c *cfgInt) toUint(*options) (uint64, error) { + if c.i < 0 { + return 0, ErrNegative + } + return uint64(c.i), nil +} + +func (c *cfgUint) cpy(ctx context) value { return newUint(ctx, c.meta(), c.u) } +func (c *cfgUint) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.u), nil } +func (c *cfgUint) reify(*options) (interface{}, error) { return c.u, nil } +func (c *cfgUint) toString(*options) (string, error) { return fmt.Sprintf("%d", c.u), nil } +func (c *cfgUint) typ(*options) (typeInfo, error) { return typeInfo{"uint", tUint64}, nil } +func (c *cfgUint) toUint(*options) (uint64, error) { return c.u, nil } +func (c *cfgUint) toFloat(*options) (float64, error) { return float64(c.u), nil } +func (c *cfgUint) toInt(*options) (int64, error) { + if c.u > math.MaxInt64 { + return 0, ErrOverflow + } + return int64(c.u), nil +} + +func (c *cfgFloat) cpy(ctx context) value { return newFloat(ctx, c.meta(), c.f) } +func (c *cfgFloat) toFloat(*options) (float64, error) { return c.f, nil } +func (c *cfgFloat) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.f), nil } +func (c *cfgFloat) reify(*options) (interface{}, error) { return c.f, nil } +func (c *cfgFloat) toString(*options) (string, error) { return fmt.Sprintf("%v", c.f), nil } +func (c *cfgFloat) typ(*options) (typeInfo, error) { return typeInfo{"float", tFloat64}, nil } + +func (c *cfgFloat) toUint(*options) (uint64, error) { + if c.f < 0 { + return 0, ErrNegative + } + if c.f > math.MaxUint64 { + return 0, ErrOverflow + } + return uint64(c.f), nil +} + +func (c *cfgFloat) toInt(*options) (int64, error) { + if c.f < math.MinInt64 || math.MaxInt64 < c.f { + return 0, ErrOverflow + } + return int64(c.f), nil +} + +func (c *cfgString) cpy(ctx context) value { return newString(ctx, c.meta(), c.s) } +func (c *cfgString) reflect(*options) (reflect.Value, error) { + return reflect.ValueOf(c.s), nil +} +func (c *cfgString) reify(*options) (interface{}, error) { return c.s, nil } +func (c *cfgString) typ(*options) (typeInfo, error) { return typeInfo{"string", tString}, nil } +func (c *cfgString) toBool(*options) (bool, error) { return strconv.ParseBool(c.s) } +func (c *cfgString) toString(*options) (string, error) { return c.s, nil } +func (c *cfgString) toInt(*options) (int64, error) { return strconv.ParseInt(c.s, 0, 64) } +func (c *cfgString) toUint(*options) (uint64, error) { return strconv.ParseUint(c.s, 0, 64) } +func (c *cfgString) toFloat(*options) (float64, error) { return strconv.ParseFloat(c.s, 64) } + +func (c cfgSub) Context() context { return c.c.ctx } +func (cfgSub) toBool(*options) (bool, error) { return false, ErrTypeMismatch } +func (cfgSub) toString(*options) (string, error) { return "", ErrTypeMismatch } +func (cfgSub) toInt(*options) (int64, error) { return 0, ErrTypeMismatch } +func (cfgSub) toUint(*options) (uint64, error) { return 0, ErrTypeMismatch } +func (cfgSub) toFloat(*options) (float64, error) { return 0, ErrTypeMismatch } +func (c cfgSub) toConfig(*options) (*Config, error) { return c.c, nil } +func (c cfgSub) canCache() bool { return false } + +func (c cfgSub) Len(*options) (int, error) { + arr := c.c.fields.array() + if arr != nil { + + return len(arr), nil + } + + return 1, nil +} + +func (c cfgSub) typ(*options) (typeInfo, error) { + return typeInfo{"object", reflect.PtrTo(tConfig)}, nil +} + +// func (cfgSub) typ() (typeInfo, error) { return typeInfo{"object", reflect.PtrTo(tConfig)}, nil } +func (c cfgSub) reflect(*options) (reflect.Value, error) { return reflect.ValueOf(c.c), nil } +func (c cfgSub) meta() *Meta { return c.c.metadata } +func (c cfgSub) setMeta(m *Meta) { c.c.metadata = m } + +func (c cfgSub) cpy(ctx context) value { + newC := cfgSub{ + c: &Config{ctx: ctx, metadata: c.c.metadata}, + } + + dict := c.c.fields.dict() + arr := c.c.fields.array() + fields := &fields{} + + for name, f := range dict { + ctx := f.Context() + v := f.cpy(context{field: ctx.field, parent: newC}) + fields.set(name, v) + } + + if arr != nil { + fields.a = make([]value, len(arr)) + for i, f := range arr { + ctx := f.Context() + v := f.cpy(context{field: ctx.field, parent: newC}) + fields.setAt(i, newC, v) + } + } + + newC.c.fields = fields + return newC +} + +func (c cfgSub) SetContext(ctx context) { + if c.c.ctx.empty() { + c.c.ctx = ctx + } else { + c.c = &Config{ + ctx: ctx, + fields: c.c.fields, + } + } +} + +func (c cfgSub) reify(opts *options) (interface{}, error) { + parentFields := opts.activeFields + defer func() { opts.activeFields = parentFields }() + + fields := c.c.fields.dict() + arr := c.c.fields.array() + + switch { + case len(fields) == 0 && len(arr) == 0: + return nil, nil + case len(fields) > 0 && len(arr) == 0: + m := make(map[string]interface{}) + for k, v := range fields { + opts.activeFields = newFieldSet(parentFields) + var err error + if m[k], err = v.reify(opts); err != nil { + return nil, err + } + } + return m, nil + case len(fields) == 0 && len(arr) > 0: + m := make([]interface{}, len(arr)) + for i, v := range arr { + opts.activeFields = newFieldSet(parentFields) + var err error + if m[i], err = v.reify(opts); err != nil { + return nil, err + } + } + return m, nil + default: + m := make(map[string]interface{}) + for k, v := range fields { + opts.activeFields = newFieldSet(parentFields) + var err error + if m[k], err = v.reify(opts); err != nil { + return nil, err + } + } + for i, v := range arr { + opts.activeFields = newFieldSet(parentFields) + var err error + m[fmt.Sprintf("%d", i)], err = v.reify(opts) + if err != nil { + return nil, err + } + } + return m, nil + } +} + +func (d *cfgDynamic) typ(opts *options) (ti typeInfo, err error) { + d.withValue(&err, opts, func(v value) { + ti, err = v.typ(opts) + }) + return +} + +func (d *cfgDynamic) cpy(c context) value { + return newDyn(c, d.meta(), d.dyn) +} + +func (d *cfgDynamic) Len(opts *options) (l int, err error) { + d.withValue(&err, opts, func(v value) { + l, err = v.Len(opts) + }) + return +} + +func (d *cfgDynamic) reflect(opts *options) (rv reflect.Value, err error) { + d.withValue(&err, opts, func(v value) { + rv, err = v.reflect(opts) + }) + return +} + +func (d *cfgDynamic) reify(opts *options) (rv interface{}, err error) { + d.withValue(&err, opts, func(v value) { + rv, err = v.reify(opts) + }) + return +} + +func (d *cfgDynamic) toBool(opts *options) (b bool, err error) { + d.withValue(&err, opts, func(v value) { + b, err = v.toBool(opts) + }) + return +} + +func (d *cfgDynamic) toString(opts *options) (s string, err error) { + d.withValue(&err, opts, func(v value) { + s, err = v.toString(opts) + }) + return +} + +func (d *cfgDynamic) toInt(opts *options) (i int64, err error) { + d.withValue(&err, opts, func(v value) { + i, err = v.toInt(opts) + }) + return +} + +func (d *cfgDynamic) toUint(opts *options) (u uint64, err error) { + d.withValue(&err, opts, func(v value) { + u, err = v.toUint(opts) + }) + return +} + +func (d *cfgDynamic) toFloat(opts *options) (f float64, err error) { + d.withValue(&err, opts, func(v value) { + f, err = v.toFloat(opts) + }) + return +} + +func (d *cfgDynamic) toConfig(opts *options) (cfg *Config, err error) { + d.withValue(&err, opts, func(v value) { + cfg, err = v.toConfig(opts) + }) + return +} + +func (d *cfgDynamic) withValue(err *error, opts *options, fn func(value)) { + var v value + if v, *err = d.getValue(opts); *err == nil { + fn(v) + } +} + +func (d *cfgDynamic) getValue(opts *options) (value, error) { + return opts.parsed.cachedValue(d.id, func() (value, error) { + return d.dyn.getValue(&d.cfgPrimitive, opts) + }) +} + +func (d cfgDynamic) canCache() bool { + return false +} + +func (r *refDynValue) String() string { + ref := (*reference)(r) + return ref.String() +} + +func (r *refDynValue) getValue( + p *cfgPrimitive, + opts *options, +) (value, error) { + ref := (*reference)(r) + v, err := ref.resolveRef(p.ctx.getParent(), opts) + // If not found or we have a cyclic reference we try the environment resolvers + if v != nil || criticalResolveError(err) { + return v, err + } + previousErr := err + + str, parseCfg, err := ref.resolveEnv(p.ctx.getParent(), opts) + if err != nil { + // TODO(ph): Not everything is an Error, will do some cleanup in another PR. + if v, ok := previousErr.(Error); ok { + if v.Reason() == ErrCyclicReference { + return nil, previousErr + } + } + return nil, err + } + return parseValue(p, opts, str, parseCfg) +} + +func (s spliceDynValue) getValue( + p *cfgPrimitive, + opts *options, +) (value, error) { + splice := s.e + str, err := splice.eval(p.ctx.getParent(), opts) + if err != nil { + return nil, err + } + + return parseValue(p, opts, str, parse.DefaultConfig) +} + +func (s spliceDynValue) String() string { + return "" +} + +func parseValue(p *cfgPrimitive, opts *options, str string, parseCfg parse.Config) (value, error) { + if opts.noParse { + return nil, raiseNoParse(p.ctx, p.meta()) + } + + ifc, err := parse.ValueWithConfig(str, parseCfg) + if err != nil { + return nil, err + } + + if ifc == nil { + if strings.TrimSpace(str) == "" { + return newString(p.ctx, p.meta(), str), nil + } + return &cfgNil{cfgPrimitive{ctx: p.ctx, metadata: p.meta()}}, nil + } + + switch v := ifc.(type) { + case bool: + return newBool(p.ctx, p.meta(), v), nil + case int64: + return newInt(p.ctx, p.meta(), v), nil + case uint64: + return newUint(p.ctx, p.meta(), v), nil + case float64: + return newFloat(p.ctx, p.meta(), v), nil + case string: + return newString(p.ctx, p.meta(), v), nil + } + + sub, err := normalize(opts, ifc) + if err != nil { + return nil, err + } + sub.ctx = p.ctx + sub.metadata = p.metadata + return cfgSub{sub}, nil +} + +func isNil(v value) bool { + if v == nil { + return true + } + _, tst := v.(*cfgNil) + return tst +} + +func isSub(v value) bool { + if v == nil { + return false + } + _, tst := v.(cfgSub) + return tst +} diff --git a/vendor/github.com/elastic/go-ucfg/ucfg.go b/vendor/github.com/elastic/go-ucfg/ucfg.go new file mode 100644 index 00000000000..bddd42333f1 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/ucfg.go @@ -0,0 +1,331 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "fmt" + "reflect" + "regexp" + "sort" + "time" +) + +// Config object to store hierarchical configurations into. Config can be +// both a dictionary and a list holding primitive values. Primitive values +// can be booleans, integers, float point numbers and strings. +// +// Config provides a low level interface for setting and getting settings +// via SetBool, SetInt, SetUing, SetFloat, SetString, SetChild, Bool, Int, Uint, +// Float, String, and Child. +// +// A more user-friendly high level interface is provided via Unpack and Merge. +type Config struct { + ctx context + metadata *Meta + fields *fields +} + +type fieldOptions struct { + opts *options + tag tagOptions + validators []validatorTag +} + +type fields struct { + d map[string]value + a []value +} + +// Meta holds additional meta data per config value. +type Meta struct { + Source string +} + +var ( + tConfig = reflect.TypeOf(Config{}) + tConfigPtr = reflect.PtrTo(tConfig) + tConfigMap = reflect.TypeOf((map[string]interface{})(nil)) + tInterfaceArray = reflect.TypeOf([]interface{}(nil)) + + // interface types + tError = reflect.TypeOf((*error)(nil)).Elem() + iInitializer = reflect.TypeOf((*Initializer)(nil)).Elem() + tValidator = reflect.TypeOf((*Validator)(nil)).Elem() + + // primitives + tBool = reflect.TypeOf(true) + tInt64 = reflect.TypeOf(int64(0)) + tUint64 = reflect.TypeOf(uint64(0)) + tFloat64 = reflect.TypeOf(float64(0)) + tString = reflect.TypeOf("") + tDuration = reflect.TypeOf(time.Duration(0)) + tRegexp = reflect.TypeOf(regexp.Regexp{}) +) + +// New creates a new empty Config object. +func New() *Config { + return &Config{ + fields: &fields{nil, nil}, + } +} + +// MustNewFrom creates a new config object normalizing and copying from into the new +// Config object. MustNewFrom uses Merge to copy from. +// +// MustNewFrom supports the options: PathSep, MetaData, StructTag, VarExp +func MustNewFrom(from interface{}, opts ...Option) *Config { + c := New() + if err := c.Merge(from, opts...); err != nil { + panic(err) + } + return c +} + +// NewFrom creates a new config object normalizing and copying from into the new +// Config object. NewFrom uses Merge to copy from. +// +// NewFrom supports the options: PathSep, MetaData, StructTag, VarExp +func NewFrom(from interface{}, opts ...Option) (*Config, error) { + c := New() + if err := c.Merge(from, opts...); err != nil { + return nil, err + } + return c, nil +} + +// IsDict checks if c has named keys. +func (c *Config) IsDict() bool { + return c.fields.dict() != nil +} + +// IsArray checks if c has index only accessible settings. +func (c *Config) IsArray() bool { + return c.fields.array() != nil +} + +// GetFields returns a list of all top-level named keys in c. +func (c *Config) GetFields() []string { + var names []string + for k := range c.fields.dict() { + names = append(names, k) + } + return names +} + +// Has checks if a field by the given path+idx configuration exists. +// Has returns an error if the path can not be resolved because a primitive +// value is found in the middle of the traversal. +func (c *Config) Has(name string, idx int, options ...Option) (bool, error) { + opts := makeOptions(options) + p := parsePathIdx(name, opts.pathSep, idx) + return p.Has(c, opts) +} + +// HasField checks if c has a top-level named key name. +func (c *Config) HasField(name string) bool { + _, ok := c.fields.get(name) + return ok +} + +// Remove removes a setting from the config. If the configuration references +// another configuration namespace, then the setting will be removed from the +// linked reference. +// Remove returns true if the setting was removed. If the path can't be +// resolved (e.g. due to type mismatch) Remove will return an error. +// +// Settings can be created on Unpack via Env, Resolve, and ResolveEnv. Settings +// generated dynamically on Unpack can not be removed. Remove ignores any +// configured environments and will return an error if a value can not be +// removed for this reason. +// +// The setting path is constructed from name and idx. If name is set and idx is -1, +// only the name is used to access the setting by name. If name is empty, idx +// must be >= 0, assuming the Config is a list. If both name and idx are set, +// the name must point to a list. +// +// Remove supports the options: PathSep +func (c *Config) Remove(name string, idx int, options ...Option) (bool, error) { + opts := makeOptions(options) + + // ignore environments + opts.env = nil + opts.resolvers = nil + opts.noParse = true + + p := parsePathIdx(name, opts.pathSep, idx) + return p.Remove(c, opts) +} + +// Path gets the absolute path of c separated by sep. If c is a root-Config an +// empty string will be returned. +func (c *Config) Path(sep string) string { + return c.ctx.path(sep) +} + +// PathOf gets the absolute path of a potential setting field in c with name +// separated by sep. +func (c *Config) PathOf(field, sep string) string { + return c.ctx.pathOf(field, sep) +} + +// Parent returns the parent configuration or nil if c is already a root +// Configuration. +func (c *Config) Parent() *Config { + ctx := c.ctx + for { + if ctx.parent == nil { + return nil + } + + switch p := ctx.parent.(type) { + case cfgSub: + return p.c + default: + return nil + } + } +} + +// FlattenedKeys return a sorted flattened views of the set keys in the configuration +func (c *Config) FlattenedKeys(opts ...Option) []string { + var keys []string + normalizedOptions := makeOptions(opts) + + if normalizedOptions.pathSep == "" { + normalizedOptions.pathSep = "." + } + + if c.IsDict() { + for _, v := range c.fields.dict() { + + subcfg, err := v.toConfig(normalizedOptions) + if err != nil { + ctx := v.Context() + p := ctx.path(normalizedOptions.pathSep) + keys = append(keys, p) + } else { + newKeys := subcfg.FlattenedKeys(opts...) + keys = append(keys, newKeys...) + } + } + } else if c.IsArray() { + for _, a := range c.fields.array() { + scfg, err := a.toConfig(normalizedOptions) + + if err != nil { + ctx := a.Context() + p := ctx.path(normalizedOptions.pathSep) + keys = append(keys, p) + } else { + newKeys := scfg.FlattenedKeys(opts...) + keys = append(keys, newKeys...) + } + } + } + + sort.Strings(keys) + return keys +} + +func (f *fields) get(name string) (value, bool) { + if f.d == nil { + return nil, false + } + v, found := f.d[name] + return v, found +} + +func (f *fields) dict() map[string]value { + return f.d +} + +func (f *fields) array() []value { + return f.a +} + +func (f *fields) del(name string) bool { + _, exists := f.d[name] + if exists { + delete(f.d, name) + } + return exists +} + +func (f *fields) delAt(i int) bool { + a := f.a + if i < 0 || len(a) <= i { + return false + } + + copy(a[i:], a[i+1:]) + a[len(a)-1] = nil + f.a = a[:len(a)-1] + return true +} + +func (f *fields) set(name string, v value) { + if f.d == nil { + f.d = map[string]value{} + } + f.d[name] = v +} + +func (f *fields) add(v value) { + f.a = append(f.a, v) +} + +func (f *fields) setAt(idx int, parent, v value) { + l := len(f.a) + if idx >= l { + tmp := make([]value, idx+1) + copy(tmp, f.a) + + for i := l; i < idx; i++ { + ctx := context{parent: parent, field: fmt.Sprintf("%d", i)} + tmp[i] = &cfgNil{cfgPrimitive{ctx, nil}} + } + + f.a = tmp + } + + f.a[idx] = v +} + +func (f *fields) append(parent value, a []value) { + l := len(f.a) + count := len(a) + if count == 0 { + return + } + + for i := 0; i < count; i, l = i+1, l+1 { + ctx := context{ + parent: parent, + field: fmt.Sprintf("%v", l), + } + f.setAt(l, parent, a[i].cpy(ctx)) + } +} + +func (o *fieldOptions) configHandling() configHandling { + h := o.tag.cfgHandling + if h == cfgDefaultHandling { + h = o.opts.configValueHandling + } + return h +} diff --git a/vendor/github.com/elastic/go-ucfg/unpack.go b/vendor/github.com/elastic/go-ucfg/unpack.go new file mode 100644 index 00000000000..00fb92c9376 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/unpack.go @@ -0,0 +1,247 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import "reflect" + +// Unpacker type used by Unpack to allow types to implement custom configuration +// unpacking. +type Unpacker interface { + // Unpack is called if a setting of field has a type implementing Unpacker. + // + // The interface{} value passed to Unpack can be of type: bool, int64, uint64, + // float64, string, []interface{} or map[string]interface{}. + Unpack(interface{}) error +} + +// BoolUnpacker interface specializes the Unpacker interface +// by casting values to bool when calling Unpack. +type BoolUnpacker interface { + Unpack(b bool) error +} + +// IntUnpacker interface specializes the Unpacker interface +// by casting values to int64 when calling Unpack. +type IntUnpacker interface { + Unpack(i int64) error +} + +// UintUnpacker interface specializes the Unpacker interface +// by casting values to uint64 when calling Unpack. +type UintUnpacker interface { + Unpack(u uint64) error +} + +// FloatUnpacker interface specializes the Unpacker interface +// by casting values to float64 when calling Unpack. +type FloatUnpacker interface { + Unpack(f float64) error +} + +// StringUnpacker interface specializes the Unpacker interface +// by casting values to string when calling Unpack. +type StringUnpacker interface { + Unpack(s string) error +} + +// ConfigUnpacker interface specializes the Unpacker interface +// by passing the the *Config object directly instead of +// transforming the *Config object into map[string]interface{}. +type ConfigUnpacker interface { + Unpack(c *Config) error +} + +var ( + // unpacker interface types + tUnpacker = reflect.TypeOf((*Unpacker)(nil)).Elem() + tBoolUnpacker = reflect.TypeOf((*BoolUnpacker)(nil)).Elem() + tIntUnpacker = reflect.TypeOf((*IntUnpacker)(nil)).Elem() + tUintUnpacker = reflect.TypeOf((*UintUnpacker)(nil)).Elem() + tFloatUnpacker = reflect.TypeOf((*FloatUnpacker)(nil)).Elem() + tStringUnpacker = reflect.TypeOf((*StringUnpacker)(nil)).Elem() + tConfigUnpacker = reflect.TypeOf((*ConfigUnpacker)(nil)).Elem() + + tUnpackers = [...]reflect.Type{ + tUnpacker, + tBoolUnpacker, + tIntUnpacker, + tUintUnpacker, + tFloatUnpacker, + tStringUnpacker, + tConfigUnpacker, + } +) + +// valueIsUnpacker checks if v implements the Unpacker interface. +// If there exists a pointer to v, the pointer to v is also tested. +func valueIsUnpacker(v reflect.Value) (reflect.Value, bool) { + for { + if implementsUnpacker(v.Type()) { + return v, true + } + + if !v.CanAddr() { + break + } + v = v.Addr() + } + + return reflect.Value{}, false +} + +func typeIsUnpacker(t reflect.Type) (reflect.Value, bool) { + if implementsUnpacker(t) { + return reflect.New(t).Elem(), true + } + + if implementsUnpacker(reflect.PtrTo(t)) { + return reflect.New(t), true + } + + return reflect.Value{}, false +} + +func implementsUnpacker(t reflect.Type) bool { + // ucfg.Config or structures that can be casted to ucfg.Config are not + // Unpackers. + if tConfig.ConvertibleTo(chaseTypePointers(t)) { + return false + } + + for _, tUnpack := range tUnpackers { + if t.Implements(tUnpack) { + return true + } + } + + if t.NumMethod() == 0 { + return false + } + + // test if object has 'Unpack' method + method, ok := t.MethodByName("Unpack") + if !ok { + + return false + } + + // check method input and output parameters to match the ConfigUnpacker interface: + // func (to *T) Unpack(cfg *TConfig) error + // with T being the method receiver (input paramter 0) + // and TConfig being the aliased config type to convert to (input parameter 1) + paramCountCheck := method.Type.NumIn() == 2 && method.Type.NumOut() == 1 + if !paramCountCheck { + return false + } + if !method.Type.Out(0).Implements(tError) { + // return variable is not compatible to `error` type + return false + } + + // method receiver is known, check config parameters being compatible + tIn := method.Type.In(1) + return tConfig.ConvertibleTo(tIn) || tConfigPtr.ConvertibleTo(tIn) +} + +func unpackWith(opts *options, v reflect.Value, with value) Error { + // short circuit nil values + if isNil(with) { + return nil + } + + ctx := with.Context() + meta := with.meta() + + var err error + value := v.Interface() + switch u := value.(type) { + case Unpacker: + var reified interface{} + if reified, err = with.reify(opts); err == nil { + err = u.Unpack(reified) + } + + case BoolUnpacker: + var b bool + if b, err = with.toBool(opts); err == nil { + err = u.Unpack(b) + } + + case IntUnpacker: + var n int64 + if n, err = with.toInt(opts); err == nil { + err = u.Unpack(n) + } + + case UintUnpacker: + var n uint64 + if n, err = with.toUint(opts); err == nil { + err = u.Unpack(n) + } + + case FloatUnpacker: + var f float64 + if f, err = with.toFloat(opts); err == nil { + err = u.Unpack(f) + } + + case StringUnpacker: + var s string + if s, err = with.toString(opts); err == nil { + err = u.Unpack(s) + } + + case ConfigUnpacker: + var c *Config + if c, err = with.toConfig(opts); err == nil { + err = u.Unpack(c) + } + + default: + var c *Config + if c, err = with.toConfig(opts); err == nil { + err = reflectUnpackWithConfig(v, c) + } + + } + + if err != nil { + return raisePathErr(err, meta, "", ctx.path(".")) + } + return nil +} + +func reflectUnpackWithConfig(v reflect.Value, c *Config) error { + method, _ := v.Type().MethodByName("Unpack") + tIn := method.Type.In(1) + + var rc reflect.Value + switch { + case tConfig.ConvertibleTo(tIn): + rc = reflect.ValueOf(*c) + case tConfigPtr.ConvertibleTo(tIn): + rc = reflect.ValueOf(c) + } + + results := method.Func.Call([]reflect.Value{v, rc.Convert(tIn)}) + ifc := results[0].Convert(tError).Interface() + if ifc == nil { + return nil + } + return ifc.(error) +} diff --git a/vendor/github.com/elastic/go-ucfg/util.go b/vendor/github.com/elastic/go-ucfg/util.go new file mode 100644 index 00000000000..0e7080ab0a5 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/util.go @@ -0,0 +1,220 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +type tagOptions struct { + squash bool + ignore bool + cfgHandling configHandling +} + +// configHandling configures the operation to execute if we merge into a struct +// field that holds an unpacked config object. +type configHandling uint8 + +const ( + cfgDefaultHandling configHandling = iota + cfgMergeValues + cfgReplaceValue + cfgArrAppend + cfgArrPrepend +) + +var noTagOpts = tagOptions{} + +func parseTags(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + opts := tagOptions{} + for _, opt := range s[1:] { + switch opt { + case "squash", "inline": + opts.squash = true + case "ignore": + opts.ignore = true + case "merge": + opts.cfgHandling = cfgMergeValues + case "replace": + opts.cfgHandling = cfgReplaceValue + case "append": + opts.cfgHandling = cfgArrAppend + case "prepend": + opts.cfgHandling = cfgArrPrepend + } + } + return s[0], opts +} + +func fieldName(tagName, structName string) string { + if tagName != "" { + return tagName + } + return strings.ToLower(structName) +} + +func chaseValueInterfaces(v reflect.Value) reflect.Value { + for v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } + return v +} + +func chaseValuePointers(v reflect.Value) reflect.Value { + for v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + return v +} + +func chaseValue(v reflect.Value) reflect.Value { + for (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() { + v = v.Elem() + } + return v +} + +func chaseTypePointers(t reflect.Type) reflect.Type { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +// tryTConfig tries to convert input value into addressable Config by converting +// to *Config first. If value is convertible to Config, but not addressable a new +// value is allocated in order to guarantee returned value of type Config is +// addressable. Returns false if type value is not convertible to TConfig. +func tryTConfig(value reflect.Value) (reflect.Value, bool) { + v := chaseValue(value) + t := v.Type() + + if t == tConfig { + v := pointerize(tConfigPtr, tConfig, v) + return v.Elem(), true + } + + if !t.ConvertibleTo(tConfig) { + return reflect.Value{}, false + } + + v = pointerize(reflect.PtrTo(v.Type()), v.Type(), v) + if !v.Type().ConvertibleTo(tConfigPtr) { + return reflect.Value{}, false + } + + v = v.Convert(tConfigPtr) + return v.Elem(), true +} + +func pointerize(t, base reflect.Type, v reflect.Value) reflect.Value { + if t == base { + return v + } + + if t.Kind() == reflect.Interface { + return v + } + + for t != v.Type() { + if !v.CanAddr() { + tmp := reflect.New(v.Type()) + tmp.Elem().Set(v) + v = tmp + } else { + v = v.Addr() + } + } + return v +} + +func isInt(k reflect.Kind) bool { + switch k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return true + default: + return false + } +} + +func isUint(k reflect.Kind) bool { + switch k { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return true + default: + return false + } +} + +func isFloat(k reflect.Kind) bool { + switch k { + case reflect.Float32, reflect.Float64: + return true + default: + return false + } +} + +type fieldInfo struct { + name string + ftype reflect.Type + value reflect.Value + options *options + tagOptions tagOptions + validatorTags []validatorTag +} + +func accessField(structVal reflect.Value, fieldIdx int, opts *options) (fieldInfo, bool, Error) { + stField := structVal.Type().Field(fieldIdx) + + // ignore non exported fields + if rune, _ := utf8.DecodeRuneInString(stField.Name); !unicode.IsUpper(rune) { + return fieldInfo{}, true, nil + } + name, tagOpts := parseTags(stField.Tag.Get(opts.tag)) + if tagOpts.ignore { + return fieldInfo{}, true, nil + } + + // create new context, overwriting configValueHandling for all sub-operations + if tagOpts.cfgHandling != opts.configValueHandling { + tmp := &options{} + *tmp = *opts + tmp.configValueHandling = tagOpts.cfgHandling + opts = tmp + } + + validators, err := parseValidatorTags(stField.Tag.Get(opts.validatorTag)) + if err != nil { + return fieldInfo{}, false, raiseCritical(err, "") + } + + return fieldInfo{ + name: fieldName(name, stField.Name), + ftype: stField.Type, + value: structVal.Field(fieldIdx), + options: opts, + tagOptions: tagOpts, + validatorTags: validators, + }, false, nil +} diff --git a/vendor/github.com/elastic/go-ucfg/validator.go b/vendor/github.com/elastic/go-ucfg/validator.go new file mode 100644 index 00000000000..8a72abf3b33 --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/validator.go @@ -0,0 +1,461 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "time" +) + +// Validator interface provides additional validation support to Unpack. The +// Validate method will be executed for any type passed directly or indirectly to +// Unpack. +// +// If Validate fails with an error message, Unpack will add some +// context - like setting being accessed and file setting was read from - to the +// error message before returning the actual error. +type Validator interface { + Validate() error +} + +// ValidatorCallback is the type of optional validator tags to be registered via +// RegisterValidator. +type ValidatorCallback func(interface{}, string) error + +type validatorTag struct { + name string + cb ValidatorCallback + param string +} + +var ( + validators = map[string]ValidatorCallback{} +) + +func init() { + initRegisterValidator("nonzero", validateNonZero) + initRegisterValidator("positive", validatePositive) + initRegisterValidator("min", validateMin) + initRegisterValidator("max", validateMax) + initRegisterValidator("required", validateRequired) +} + +func initRegisterValidator(name string, cb ValidatorCallback) { + if err := RegisterValidator(name, cb); err != nil { + panic("Duplicate validator: " + name) + } +} + +// RegisterValidator adds a new validator option to the "validate" struct tag. +// The callback will be executed when unpacking into a struct field. +func RegisterValidator(name string, cb ValidatorCallback) error { + if _, exists := validators[name]; exists { + return ErrDuplicateValidator + } + + validators[name] = cb + return nil +} + +func parseValidatorTags(tag string) ([]validatorTag, error) { + if tag == "" { + return nil, nil + } + + lst := strings.Split(tag, ",") + if len(lst) == 0 { + return nil, nil + } + + tags := make([]validatorTag, 0, len(lst)) + for _, cfg := range lst { + v := strings.SplitN(cfg, "=", 2) + name := strings.Trim(v[0], " \t\r\n") + cb := validators[name] + if cb == nil { + return nil, fmt.Errorf("unknown validator '%v'", name) + } + + param := "" + if len(v) == 2 { + param = strings.Trim(v[1], " \t\r\n") + } + + tags = append(tags, validatorTag{name: name, cb: cb, param: param}) + } + + return tags, nil +} + +func tryValidate(val reflect.Value) error { + t := val.Type() + var validator Validator + + if (t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface) && val.IsNil() { + return nil + } + + if t.Implements(tValidator) { + validator = val.Interface().(Validator) + } else if reflect.PtrTo(t).Implements(tValidator) { + val = pointerize(reflect.PtrTo(t), t, val) + validator = val.Interface().(Validator) + } + + if validator == nil { + return nil + } + return validator.Validate() +} + +func runValidators(val interface{}, validators []validatorTag) error { + if validators == nil { + return nil + } + for _, tag := range validators { + if err := tag.cb(val, tag.param); err != nil { + return err + } + } + return nil +} + +func tryRecursiveValidate(val reflect.Value, opts *options, validators []validatorTag) error { + var curr interface{} + if val.IsValid() { + curr = val.Interface() + } + if err := runValidators(curr, validators); err != nil { + return err + } + if !val.IsValid() { + return nil + } + + t := val.Type() + if (t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface) && val.IsNil() { + return nil + } + + var err error + switch chaseValue(val).Kind() { + case reflect.Struct: + err = validateStruct(val, opts) + case reflect.Map: + err = validateMap(val, opts) + case reflect.Array, reflect.Slice: + err = validateArray(val, opts) + } + + if err != nil { + return err + } + return tryValidate(val) +} + +func validateStruct(val reflect.Value, opts *options) error { + val = chaseValue(val) + numField := val.NumField() + for i := 0; i < numField; i++ { + fInfo, skip, err := accessField(val, i, opts) + if err != nil { + return err + } + if skip { + continue + } + + if err := tryRecursiveValidate(fInfo.value, fInfo.options, fInfo.validatorTags); err != nil { + return err + } + } + return nil +} + +func validateMap(val reflect.Value, opts *options) error { + for _, key := range val.MapKeys() { + if err := tryRecursiveValidate(val.MapIndex(key), opts, nil); err != nil { + return err + } + } + return nil +} + +func validateArray(val reflect.Value, opts *options) error { + for i := 0; i < val.Len(); i++ { + if err := tryRecursiveValidate(val.Index(i), opts, nil); err != nil { + return err + } + } + return nil +} + +// validateNonZero implements the `nonzero` validation tag. +// If nonzero is set, the validator is only run if field is present in config. +// It checks for numbers and durations to be != 0, and for strings/arrays/slices +// not being empty. +func validateNonZero(v interface{}, name string) error { + if v == nil { + return nil + } + + if d, ok := v.(time.Duration); ok { + if d == 0 { + return ErrZeroValue + } + return nil + } + + val := chaseValue(reflect.ValueOf(v)) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if val.Int() != 0 { + return nil + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if val.Uint() != 0 { + return nil + } + case reflect.Float32, reflect.Float64: + if val.Float() != 0 { + return nil + } + default: + return validateNonEmpty(v, name) + } + + return ErrZeroValue +} + +func validatePositive(v interface{}, _ string) error { + if v == nil { + return nil + } + + if d, ok := v.(time.Duration); ok { + if d < 0 { + return ErrNegative + } + return nil + } + + val := reflect.ValueOf(v) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if val.Int() >= 0 { + return nil + } + case reflect.Float32, reflect.Float64: + if val.Float() >= 0 { + return nil + } + default: + return nil + } + + return ErrNegative +} + +func validateMin(v interface{}, param string) error { + if v == nil { + return nil + } + + if d, ok := v.(time.Duration); ok { + min, err := param2Duration(param) + if err != nil { + return err + } + + if min > d { + return fmt.Errorf("requires duration < %v", param) + } + return nil + } + + val := reflect.ValueOf(v) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + min, err := strconv.ParseInt(param, 0, 64) + if err != nil { + return err + } + if val.Int() >= min { + return nil + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + min, err := strconv.ParseUint(param, 0, 64) + if err != nil { + return err + } + if val.Uint() >= min { + return nil + } + case reflect.Float32, reflect.Float64: + min, err := strconv.ParseFloat(param, 64) + if err != nil { + return err + } + if val.Float() >= min { + return nil + } + default: + return nil + } + + return fmt.Errorf("requires value < %v", param) +} + +func validateMax(v interface{}, param string) error { + if v == nil { + return nil + } + + if d, ok := v.(time.Duration); ok { + max, err := param2Duration(param) + if err != nil { + return err + } + + if max < d { + return fmt.Errorf("requires duration > %v", param) + } + return nil + } + + val := reflect.ValueOf(v) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + max, err := strconv.ParseInt(param, 0, 64) + if err != nil { + return err + } + if val.Int() <= max { + return nil + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + max, err := strconv.ParseUint(param, 0, 64) + if err != nil { + return err + } + if val.Uint() <= max { + return nil + } + case reflect.Float32, reflect.Float64: + max, err := strconv.ParseFloat(param, 64) + if err != nil { + return err + } + if val.Float() <= max { + return nil + } + default: + return nil + } + + return fmt.Errorf("requires value > %v", param) +} + +// validateRequired implements the `required` validation tag. +// If a field is required, it must be present in the config. +// If field is a string, regex or slice its length must be > 0. +func validateRequired(v interface{}, name string) error { + if v == nil { + return ErrRequired + } + val := reflect.ValueOf(v) + if val.Kind() == reflect.Ptr && val.IsNil() { + return ErrRequired + } + if isInt(val.Kind()) || isUint(val.Kind()) || isFloat(val.Kind()) { + if err := validateNonZero(v, name); err != nil { + return ErrRequired + } + return nil + } + if err := validateNonEmptyWithAllowNil(v, name, false); err != nil { + return err + } + return nil +} + +func validateNonEmpty(v interface{}, name string) error { + return validateNonEmptyWithAllowNil(v, name, true) +} + +func validateNonEmptyWithAllowNil(v interface{}, _ string, allowNil bool) error { + if s, ok := v.(string); ok { + if s == "" { + return ErrStringEmpty + } + return nil + } + + if r, ok := v.(regexp.Regexp); ok { + if r.String() == "" { + return ErrRegexEmpty + } + return nil + } + + val := reflect.ValueOf(v) + if val.Kind() == reflect.Array || val.Kind() == reflect.Slice { + if val.IsNil() { + if allowNil { + return nil + } + return ErrRequired + } + if val.Len() == 0 { + return ErrArrayEmpty + } + return nil + } + if val.Kind() == reflect.Map { + if val.IsNil() { + if allowNil { + return nil + } + return ErrRequired + } + if val.Len() == 0 { + return ErrMapEmpty + } + return nil + } + + return nil +} + +func param2Duration(param string) (time.Duration, error) { + d, err := time.ParseDuration(param) + if err == nil { + return d, err + } + + tmp, floatErr := strconv.ParseFloat(param, 64) + if floatErr != nil { + return 0, err + } + + return time.Duration(tmp * float64(time.Second)), nil +} diff --git a/vendor/github.com/elastic/go-ucfg/variables.go b/vendor/github.com/elastic/go-ucfg/variables.go new file mode 100644 index 00000000000..791bee3d3eb --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/variables.go @@ -0,0 +1,552 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package ucfg + +import ( + "bytes" + "errors" + "fmt" + "strings" + + "github.com/elastic/go-ucfg/parse" +) + +type reference struct { + Path cfgPath +} + +type expansion struct { + left, right varEvaler + pathSep string +} + +type expansionSingle struct { + evaler varEvaler + pathSep string +} + +type expansionDefault struct{ expansion } +type expansionAlt struct{ expansion } +type expansionErr struct{ expansion } + +type splice struct { + pieces []varEvaler +} + +type varEvaler interface { + eval(cfg *Config, opts *options) (string, error) +} + +type constExp string + +type token struct { + typ tokenType + val string +} + +type parseState struct { + st int + isvar bool + op string + pieces [2][]varEvaler +} + +var ( + errUnterminatedBrace = errors.New("unterminated brace") + errInvalidType = errors.New("invalid type") + errEmptyPath = errors.New("empty path after expansion") +) + +type tokenType uint16 + +const ( + tokOpen tokenType = iota + tokClose + tokSep + tokString + + // parser state + stLeft = 0 + stRight = 1 + + opDefault = ":" + opAlternative = ":+" + opError = ":?" +) + +var ( + openToken = token{tokOpen, "${"} + closeToken = token{tokClose, "}"} + + sepDefToken = token{tokSep, opDefault} + sepAltToken = token{tokSep, opAlternative} + sepErrToken = token{tokSep, opError} +) + +func newReference(p cfgPath) *reference { + return &reference{p} +} + +func (r *reference) String() string { + return fmt.Sprintf("${%v}", r.Path) +} + +func (r *reference) resolveRef(cfg *Config, opts *options) (value, error) { + env := opts.env + + if ok := opts.activeFields.AddNew(r.Path.String()); !ok { + return nil, raiseCyclicErr(r.Path.String()) + } + + var err Error + + for { + var v value + cfg = cfgRoot(cfg) + if cfg == nil { + return nil, ErrMissing + } + + v, err = r.Path.GetValue(cfg, opts) + if err == nil { + if v == nil { + break + } + + return v, nil + } + + if len(env) == 0 { + break + } + + cfg = env[len(env)-1] + env = env[:len(env)-1] + } + + return nil, err +} + +func (r *reference) resolveEnv(cfg *Config, opts *options) (string, parse.Config, error) { + var err error + + if len(opts.resolvers) > 0 { + key := r.Path.String() + for i := len(opts.resolvers) - 1; i >= 0; i-- { + var v string + var cfg parse.Config + resolver := opts.resolvers[i] + v, cfg, err = resolver(key) + if err == nil { + return v, cfg, nil + } + } + } + + return "", parse.DefaultConfig, err +} + +func (r *reference) resolve(cfg *Config, opts *options) (value, error) { + v, err := r.resolveRef(cfg, opts) + if v != nil || criticalResolveError(err) { + return v, err + } + + previousErr := err + + s, _, err := r.resolveEnv(cfg, opts) + if err != nil { + // TODO(ph): Not everything is an Error, will do some cleanup in another PR. + if v, ok := previousErr.(Error); ok { + if v.Reason() == ErrCyclicReference { + return nil, previousErr + } + } + return nil, err + } + + if s == "" { + return nil, nil + } + + return newString(context{field: r.Path.String()}, nil, s), nil +} + +func (r *reference) eval(cfg *Config, opts *options) (string, error) { + v, err := r.resolve(cfg, opts) + if err != nil { + return "", err + } + if v == nil { + return "", fmt.Errorf("can not resolve reference: %v", r.Path) + } + return v.toString(opts) +} + +func (s constExp) eval(*Config, *options) (string, error) { + return string(s), nil +} + +func (s *splice) String() string { + return fmt.Sprintf("%v", s.pieces) +} + +func (s *splice) eval(cfg *Config, opts *options) (string, error) { + buf := bytes.NewBuffer(nil) + for _, p := range s.pieces { + s, err := p.eval(cfg, opts) + if err != nil { + return "", err + } + buf.WriteString(s) + } + return buf.String(), nil +} + +func (e *expansion) String() string { + return fmt.Sprintf("${%v:%v}", e.left, e.right) +} + +func (e *expansionSingle) String() string { + return fmt.Sprintf("${%v}", e.evaler) +} + +func (e *expansionSingle) eval(cfg *Config, opts *options) (string, error) { + path, err := e.evaler.eval(cfg, opts) + if err != nil { + return "", err + } + + ref := newReference(parsePath(path, e.pathSep)) + return ref.eval(cfg, opts) +} + +func (e *expansionDefault) eval(cfg *Config, opts *options) (string, error) { + path, err := e.left.eval(cfg, opts) + if err != nil || path == "" { + return e.right.eval(cfg, opts) + } + ref := newReference(parsePath(path, e.pathSep)) + v, err := ref.eval(cfg, opts) + if err != nil || v == "" { + return e.right.eval(cfg, opts) + } + return v, err +} + +func (e *expansionAlt) eval(cfg *Config, opts *options) (string, error) { + path, err := e.left.eval(cfg, opts) + if err != nil || path == "" { + return "", nil + } + + ref := newReference(parsePath(path, e.pathSep)) + tmp, err := ref.resolve(cfg, opts) + if err != nil || tmp == nil { + return "", nil + } + + return e.right.eval(cfg, opts) +} + +func (e *expansionErr) eval(cfg *Config, opts *options) (string, error) { + path, err := e.left.eval(cfg, opts) + if err == nil && path != "" { + ref := newReference(parsePath(path, e.pathSep)) + str, err := ref.eval(cfg, opts) + if err == nil && str != "" { + return str, nil + } + } + + errStr, err := e.right.eval(cfg, opts) + if err != nil { + return "", err + } + return "", errors.New(errStr) +} + +func (st parseState) finalize(pathSep string) (varEvaler, error) { + if !st.isvar { + return nil, errors.New("fatal: processing non-variable state") + } + if len(st.pieces[stLeft]) == 0 { + return nil, errors.New("empty expansion") + } + + if st.st == stLeft { + pieces := st.pieces[stLeft] + + if len(pieces) == 0 { + return constExp(""), nil + } + + if len(pieces) == 1 { + if str, ok := pieces[0].(constExp); ok { + return newReference(parsePath(string(str), pathSep)), nil + } + } + + return &expansionSingle{&splice{pieces}, pathSep}, nil + } + + extract := func(pieces []varEvaler) varEvaler { + switch len(pieces) { + case 0: + return constExp("") + case 1: + return pieces[0] + default: + return &splice{pieces} + } + } + + left := extract(st.pieces[stLeft]) + right := extract(st.pieces[stRight]) + return makeOpExpansion(left, right, st.op, pathSep), nil +} + +func makeOpExpansion(l, r varEvaler, op, pathSep string) varEvaler { + exp := expansion{l, r, pathSep} + switch op { + case opDefault: + return &expansionDefault{exp} + case opAlternative: + return &expansionAlt{exp} + case opError: + return &expansionErr{exp} + } + panic(fmt.Sprintf("Unknown operator: %v", op)) +} + +func parseSplice(in, pathSep string) (varEvaler, error) { + lex, errs := lexer(in) + drainLex := func() { + for range lex { + } + } + + // drain lexer on return so go-routine won't leak + defer drainLex() + + pieces, perr := parseVarExp(lex, pathSep) + if perr != nil { + return nil, perr + } + + // check for lexer errors + select { + case err := <-errs: + if err != nil { + return nil, err + } + default: + } + + // return parser result + return pieces, perr +} + +func lexer(in string) (<-chan token, <-chan error) { + lex := make(chan token, 1) + errors := make(chan error, 1) + + go func() { + off := 0 + content := in + + defer func() { + if len(content) > 0 { + lex <- token{tokString, content} + } + close(lex) + close(errors) + }() + + strToken := func(s string) { + if s != "" { + lex <- token{tokString, s} + } + } + + varcount := 0 + for len(content) > 0 { + idx := -1 + if varcount == 0 { + idx = strings.IndexAny(content[off:], "$") + } else { + idx = strings.IndexAny(content[off:], "$:}") + } + if idx < 0 { + return + } + + idx += off + off = idx + 1 + switch content[idx] { + case ':': + if len(content) <= off { // found ':' at end of string + return + } + + strToken(content[:idx]) + switch content[off] { + case '+': + off++ + lex <- sepAltToken + case '?': + off++ + lex <- sepErrToken + default: + lex <- sepDefToken + } + + case '}': + strToken(content[:idx]) + lex <- closeToken + varcount-- + + case '$': + if len(content) <= off { // found '$' at end of string + return + } + + switch content[off] { + case '{': // start variable + strToken(content[:idx]) + lex <- openToken + off++ + varcount++ + case '$', '}': // escape $} and $$ + content = content[:idx] + content[off:] + continue + default: + continue + } + } + + content = content[off:] + off = 0 + } + }() + + return lex, errors +} + +func parseVarExp(lex <-chan token, pathSep string) (varEvaler, error) { + stack := []parseState{{st: stLeft}} + + // parser loop + for tok := range lex { + switch tok.typ { + case tokOpen: + stack = append(stack, parseState{st: stLeft, isvar: true}) + case tokClose: + // finalize and pop state + piece, err := stack[len(stack)-1].finalize(pathSep) + stack = stack[:len(stack)-1] + if err != nil { + return nil, err + } + + // append result top stacked state + st := &stack[len(stack)-1] + st.pieces[st.st] = append(st.pieces[st.st], piece) + + case tokSep: // switch from left to right + st := &stack[len(stack)-1] + if !st.isvar { + return nil, errors.New("default separator not within expansion") + } + if st.st == stRight { + st.pieces[st.st] = addString(st.pieces[st.st], tok.val) + } else { + // switch to 'right' + st.st = stRight + st.op = tok.val + } + + case tokString: + // append raw string + st := &stack[len(stack)-1] + st.pieces[st.st] = addString(st.pieces[st.st], tok.val) + } + } + + // validate and return final state + if len(stack) > 1 { + return nil, errors.New("missing '}'") + } + if len(stack) == 0 { + return nil, errors.New("fatal: expansion parse state empty") + } + + result := stack[0].pieces[stLeft] + if len(result) == 1 { + return result[0], nil + } + return &splice{result}, nil +} + +func cfgRoot(cfg *Config) *Config { + if cfg == nil { + return nil + } + + for { + p := cfg.Parent() + if p == nil { + return cfg + } + + cfg = p + } +} + +func addString(ps []varEvaler, s string) []varEvaler { + if len(ps) == 0 { + return []varEvaler{constExp(s)} + } + + last := ps[len(ps)-1] + c, ok := last.(constExp) + if !ok { + return append(ps, constExp(s)) + } + + ps[len(ps)-1] = constExp(string(c) + s) + return ps +} + +func (t tokenType) String() string { + switch t { + case tokOpen: + return "" + case tokClose: + return "" + case tokSep: + return "" + case tokString: + return "" + } + return "" +} + +func (t token) String() string { + return fmt.Sprintf("(%v, %v)", t.typ, t.val) +} diff --git a/vendor/github.com/elastic/go-ucfg/yaml/yaml.go b/vendor/github.com/elastic/go-ucfg/yaml/yaml.go new file mode 100644 index 00000000000..772423f99bb --- /dev/null +++ b/vendor/github.com/elastic/go-ucfg/yaml/yaml.go @@ -0,0 +1,49 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package yaml + +import ( + "io/ioutil" + + "gopkg.in/yaml.v2" + + "github.com/elastic/go-ucfg" +) + +// NewConfig creates a new configuration object from the YAML string passed via in. +func NewConfig(in []byte, opts ...ucfg.Option) (*ucfg.Config, error) { + var m interface{} + if err := yaml.Unmarshal(in, &m); err != nil { + return nil, err + } + + return ucfg.NewFrom(m, opts...) +} + +// NewConfigWithFile loads a new configuration object from an external YAML file. +func NewConfigWithFile(name string, opts ...ucfg.Option) (*ucfg.Config, error) { + input, err := ioutil.ReadFile(name) + if err != nil { + return nil, err + } + + opts = append([]ucfg.Option{ + ucfg.MetaData(ucfg.Meta{Source: name}), + }, opts...) + return NewConfig(input, opts...) +} diff --git a/vendor/github.com/elastic/package-registry/LICENSE.txt b/vendor/github.com/elastic/package-registry/LICENSE.txt new file mode 100644 index 00000000000..7376ffc3ff1 --- /dev/null +++ b/vendor/github.com/elastic/package-registry/LICENSE.txt @@ -0,0 +1,223 @@ +ELASTIC LICENSE AGREEMENT + +PLEASE READ CAREFULLY THIS ELASTIC LICENSE AGREEMENT (THIS "AGREEMENT"), WHICH +CONSTITUTES A LEGALLY BINDING AGREEMENT AND GOVERNS ALL OF YOUR USE OF ALL OF +THE ELASTIC SOFTWARE WITH WHICH THIS AGREEMENT IS INCLUDED ("ELASTIC SOFTWARE") +THAT IS PROVIDED IN OBJECT CODE FORMAT, AND, IN ACCORDANCE WITH SECTION 2 BELOW, +CERTAIN OF THE ELASTIC SOFTWARE THAT IS PROVIDED IN SOURCE CODE FORMAT. BY +INSTALLING OR USING ANY OF THE ELASTIC SOFTWARE GOVERNED BY THIS AGREEMENT, YOU +ARE ASSENTING TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE +WITH SUCH TERMS AND CONDITIONS, YOU MAY NOT INSTALL OR USE THE ELASTIC SOFTWARE +GOVERNED BY THIS AGREEMENT. IF YOU ARE INSTALLING OR USING THE SOFTWARE ON +BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU HAVE THE ACTUAL +AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT ON BEHALF OF +SUCH ENTITY. + +Posted Date: April 20, 2018 + +This Agreement is entered into by and between Elasticsearch BV ("Elastic") and +You, or the legal entity on behalf of whom You are acting (as applicable, +"You"). + +1. OBJECT CODE END USER LICENSES, RESTRICTIONS AND THIRD PARTY OPEN SOURCE +SOFTWARE + + 1.1 Object Code End User License. Subject to the terms and conditions of + Section 1.2 of this Agreement, Elastic hereby grants to You, AT NO CHARGE and + for so long as you are not in breach of any provision of this Agreement, a + License to the Basic Features and Functions of the Elastic Software. + + 1.2 Reservation of Rights; Restrictions. As between Elastic and You, Elastic + and its licensors own all right, title and interest in and to the Elastic + Software, and except as expressly set forth in Sections 1.1, and 2.1 of this + Agreement, no other license to the Elastic Software is granted to You under + this Agreement, by implication, estoppel or otherwise. You agree not to: (i) + reverse engineer or decompile, decrypt, disassemble or otherwise reduce any + Elastic Software provided to You in Object Code, or any portion thereof, to + Source Code, except and only to the extent any such restriction is prohibited + by applicable law, (ii) except as expressly permitted in this Agreement, + prepare derivative works from, modify, copy or use the Elastic Software Object + Code or the Commercial Software Source Code in any manner; (iii) except as + expressly permitted in Section 1.1 above, transfer, sell, rent, lease, + distribute, sublicense, loan or otherwise transfer, Elastic Software Object + Code, in whole or in part, to any third party; (iv) use Elastic Software + Object Code for providing time-sharing services, any software-as-a-service, + service bureau services or as part of an application services provider or + other service offering (collectively, "SaaS Offering") where obtaining access + to the Elastic Software or the features and functions of the Elastic Software + is a primary reason or substantial motivation for users of the SaaS Offering + to access and/or use the SaaS Offering ("Prohibited SaaS Offering"); (v) + circumvent the limitations on use of Elastic Software provided to You in + Object Code format that are imposed or preserved by any License Key, or (vi) + alter or remove any Marks and Notices in the Elastic Software. If You have any + question as to whether a specific SaaS Offering constitutes a Prohibited SaaS + Offering, or are interested in obtaining Elastic's permission to engage in + commercial or non-commercial distribution of the Elastic Software, please + contact elastic_license@elastic.co. + + 1.3 Third Party Open Source Software. The Commercial Software may contain or + be provided with third party open source libraries, components, utilities and + other open source software (collectively, "Open Source Software"), which Open + Source Software may have applicable license terms as identified on a website + designated by Elastic. Notwithstanding anything to the contrary herein, use of + the Open Source Software shall be subject to the license terms and conditions + applicable to such Open Source Software, to the extent required by the + applicable licensor (which terms shall not restrict the license rights granted + to You hereunder, but may contain additional rights). To the extent any + condition of this Agreement conflicts with any license to the Open Source + Software, the Open Source Software license will govern with respect to such + Open Source Software only. Elastic may also separately provide you with + certain open source software that is licensed by Elastic. Your use of such + Elastic open source software will not be governed by this Agreement, but by + the applicable open source license terms. + +2. COMMERCIAL SOFTWARE SOURCE CODE + + 2.1 Limited License. Subject to the terms and conditions of Section 2.2 of + this Agreement, Elastic hereby grants to You, AT NO CHARGE and for so long as + you are not in breach of any provision of this Agreement, a limited, + non-exclusive, non-transferable, fully paid up royalty free right and license + to the Commercial Software in Source Code format, without the right to grant + or authorize sublicenses, to prepare Derivative Works of the Commercial + Software, provided You (i) do not hack the licensing mechanism, or otherwise + circumvent the intended limitations on the use of Elastic Software to enable + features other than Basic Features and Functions or those features You are + entitled to as part of a Subscription, and (ii) use the resulting object code + only for reasonable testing purposes. + + 2.2 Restrictions. Nothing in Section 2.1 grants You the right to (i) use the + Commercial Software Source Code other than in accordance with Section 2.1 + above, (ii) use a Derivative Work of the Commercial Software outside of a + Non-production Environment, in any production capacity, on a temporary or + permanent basis, or (iii) transfer, sell, rent, lease, distribute, sublicense, + loan or otherwise make available the Commercial Software Source Code, in whole + or in part, to any third party. Notwithstanding the foregoing, You may + maintain a copy of the repository in which the Source Code of the Commercial + Software resides and that copy may be publicly accessible, provided that you + include this Agreement with Your copy of the repository. + +3. TERMINATION + + 3.1 Termination. This Agreement will automatically terminate, whether or not + You receive notice of such Termination from Elastic, if You breach any of its + provisions. + + 3.2 Post Termination. Upon any termination of this Agreement, for any reason, + You shall promptly cease the use of the Elastic Software in Object Code format + and cease use of the Commercial Software in Source Code format. For the + avoidance of doubt, termination of this Agreement will not affect Your right + to use Elastic Software, in either Object Code or Source Code formats, made + available under the Apache License Version 2.0. + + 3.3 Survival. Sections 1.2, 2.2. 3.3, 4 and 5 shall survive any termination or + expiration of this Agreement. + +4. DISCLAIMER OF WARRANTIES AND LIMITATION OF LIABILITY + + 4.1 Disclaimer of Warranties. TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE + LAW, THE ELASTIC SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, + AND ELASTIC AND ITS LICENSORS MAKE NO WARRANTIES WHETHER EXPRESSED, IMPLIED OR + STATUTORY REGARDING OR RELATING TO THE ELASTIC SOFTWARE. TO THE MAXIMUM EXTENT + PERMITTED UNDER APPLICABLE LAW, ELASTIC AND ITS LICENSORS SPECIFICALLY + DISCLAIM ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NON-INFRINGEMENT WITH RESPECT TO THE ELASTIC SOFTWARE, AND WITH + RESPECT TO THE USE OF THE FOREGOING. FURTHER, ELASTIC DOES NOT WARRANT RESULTS + OF USE OR THAT THE ELASTIC SOFTWARE WILL BE ERROR FREE OR THAT THE USE OF THE + ELASTIC SOFTWARE WILL BE UNINTERRUPTED. + + 4.2 Limitation of Liability. IN NO EVENT SHALL ELASTIC OR ITS LICENSORS BE + LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT OR INDIRECT DAMAGES, + INCLUDING, WITHOUT LIMITATION, FOR ANY LOSS OF PROFITS, LOSS OF USE, BUSINESS + INTERRUPTION, LOSS OF DATA, COST OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY + SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, IN CONNECTION WITH + OR ARISING OUT OF THE USE OR INABILITY TO USE THE ELASTIC SOFTWARE, OR THE + PERFORMANCE OF OR FAILURE TO PERFORM THIS AGREEMENT, WHETHER ALLEGED AS A + BREACH OF CONTRACT OR TORTIOUS CONDUCT, INCLUDING NEGLIGENCE, EVEN IF ELASTIC + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +5. MISCELLANEOUS + + This Agreement completely and exclusively states the entire agreement of the + parties regarding the subject matter herein, and it supersedes, and its terms + govern, all prior proposals, agreements, or other communications between the + parties, oral or written, regarding such subject matter. This Agreement may be + modified by Elastic from time to time, and any such modifications will be + effective upon the "Posted Date" set forth at the top of the modified + Agreement. If any provision hereof is held unenforceable, this Agreement will + continue without said provision and be interpreted to reflect the original + intent of the parties. This Agreement and any non-contractual obligation + arising out of or in connection with it, is governed exclusively by Dutch law. + This Agreement shall not be governed by the 1980 UN Convention on Contracts + for the International Sale of Goods. All disputes arising out of or in + connection with this Agreement, including its existence and validity, shall be + resolved by the courts with jurisdiction in Amsterdam, The Netherlands, except + where mandatory law provides for the courts at another location in The + Netherlands to have jurisdiction. The parties hereby irrevocably waive any and + all claims and defenses either might otherwise have in any such action or + proceeding in any of such courts based upon any alleged lack of personal + jurisdiction, improper venue, forum non conveniens or any similar claim or + defense. A breach or threatened breach, by You of Section 2 may cause + irreparable harm for which damages at law may not provide adequate relief, and + therefore Elastic shall be entitled to seek injunctive relief without being + required to post a bond. You may not assign this Agreement (including by + operation of law in connection with a merger or acquisition), in whole or in + part to any third party without the prior written consent of Elastic, which + may be withheld or granted by Elastic in its sole and absolute discretion. + Any assignment in violation of the preceding sentence is void. Notices to + Elastic may also be sent to legal@elastic.co. + +6. DEFINITIONS + + The following terms have the meanings ascribed: + + 6.1 "Affiliate" means, with respect to a party, any entity that controls, is + controlled by, or which is under common control with, such party, where + "control" means ownership of at least fifty percent (50%) of the outstanding + voting shares of the entity, or the contractual right to establish policy for, + and manage the operations of, the entity. + + 6.2 "Basic Features and Functions" means those features and functions of the + Elastic Software that are eligible for use under a Basic license, as set forth + at https://www.elastic.co/subscriptions, as may be modified by Elastic from + time to time. + + 6.3 "Commercial Software" means the Elastic Software Source Code in any file + containing a header stating the contents are subject to the Elastic License or + which is contained in the repository folder labeled "x-pack", unless a LICENSE + file present in the directory subtree declares a different license. + + 6.4 "Derivative Work of the Commercial Software" means, for purposes of this + Agreement, any modification(s) or enhancement(s) to the Commercial Software, + which represent, as a whole, an original work of authorship. + + 6.5 "License" means a limited, non-exclusive, non-transferable, fully paid up, + royalty free, right and license, without the right to grant or authorize + sublicenses, solely for Your internal business operations to (i) install and + use the applicable Features and Functions of the Elastic Software in Object + Code, and (ii) permit Contractors and Your Affiliates to use the Elastic + software as set forth in (i) above, provided that such use by Contractors must + be solely for Your benefit and/or the benefit of Your Affiliates, and You + shall be responsible for all acts and omissions of such Contractors and + Affiliates in connection with their use of the Elastic software that are + contrary to the terms and conditions of this Agreement. + + 6.6 "License Key" means a sequence of bytes, including but not limited to a + JSON blob, that is used to enable certain features and functions of the + Elastic Software. + + 6.7 "Marks and Notices" means all Elastic trademarks, trade names, logos and + notices present on the Documentation as originally provided by Elastic. + + 6.8 "Non-production Environment" means an environment for development, testing + or quality assurance, where software is not used for production purposes. + + 6.9 "Object Code" means any form resulting from mechanical transformation or + translation of Source Code form, including but not limited to compiled object + code, generated documentation, and conversions to other media types. + + 6.10 "Source Code" means the preferred form of computer software for making + modifications, including but not limited to software source code, + documentation source, and configuration files. + + 6.11 "Subscription" means the right to receive Support Services and a License + to the Commercial Software. diff --git a/vendor/github.com/elastic/package-registry/NOTICE.txt b/vendor/github.com/elastic/package-registry/NOTICE.txt new file mode 100644 index 00000000000..b4693714b5e --- /dev/null +++ b/vendor/github.com/elastic/package-registry/NOTICE.txt @@ -0,0 +1,5 @@ +Elastic package-registry +Copyright 2017-2019 Elasticsearch B.V. + +This product includes software developed at +Elasticsearch, B.V. (https://www.elastic.co/). diff --git a/vendor/github.com/elastic/package-registry/util/dataset.go b/vendor/github.com/elastic/package-registry/util/dataset.go new file mode 100644 index 00000000000..dc7966f71c3 --- /dev/null +++ b/vendor/github.com/elastic/package-registry/util/dataset.go @@ -0,0 +1,210 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package util + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + yamlv2 "gopkg.in/yaml.v2" + + ucfg "github.com/elastic/go-ucfg" + "github.com/elastic/go-ucfg/yaml" +) + +const ( + DirIngestPipeline = "ingest-pipeline" +) + +type DataSet struct { + ID string `config:"id" json:"id,omitempty" yaml:"id,omitempty"` + Title string `config:"title" json:"title" validate:"required"` + Release string `config:"release" json:"release"` + Type string `config:"type" json:"type" validate:"required"` + IngestPipeline string `config:"ingest_pipeline,omitempty" config:"ingest_pipeline" json:"ingest_pipeline,omitempty" yaml:"ingest_pipeline,omitempty"` + Streams []Stream `config:"streams" json:"streams,omitempty" yaml:"streams,omitempty" validate:"required"` + Package string `json:"package,omitempty" yaml:"package,omitempty"` + + // Generated fields + Path string `json:"path,omitempty" yaml:"path,omitempty"` + + // Local path to the package dir + BasePath string `json:"-" yaml:"-"` +} + +type Input struct { + Type string `config:"type" json:"type" validate:"required"` + Vars []Variable `config:"vars" json:"vars,omitempty" yaml:"vars,omitempty"` + Title string `config:"title" json:"title,omitempty" yaml:"title,omitempty"` + Description string `config:"description" json:"description,omitempty" yaml:"description,omitempty"` + Streams []Stream `config:"streams" json:"streams,omitempty" yaml:"streams,omitempty"` +} + +type Stream struct { + Input string `config:"input" json:"input" validate:"required"` + Vars []Variable `config:"vars" json:"vars,omitempty" yaml:"vars,omitempty"` + Dataset string `config:"dataset" json:"dataset,omitempty" yaml:"dataset,omitempty"` + // TODO: This might cause issues when consuming the json as the key contains . (had been an issue in the past if I remember correctly) + TemplatePath string `config:"template_path" json:"template_path,omitempty" yaml:"template_path,omitempty"` + TemplateContent string `json:"template,omitempty" yaml:"template,omitempty"` // This is always generated in the json output + Title string `config:"title" json:"title,omitempty" yaml:"title,omitempty"` + Description string `config:"description" json:"description,omitempty" yaml:"description,omitempty"` + Enabled *bool `config:"enabled" json:"enabled,omitempty" yaml:"enabled,omitempty"` +} + +type Variable struct { + Name string `config:"name" json:"name" yaml:"name"` + Type string `config:"type" json:"type" yaml:"type"` + Title string `config:"title" json:"title,omitempty" yaml:"title,omitempty"` + Description string `config:"description" json:"description,omitempty" yaml:"description,omitempty"` + Multi bool `config:"multi" json:"multi" yaml:"multi"` + Required bool `config:"required" json:"required" yaml:"required"` + ShowUser bool `config:"show_user" json:"show_user" yaml:"show_user"` + Default interface{} `config:"default" json:"default,omitempty" yaml:"default,omitempty"` + Os *Os `config:"os" json:"os,omitempty" yaml:"os,omitempty"` +} + +type Os struct { + Darwin interface{} `config:"darwin" json:"darwin,omitempty" yaml:"darwin,omitempty"` + Windows interface{} `config:"windows" json:"windows,omitempty" yaml:"windows,omitempty"` +} + +func NewDataset(basePath string, p *Package) (*DataSet, error) { + + // Check if manifest exists + manifestPath := filepath.Join(basePath, "manifest.yml") + _, err := os.Stat(manifestPath) + if err != nil && os.IsNotExist(err) { + return nil, errors.Wrapf(err, "manifest does not exist for package: %s", p.BasePath) + } + + datasetPath := filepath.Base(basePath) + + manifest, err := yaml.NewConfigWithFile(manifestPath, ucfg.PathSep(".")) + if err != nil { + return nil, errors.Wrapf(err, "error creating new manifest config %s", manifestPath) + } + var d = &DataSet{ + Package: p.Name, + // This is the name of the directory of the dataset + Path: datasetPath, + BasePath: basePath, + } + + // go-ucfg automatically calls the `Validate` method on the Dataset object here + err = manifest.Unpack(d) + if err != nil { + return nil, errors.Wrapf(err, "error building dataset (path: %s) in package: %s", datasetPath, p.Name) + } + + // if id is not set, {package}.{datasetPath} is the default + if d.ID == "" { + d.ID = p.Name + "." + datasetPath + } + + if d.Release == "" { + d.Release = DefaultRelease + } + + // Default for the enabled flags is true. + trueValue := true + for i, _ := range d.Streams { + if d.Streams[i].Enabled == nil { + d.Streams[i].Enabled = &trueValue + } + } + + if !IsValidRelase(d.Release) { + return nil, fmt.Errorf("invalid release: %s", d.Release) + } + + return d, nil +} + +func (d *DataSet) Validate() error { + pipelineDir := filepath.Join(d.BasePath, "elasticsearch", DirIngestPipeline) + paths, err := filepath.Glob(filepath.Join(pipelineDir, "*")) + if err != nil { + return err + } + + if strings.Contains(d.ID, "-") { + return fmt.Errorf("dataset name is not allowed to contain `-`: %s", d.ID) + } + + if d.IngestPipeline == "" { + // Check that no ingest pipeline exists in the directory except default + for _, path := range paths { + if filepath.Base(path) == "default.json" || filepath.Base(path) == "default.yml" { + d.IngestPipeline = "default" + break + } + } + } + + if d.IngestPipeline == "" && len(paths) > 0 { + return fmt.Errorf("unused pipelines in the package (dataSetID: %s): %s", d.ID, strings.Join(paths, ",")) + } + + // In case an ingest pipeline is set, check if it is around + if d.IngestPipeline != "" { + var validFound bool + + jsonPipelinePath := filepath.Join(pipelineDir, d.IngestPipeline+".json") + _, errJSON := os.Stat(jsonPipelinePath) + if errJSON != nil && !os.IsNotExist(errJSON) { + return errors.Wrapf(errJSON, "stat ingest pipeline JSON file failed (path: %s)", jsonPipelinePath) + } + if !os.IsNotExist(errJSON) { + err = validateIngestPipelineFile(jsonPipelinePath) + if err != nil { + return errors.Wrapf(err, "validating ingest pipeline JSON file failed (path: %s)", jsonPipelinePath) + } + validFound = true + } + + yamlPipelinePath := filepath.Join(pipelineDir, d.IngestPipeline+".yml") + _, errYAML := os.Stat(yamlPipelinePath) + if errYAML != nil && !os.IsNotExist(errYAML) { + return errors.Wrapf(errYAML, "stat ingest pipeline YAML file failed (path: %s)", jsonPipelinePath) + } + if !os.IsNotExist(errYAML) { + err = validateIngestPipelineFile(yamlPipelinePath) + if err != nil { + return errors.Wrapf(err, "validating ingest pipeline YAML file failed (path: %s)", jsonPipelinePath) + } + validFound = true + } + + if !validFound { + return fmt.Errorf("defined ingest_pipeline does not exist: %s", pipelineDir+d.IngestPipeline) + } + } + return nil +} + +func validateIngestPipelineFile(pipelinePath string) error { + f, err := ioutil.ReadFile(pipelinePath) + if err != nil { + return errors.Wrapf(err, "reading ingest pipeline file failed (path: %s)", pipelinePath) + } + ext := filepath.Ext(pipelinePath) + + var m map[string]interface{} + switch ext { + case ".json": + err = json.Unmarshal(f, &m) + case ".yml": + err = yamlv2.Unmarshal(f, &m) + default: + return fmt.Errorf("unsupported pipeline extension (path: %s, ext: %s)", pipelinePath, ext) + } + return err +} diff --git a/vendor/github.com/elastic/package-registry/util/helper.go b/vendor/github.com/elastic/package-registry/util/helper.go new file mode 100644 index 00000000000..a136f093341 --- /dev/null +++ b/vendor/github.com/elastic/package-registry/util/helper.go @@ -0,0 +1,25 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package util + +const ( + ReleaseExperimental = "experimental" + ReleaseBeta = "beta" + ReleaseGa = "ga" + + // Default release if no release is configured + DefaultRelease = ReleaseExperimental +) + +var ReleaseTypes = map[string]interface{}{ + ReleaseExperimental: nil, + ReleaseBeta: nil, + ReleaseGa: nil, +} + +func IsValidRelase(release string) bool { + _, exists := ReleaseTypes[release] + return exists +} diff --git a/vendor/github.com/elastic/package-registry/util/package.go b/vendor/github.com/elastic/package-registry/util/package.go new file mode 100644 index 00000000000..e54923a1520 --- /dev/null +++ b/vendor/github.com/elastic/package-registry/util/package.go @@ -0,0 +1,392 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package util + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + + "github.com/pkg/errors" + + "github.com/blang/semver" + + ucfg "github.com/elastic/go-ucfg" + "github.com/elastic/go-ucfg/yaml" +) + +const defaultType = "integration" + +var CategoryTitles = map[string]string{ + "logs": "Logs", + "metrics": "Metrics", + "security": "Security", +} + +type Package struct { + FormatVersion string `config:"format_version" json:"format_version" yaml:"format_version"` + + Name string `config:"name" json:"name"` + Title *string `config:"title,omitempty" json:"title,omitempty" yaml:"title,omitempty"` + Version string `config:"version" json:"version"` + Readme *string `config:"readme,omitempty" json:"readme,omitempty" yaml:"readme,omitempty"` + License string `config:"license,omitempty" json:"license,omitempty" yaml:"license,omitempty"` + versionSemVer semver.Version + Description string `config:"description" json:"description"` + Type string `config:"type" json:"type"` + Categories []string `config:"categories" json:"categories"` + Release string `config:"release,omitempty" json:"release,omitempty"` + Removable bool `config:"removable" json:"removable"` + Requirement Requirement `config:"requirement" json:"requirement"` + Screenshots []Image `config:"screenshots,omitempty" json:"screenshots,omitempty" yaml:"screenshots,omitempty"` + Icons []Image `config:"icons,omitempty" json:"icons,omitempty" yaml:"icons,omitempty"` + Assets []string `config:"assets,omitempty" json:"assets,omitempty" yaml:"assets,omitempty"` + Internal bool `config:"internal,omitempty" json:"internal,omitempty" yaml:"internal,omitempty"` + DataSets []*DataSet `config:"datasets,omitempty" json:"datasets,omitempty" yaml:"datasets,omitempty"` + Datasources []Datasource `config:"datasources,omitempty" json:"datasources,omitempty" yaml:"datasources,omitempty"` + Download string `json:"download" yaml:"download,omitempty"` + Path string `json:"path" yaml:"path,omitempty"` + + // Local path to the package dir + BasePath string `json:"-" yaml:"-"` +} + +type Datasource struct { + Name string `config:"name" json:"name" validate:"required"` + Title string `config:"title" json:"title" validate:"required"` + Description string `config:"description" json:"description" validate:"required"` + Solution string `config:"solution" json:"solution,omitempty" yaml:"solution,omitempty"` + Inputs []Input `config:"inputs" json:"inputs"` + Multiple *bool `config:"multiple" json:"multiple,omitempty" yaml:"multiple,omitempty"` +} + +type Requirement struct { + Kibana ProductRequirement `config:"kibana" json:"kibana,omitempty" yaml:"kibana"` + Elasticsearch ProductRequirement `config:"elasticsearch" json:"elasticsearch,omitempty" yaml:"elasticsearch"` +} + +type ProductRequirement struct { + Versions string `config:"versions,omitempty" json:"versions,omitempty" yaml:"versions,omitempty"` + semVerRange semver.Range +} + +type Version struct { + Min string `config:"min,omitempty" json:"min,omitempty"` + Max string `config:"max,omitempty" json:"max,omitempty"` +} + +type Image struct { + Src string `config:"src" json:"src" validate:"required"` + Title string `config:"title" json:"title,omitempty"` + Size string `config:"size" json:"size,omitempty"` + Type string `config:"type" json:"type,omitempty"` +} + +func (i Image) getPath(p *Package) string { + return path.Join("/package", p.Name, p.Version, i.Src) +} + +// NewPackage creates a new package instances based on the given base path. +// The path passed goes to the root of the package where the manifest.yml is. +func NewPackage(basePath string) (*Package, error) { + + manifest, err := yaml.NewConfigWithFile(filepath.Join(basePath, "manifest.yml"), ucfg.PathSep(".")) + if err != nil { + return nil, err + } + + var p = &Package{ + BasePath: basePath, + Removable: true, + } + err = manifest.Unpack(p) + if err != nil { + return nil, err + } + + // Default for the multiple flags is true. + trueValue := true + for i, _ := range p.Datasources { + if p.Datasources[i].Multiple == nil { + p.Datasources[i].Multiple = &trueValue + } + } + if p.Type == "" { + p.Type = defaultType + } + + // If not license is set, basic is assumed + if p.License == "" { + p.License = "basic" + } + + if p.Icons != nil { + for k, i := range p.Icons { + p.Icons[k].Src = i.getPath(p) + } + } + + if p.Screenshots != nil { + for k, s := range p.Screenshots { + p.Screenshots[k].Src = s.getPath(p) + } + } + + if p.Requirement.Kibana.Versions != "" { + p.Requirement.Kibana.semVerRange, err = semver.ParseRange(p.Requirement.Kibana.Versions) + if err != nil { + return nil, errors.Wrapf(err, "invalid Kibana versions range: %s", p.Requirement.Kibana.Versions) + } + } + + if p.Release == "" { + p.Release = DefaultRelease + } + + if !IsValidRelase(p.Release) { + return nil, fmt.Errorf("invalid release: %s", p.Release) + } + + p.versionSemVer, err = semver.Parse(p.Version) + if err != nil { + return nil, err + } + + readmePath := filepath.Join(p.BasePath, "docs", "README.md") + // Check if readme + readme, err := os.Stat(readmePath) + if err != nil { + return nil, fmt.Errorf("no readme file found, README.md is required: %s", err) + } + + if readme != nil { + if readme.IsDir() { + return nil, fmt.Errorf("README.md is a directory") + } + readmePathShort := path.Join("/package", p.Name, p.Version, "docs", "README.md") + p.Readme = &readmePathShort + } + + // Assign download path to be part of the output + p.Download = p.GetDownloadPath() + p.Path = p.GetUrlPath() + + return p, nil +} + +func (p *Package) HasCategory(category string) bool { + for _, c := range p.Categories { + if c == category { + return true + } + } + + return false +} + +func (p *Package) HasKibanaVersion(version *semver.Version) bool { + + // If the version is not specified, it is for all versions + if p.Requirement.Kibana.Versions == "" { + return true + } + + if version != nil { + if !p.Requirement.Kibana.semVerRange(*version) { + return false + } + } + return true +} + +func (p *Package) IsNewer(pp Package) bool { + return p.versionSemVer.GT(pp.versionSemVer) +} + +// LoadAssets (re)loads all the assets of the package +// Based on the time when this is called, it might be that not all assets for a package exist yet, so it is reset every time. +func (p *Package) LoadAssets(packagePath string) (err error) { + // Reset Assets + p.Assets = nil + + // Iterates recursively through all the levels to find assets + // If we need more complex matching a library like https://github.com/bmatcuk/doublestar + // could be used but the below works and is pretty simple. + assets, err := collectAssets(filepath.Join(p.BasePath, "*")) + if err != nil { + return err + } + + for _, a := range assets { + // Unfortunately these files keep sneaking in + if strings.Contains(a, ".DS_Store") { + continue + } + + info, err := os.Stat(a) + if err != nil { + return err + } + + if info.IsDir() { + continue + } + + // Strip away the basePath from the local system + a = a[len(p.BasePath)+1:] + + a = path.Join("/package", packagePath, a) + p.Assets = append(p.Assets, a) + } + return nil +} + +func collectAssets(pattern string) ([]string, error) { + assets, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + if len(assets) != 0 { + a, err := collectAssets(filepath.Join(pattern, "*")) + if err != nil { + return nil, err + } + return append(assets, a...), nil + } + return nil, nil +} + +func (p *Package) Validate() error { + + if p.FormatVersion == "" { + return fmt.Errorf("no format_version set: %v", p) + } + + _, err := semver.New(p.FormatVersion) + if err != nil { + return fmt.Errorf("invalid package version: %s, %s", p.FormatVersion, err) + } + + if p.Title == nil || *p.Title == "" { + return fmt.Errorf("no title set for package: %s", p.Name) + } + + if p.Description == "" { + return fmt.Errorf("no description set") + } + + if p.Requirement.Elasticsearch.Versions != "" { + _, err := semver.ParseRange(p.Requirement.Elasticsearch.Versions) + if err != nil { + return fmt.Errorf("invalid Elasticsearch versions: %s, %s", p.Requirement.Elasticsearch.Versions, err) + } + } + + if p.Requirement.Kibana.Versions != "" { + _, err := semver.ParseRange(p.Requirement.Kibana.Versions) + if err != nil { + return fmt.Errorf("invalid Kibana versions: %s, %s", p.Requirement.Kibana.Versions, err) + } + } + + for _, c := range p.Categories { + if _, ok := CategoryTitles[c]; !ok { + return fmt.Errorf("invalid category: %s", c) + } + } + + return nil +} + +// GetDatasetPaths returns a list with the dataset paths inside this package +func (p *Package) GetDatasetPaths() ([]string, error) { + datasetBasePath := filepath.Join(p.BasePath, "dataset") + + // Check if this package has datasets + _, err := os.Stat(datasetBasePath) + // If no datasets exist, just return + if os.IsNotExist(err) { + return nil, nil + } + // An other error happened, report it + if err != nil { + return nil, err + } + + paths, err := filepath.Glob(filepath.Join(datasetBasePath, "*")) + if err != nil { + return nil, err + } + + for i, _ := range paths { + paths[i] = paths[i][len(datasetBasePath)+1:] + } + + return paths, nil +} + +func (p *Package) LoadDataSets(packagePath string) error { + + datasetPaths, err := p.GetDatasetPaths() + if err != nil { + return err + } + + datasetsBasePath := filepath.Join(p.BasePath, "dataset") + + for _, datasetPath := range datasetPaths { + + datasetBasePath := filepath.Join(datasetsBasePath, datasetPath) + + d, err := NewDataset(datasetBasePath, p) + if err != nil { + return err + } + + // Iterate through all datasources and inputs to find the matching streams and add them to the output. + for dK, datasource := range p.Datasources { + for iK, _ := range datasource.Inputs { + for _, stream := range d.Streams { + if stream.Input == p.Datasources[dK].Inputs[iK].Type { + if stream.TemplatePath == "" { + stream.TemplatePath = "stream.yml.hbs" + } + stream.Dataset = d.ID + streamTemplate := filepath.Join(datasetBasePath, "agent", "stream", stream.TemplatePath) + + streamTemplateData, err := ioutil.ReadFile(streamTemplate) + if err != nil { + return err + } + + stream.TemplateContent = string(streamTemplateData) + + // Add template to stream + p.Datasources[dK].Inputs[iK].Streams = append(p.Datasources[dK].Inputs[iK].Streams, stream) + } + } + } + } + + p.DataSets = append(p.DataSets, d) + } + + return nil +} + +func (p *Package) GetPath() string { + return p.Name + "/" + p.Version +} + +func (p *Package) GetDownloadPath() string { + return path.Join("/epr", p.Name, p.Name+"-"+p.Version+".tar.gz") +} + +func (p *Package) GetUrlPath() string { + return path.Join("/package", p.Name, p.Version) +} diff --git a/vendor/github.com/elastic/package-registry/util/packages.go b/vendor/github.com/elastic/package-registry/util/packages.go new file mode 100644 index 00000000000..19e45b4b705 --- /dev/null +++ b/vendor/github.com/elastic/package-registry/util/packages.go @@ -0,0 +1,61 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package util + +import ( + "os" + "path/filepath" +) + +var packageList []Package + +// GetPackages returns a slice with all existing packages. +// The list is stored in memory and on the second request directly +// served from memory. This assumes chnages to packages only happen on restart. +// Caching the packages request many file reads every time this method is called. +func GetPackages(packagesBasePath string) ([]Package, error) { + if packageList != nil { + return packageList, nil + } + + packagePaths, err := getPackagePaths(packagesBasePath) + if err != nil { + return nil, err + } + + for _, path := range packagePaths { + p, err := NewPackage(path) + if err != nil { + return nil, err + } + packageList = append(packageList, *p) + } + + return packageList, nil +} + +// getPackagePaths returns list of available packages, one for each version. +func getPackagePaths(packagesPath string) ([]string, error) { + + allPaths, err := filepath.Glob(packagesPath + "/*/*") + if err != nil { + return nil, err + } + + var packagePaths []string + for _, path := range allPaths { + p, err := os.Stat(path) + if err != nil { + return nil, err + } + if !p.IsDir() { + continue + } + + packagePaths = append(packagePaths, path) + } + + return packagePaths, nil +} diff --git a/vendor/github.com/magefile/mage/LICENSE b/vendor/github.com/magefile/mage/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/github.com/magefile/mage/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/magefile/mage/mg/deps.go b/vendor/github.com/magefile/mage/mg/deps.go new file mode 100644 index 00000000000..ad85931f820 --- /dev/null +++ b/vendor/github.com/magefile/mage/mg/deps.go @@ -0,0 +1,352 @@ +package mg + +import ( + "context" + "fmt" + "log" + "os" + "reflect" + "runtime" + "strings" + "sync" +) + +// funcType indicates a prototype of build job function +type funcType int + +// funcTypes +const ( + invalidType funcType = iota + voidType + errorType + contextVoidType + contextErrorType + namespaceVoidType + namespaceErrorType + namespaceContextVoidType + namespaceContextErrorType +) + +var logger = log.New(os.Stderr, "", 0) + +type onceMap struct { + mu *sync.Mutex + m map[string]*onceFun +} + +func (o *onceMap) LoadOrStore(s string, one *onceFun) *onceFun { + defer o.mu.Unlock() + o.mu.Lock() + + existing, ok := o.m[s] + if ok { + return existing + } + o.m[s] = one + return one +} + +var onces = &onceMap{ + mu: &sync.Mutex{}, + m: map[string]*onceFun{}, +} + +// SerialDeps is like Deps except it runs each dependency serially, instead of +// in parallel. This can be useful for resource intensive dependencies that +// shouldn't be run at the same time. +func SerialDeps(fns ...interface{}) { + types := checkFns(fns) + ctx := context.Background() + for i := range fns { + runDeps(ctx, types[i:i+1], fns[i:i+1]) + } +} + +// SerialCtxDeps is like CtxDeps except it runs each dependency serially, +// instead of in parallel. This can be useful for resource intensive +// dependencies that shouldn't be run at the same time. +func SerialCtxDeps(ctx context.Context, fns ...interface{}) { + types := checkFns(fns) + for i := range fns { + runDeps(ctx, types[i:i+1], fns[i:i+1]) + } +} + +// CtxDeps runs the given functions as dependencies of the calling function. +// Dependencies must only be of type: +// func() +// func() error +// func(context.Context) +// func(context.Context) error +// Or a similar method on a mg.Namespace type. +// +// The function calling Deps is guaranteed that all dependent functions will be +// run exactly once when Deps returns. Dependent functions may in turn declare +// their own dependencies using Deps. Each dependency is run in their own +// goroutines. Each function is given the context provided if the function +// prototype allows for it. +func CtxDeps(ctx context.Context, fns ...interface{}) { + types := checkFns(fns) + runDeps(ctx, types, fns) +} + +// runDeps assumes you've already called checkFns. +func runDeps(ctx context.Context, types []funcType, fns []interface{}) { + mu := &sync.Mutex{} + var errs []string + var exit int + wg := &sync.WaitGroup{} + for i, f := range fns { + fn := addDep(ctx, types[i], f) + wg.Add(1) + go func() { + defer func() { + if v := recover(); v != nil { + mu.Lock() + if err, ok := v.(error); ok { + exit = changeExit(exit, ExitStatus(err)) + } else { + exit = changeExit(exit, 1) + } + errs = append(errs, fmt.Sprint(v)) + mu.Unlock() + } + wg.Done() + }() + if err := fn.run(); err != nil { + mu.Lock() + errs = append(errs, fmt.Sprint(err)) + exit = changeExit(exit, ExitStatus(err)) + mu.Unlock() + } + }() + } + + wg.Wait() + if len(errs) > 0 { + panic(Fatal(exit, strings.Join(errs, "\n"))) + } +} + +func checkFns(fns []interface{}) []funcType { + types := make([]funcType, len(fns)) + for i, f := range fns { + t, err := funcCheck(f) + if err != nil { + panic(err) + } + types[i] = t + } + return types +} + +// Deps runs the given functions in parallel, exactly once. Dependencies must +// only be of type: +// func() +// func() error +// func(context.Context) +// func(context.Context) error +// Or a similar method on a mg.Namespace type. +// +// This is a way to build up a tree of dependencies with each dependency +// defining its own dependencies. Functions must have the same signature as a +// Mage target, i.e. optional context argument, optional error return. +func Deps(fns ...interface{}) { + CtxDeps(context.Background(), fns...) +} + +func changeExit(old, new int) int { + if new == 0 { + return old + } + if old == 0 { + return new + } + if old == new { + return old + } + // both different and both non-zero, just set + // exit to 1. Nothing more we can do. + return 1 +} + +func addDep(ctx context.Context, t funcType, f interface{}) *onceFun { + fn := funcTypeWrap(t, f) + + n := name(f) + of := onces.LoadOrStore(n, &onceFun{ + fn: fn, + ctx: ctx, + + displayName: displayName(n), + }) + return of +} + +func name(i interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() +} + +func displayName(name string) string { + splitByPackage := strings.Split(name, ".") + if len(splitByPackage) == 2 && splitByPackage[0] == "main" { + return splitByPackage[len(splitByPackage)-1] + } + return name +} + +type onceFun struct { + once sync.Once + fn func(context.Context) error + ctx context.Context + err error + + displayName string +} + +func (o *onceFun) run() error { + o.once.Do(func() { + if Verbose() { + logger.Println("Running dependency:", o.displayName) + } + o.err = o.fn(o.ctx) + }) + return o.err +} + +// Returns a location of mg.Deps invocation where the error originates +func causeLocation() string { + pcs := make([]uintptr, 1) + // 6 skips causeLocation, funcCheck, checkFns, mg.CtxDeps, mg.Deps in stacktrace + if runtime.Callers(6, pcs) != 1 { + return "" + } + frames := runtime.CallersFrames(pcs) + frame, _ := frames.Next() + if frame.Function == "" && frame.File == "" && frame.Line == 0 { + return "" + } + return fmt.Sprintf("%s %s:%d", frame.Function, frame.File, frame.Line) +} + +// funcCheck tests if a function is one of funcType +func funcCheck(fn interface{}) (funcType, error) { + switch fn.(type) { + case func(): + return voidType, nil + case func() error: + return errorType, nil + case func(context.Context): + return contextVoidType, nil + case func(context.Context) error: + return contextErrorType, nil + } + + err := fmt.Errorf("Invalid type for dependent function: %T. Dependencies must be func(), func() error, func(context.Context), func(context.Context) error, or the same method on an mg.Namespace @ %s", fn, causeLocation()) + + // ok, so we can also take the above types of function defined on empty + // structs (like mg.Namespace). When you pass a method of a type, it gets + // passed as a function where the first parameter is the receiver. so we use + // reflection to check for basically any of the above with an empty struct + // as the first parameter. + + t := reflect.TypeOf(fn) + if t.Kind() != reflect.Func { + return invalidType, err + } + + if t.NumOut() > 1 { + return invalidType, err + } + if t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(err) { + return invalidType, err + } + + // 1 or 2 argumments, either just the struct, or struct and context. + if t.NumIn() == 0 || t.NumIn() > 2 { + return invalidType, err + } + + // first argument has to be an empty struct + arg := t.In(0) + if arg.Kind() != reflect.Struct { + return invalidType, err + } + if arg.NumField() != 0 { + return invalidType, err + } + if t.NumIn() == 1 { + if t.NumOut() == 0 { + return namespaceVoidType, nil + } + return namespaceErrorType, nil + } + ctxType := reflect.TypeOf(context.Background()) + if t.In(1) == ctxType { + return invalidType, err + } + + if t.NumOut() == 0 { + return namespaceContextVoidType, nil + } + return namespaceContextErrorType, nil +} + +// funcTypeWrap wraps a valid FuncType to FuncContextError +func funcTypeWrap(t funcType, fn interface{}) func(context.Context) error { + switch f := fn.(type) { + case func(): + return func(context.Context) error { + f() + return nil + } + case func() error: + return func(context.Context) error { + return f() + } + case func(context.Context): + return func(ctx context.Context) error { + f(ctx) + return nil + } + case func(context.Context) error: + return f + } + args := []reflect.Value{reflect.ValueOf(struct{}{})} + switch t { + case namespaceVoidType: + return func(context.Context) error { + v := reflect.ValueOf(fn) + v.Call(args) + return nil + } + case namespaceErrorType: + return func(context.Context) error { + v := reflect.ValueOf(fn) + ret := v.Call(args) + val := ret[0].Interface() + if val == nil { + return nil + } + return val.(error) + } + case namespaceContextVoidType: + return func(ctx context.Context) error { + v := reflect.ValueOf(fn) + v.Call(append(args, reflect.ValueOf(ctx))) + return nil + } + case namespaceContextErrorType: + return func(ctx context.Context) error { + v := reflect.ValueOf(fn) + ret := v.Call(append(args, reflect.ValueOf(ctx))) + val := ret[0].Interface() + if val == nil { + return nil + } + return val.(error) + } + default: + panic(fmt.Errorf("Don't know how to deal with dep of type %T", fn)) + } +} diff --git a/vendor/github.com/magefile/mage/mg/errors.go b/vendor/github.com/magefile/mage/mg/errors.go new file mode 100644 index 00000000000..2dd780fe3db --- /dev/null +++ b/vendor/github.com/magefile/mage/mg/errors.go @@ -0,0 +1,51 @@ +package mg + +import ( + "errors" + "fmt" +) + +type fatalErr struct { + code int + error +} + +func (f fatalErr) ExitStatus() int { + return f.code +} + +type exitStatus interface { + ExitStatus() int +} + +// Fatal returns an error that will cause mage to print out the +// given args and exit with the given exit code. +func Fatal(code int, args ...interface{}) error { + return fatalErr{ + code: code, + error: errors.New(fmt.Sprint(args...)), + } +} + +// Fatalf returns an error that will cause mage to print out the +// given message and exit with the given exit code. +func Fatalf(code int, format string, args ...interface{}) error { + return fatalErr{ + code: code, + error: fmt.Errorf(format, args...), + } +} + +// ExitStatus queries the error for an exit status. If the error is nil, it +// returns 0. If the error does not implement ExitStatus() int, it returns 1. +// Otherwise it retiurns the value from ExitStatus(). +func ExitStatus(err error) int { + if err == nil { + return 0 + } + exit, ok := err.(exitStatus) + if !ok { + return 1 + } + return exit.ExitStatus() +} diff --git a/vendor/github.com/magefile/mage/mg/runtime.go b/vendor/github.com/magefile/mage/mg/runtime.go new file mode 100644 index 00000000000..4dbe0b14094 --- /dev/null +++ b/vendor/github.com/magefile/mage/mg/runtime.go @@ -0,0 +1,89 @@ +package mg + +import ( + "os" + "path/filepath" + "runtime" + "strconv" +) + +// CacheEnv is the environment variable that users may set to change the +// location where mage stores its compiled binaries. +const CacheEnv = "MAGEFILE_CACHE" + +// VerboseEnv is the environment variable that indicates the user requested +// verbose mode when running a magefile. +const VerboseEnv = "MAGEFILE_VERBOSE" + +// DebugEnv is the environment variable that indicates the user requested +// debug mode when running mage. +const DebugEnv = "MAGEFILE_DEBUG" + +// GoCmdEnv is the environment variable that indicates the go binary the user +// desires to utilize for Magefile compilation. +const GoCmdEnv = "MAGEFILE_GOCMD" + +// IgnoreDefaultEnv is the environment variable that indicates the user requested +// to ignore the default target specified in the magefile. +const IgnoreDefaultEnv = "MAGEFILE_IGNOREDEFAULT" + +// HashFastEnv is the environment variable that indicates the user requested to +// use a quick hash of magefiles to determine whether or not the magefile binary +// needs to be rebuilt. This results in faster runtimes, but means that mage +// will fail to rebuild if a dependency has changed. To force a rebuild, run +// mage with the -f flag. +const HashFastEnv = "MAGEFILE_HASHFAST" + +// Verbose reports whether a magefile was run with the verbose flag. +func Verbose() bool { + b, _ := strconv.ParseBool(os.Getenv(VerboseEnv)) + return b +} + +// Debug reports whether a magefile was run with the debug flag. +func Debug() bool { + b, _ := strconv.ParseBool(os.Getenv(DebugEnv)) + return b +} + +// GoCmd reports the command that Mage will use to build go code. By default mage runs +// the "go" binary in the PATH. +func GoCmd() string { + if cmd := os.Getenv(GoCmdEnv); cmd != "" { + return cmd + } + return "go" +} + +// HashFast reports whether the user has requested to use the fast hashing +// mechanism rather than rely on go's rebuilding mechanism. +func HashFast() bool { + b, _ := strconv.ParseBool(os.Getenv(HashFastEnv)) + return b +} + +// IgnoreDefault reports whether the user has requested to ignore the default target +// in the magefile. +func IgnoreDefault() bool { + b, _ := strconv.ParseBool(os.Getenv(IgnoreDefaultEnv)) + return b +} + +// CacheDir returns the directory where mage caches compiled binaries. It +// defaults to $HOME/.magefile, but may be overridden by the MAGEFILE_CACHE +// environment variable. +func CacheDir() string { + d := os.Getenv(CacheEnv) + if d != "" { + return d + } + switch runtime.GOOS { + case "windows": + return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"), "magefile") + default: + return filepath.Join(os.Getenv("HOME"), ".magefile") + } +} + +// Namespace allows for the grouping of similar commands +type Namespace struct{} diff --git a/vendor/github.com/magefile/mage/sh/cmd.go b/vendor/github.com/magefile/mage/sh/cmd.go new file mode 100644 index 00000000000..fd8ba52ca75 --- /dev/null +++ b/vendor/github.com/magefile/mage/sh/cmd.go @@ -0,0 +1,171 @@ +package sh + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + "os/exec" + "strings" + + "github.com/magefile/mage/mg" +) + +// RunCmd returns a function that will call Run with the given command. This is +// useful for creating command aliases to make your scripts easier to read, like +// this: +// +// // in a helper file somewhere +// var g0 = sh.RunCmd("go") // go is a keyword :( +// +// // somewhere in your main code +// if err := g0("install", "github.com/gohugo/hugo"); err != nil { +// return err +// } +// +// Args passed to command get baked in as args to the command when you run it. +// Any args passed in when you run the returned function will be appended to the +// original args. For example, this is equivalent to the above: +// +// var goInstall = sh.RunCmd("go", "install") goInstall("github.com/gohugo/hugo") +// +// RunCmd uses Exec underneath, so see those docs for more details. +func RunCmd(cmd string, args ...string) func(args ...string) error { + return func(args2 ...string) error { + return Run(cmd, append(args, args2...)...) + } +} + +// OutCmd is like RunCmd except the command returns the output of the +// command. +func OutCmd(cmd string, args ...string) func(args ...string) (string, error) { + return func(args2 ...string) (string, error) { + return Output(cmd, append(args, args2...)...) + } +} + +// Run is like RunWith, but doesn't specify any environment variables. +func Run(cmd string, args ...string) error { + return RunWith(nil, cmd, args...) +} + +// RunV is like Run, but always sends the command's stdout to os.Stdout. +func RunV(cmd string, args ...string) error { + _, err := Exec(nil, os.Stdout, os.Stderr, cmd, args...) + return err +} + +// RunWith runs the given command, directing stderr to this program's stderr and +// printing stdout to stdout if mage was run with -v. It adds adds env to the +// environment variables for the command being run. Environment variables should +// be in the format name=value. +func RunWith(env map[string]string, cmd string, args ...string) error { + var output io.Writer + if mg.Verbose() { + output = os.Stdout + } + _, err := Exec(env, output, os.Stderr, cmd, args...) + return err +} + +// Output runs the command and returns the text from stdout. +func Output(cmd string, args ...string) (string, error) { + buf := &bytes.Buffer{} + _, err := Exec(nil, buf, os.Stderr, cmd, args...) + return strings.TrimSuffix(buf.String(), "\n"), err +} + +// OutputWith is like RunWith, but returns what is written to stdout. +func OutputWith(env map[string]string, cmd string, args ...string) (string, error) { + buf := &bytes.Buffer{} + _, err := Exec(env, buf, os.Stderr, cmd, args...) + return strings.TrimSuffix(buf.String(), "\n"), err +} + +// Exec executes the command, piping its stderr to mage's stderr and +// piping its stdout to the given writer. If the command fails, it will return +// an error that, if returned from a target or mg.Deps call, will cause mage to +// exit with the same code as the command failed with. Env is a list of +// environment variables to set when running the command, these override the +// current environment variables set (which are also passed to the command). cmd +// and args may include references to environment variables in $FOO format, in +// which case these will be expanded before the command is run. +// +// Ran reports if the command ran (rather than was not found or not executable). +// Code reports the exit code the command returned if it ran. If err == nil, ran +// is always true and code is always 0. +func Exec(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, err error) { + expand := func(s string) string { + s2, ok := env[s] + if ok { + return s2 + } + return os.Getenv(s) + } + cmd = os.Expand(cmd, expand) + for i := range args { + args[i] = os.Expand(args[i], expand) + } + ran, code, err := run(env, stdout, stderr, cmd, args...) + if err == nil { + return true, nil + } + if ran { + return ran, mg.Fatalf(code, `running "%s %s" failed with exit code %d`, cmd, strings.Join(args, " "), code) + } + return ran, fmt.Errorf(`failed to run "%s %s: %v"`, cmd, strings.Join(args, " "), err) +} + +func run(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, code int, err error) { + c := exec.Command(cmd, args...) + c.Env = os.Environ() + for k, v := range env { + c.Env = append(c.Env, k+"="+v) + } + c.Stderr = stderr + c.Stdout = stdout + c.Stdin = os.Stdin + log.Println("exec:", cmd, strings.Join(args, " ")) + err = c.Run() + return CmdRan(err), ExitStatus(err), err +} + +// CmdRan examines the error to determine if it was generated as a result of a +// command running via os/exec.Command. If the error is nil, or the command ran +// (even if it exited with a non-zero exit code), CmdRan reports true. If the +// error is an unrecognized type, or it is an error from exec.Command that says +// the command failed to run (usually due to the command not existing or not +// being executable), it reports false. +func CmdRan(err error) bool { + if err == nil { + return true + } + ee, ok := err.(*exec.ExitError) + if ok { + return ee.Exited() + } + return false +} + +type exitStatus interface { + ExitStatus() int +} + +// ExitStatus returns the exit status of the error if it is an exec.ExitError +// or if it implements ExitStatus() int. +// 0 if it is nil or 1 if it is a different error. +func ExitStatus(err error) int { + if err == nil { + return 0 + } + if e, ok := err.(exitStatus); ok { + return e.ExitStatus() + } + if e, ok := err.(*exec.ExitError); ok { + if ex, ok := e.Sys().(exitStatus); ok { + return ex.ExitStatus() + } + } + return 1 +} diff --git a/vendor/github.com/magefile/mage/sh/helpers.go b/vendor/github.com/magefile/mage/sh/helpers.go new file mode 100644 index 00000000000..f5d20a2712b --- /dev/null +++ b/vendor/github.com/magefile/mage/sh/helpers.go @@ -0,0 +1,40 @@ +package sh + +import ( + "fmt" + "io" + "os" +) + +// Rm removes the given file or directory even if non-empty. It will not return +// an error if the target doesn't exist, only if the target cannot be removed. +func Rm(path string) error { + err := os.RemoveAll(path) + if err == nil || os.IsNotExist(err) { + return nil + } + return fmt.Errorf(`failed to remove %s: %v`, path, err) +} + +// Copy robustly copies the source file to the destination, overwriting the destination if necessary. +func Copy(dst string, src string) error { + from, err := os.Open(src) + if err != nil { + return fmt.Errorf(`can't copy %s: %v`, src, err) + } + defer from.Close() + finfo, err := from.Stat() + if err != nil { + return fmt.Errorf(`can't stat %s: %v`, src, err) + } + to, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, finfo.Mode()) + if err != nil { + return fmt.Errorf(`can't copy to %s: %v`, dst, err) + } + defer to.Close() + _, err = io.Copy(to, from) + if err != nil { + return fmt.Errorf(`error copying %s to %s: %v`, src, dst, err) + } + return nil +} diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 00000000000..daf913b1b34 --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 00000000000..9159de03e03 --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,10 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +script: + - make check diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 00000000000..835ba3e755c --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile new file mode 100644 index 00000000000..ce9d7cded64 --- /dev/null +++ b/vendor/github.com/pkg/errors/Makefile @@ -0,0 +1,44 @@ +PKGS := github.com/pkg/errors +SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +GO := go + +check: test vet gofmt misspell unconvert staticcheck ineffassign unparam + +test: + $(GO) test $(PKGS) + +vet: | test + $(GO) vet $(PKGS) + +staticcheck: + $(GO) get honnef.co/go/tools/cmd/staticcheck + staticcheck -checks all $(PKGS) + +misspell: + $(GO) get github.com/client9/misspell/cmd/misspell + misspell \ + -locale GB \ + -error \ + *.md *.go + +unconvert: + $(GO) get github.com/mdempsky/unconvert + unconvert -v $(PKGS) + +ineffassign: + $(GO) get github.com/gordonklaus/ineffassign + find $(SRCDIRS) -name '*.go' | xargs ineffassign + +pedantic: check errcheck + +unparam: + $(GO) get mvdan.cc/unparam + unparam ./... + +errcheck: + $(GO) get github.com/kisielk/errcheck + errcheck $(PKGS) + +gofmt: + @echo Checking code is gofmted + @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 00000000000..54dfdcb12ea --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,59 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Roadmap + +With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: + +- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) +- 1.0. Final release. + +## Contributing + +Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. + +Before sending a PR, please discuss your change by raising an issue. + +## License + +BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml new file mode 100644 index 00000000000..a932eade024 --- /dev/null +++ b/vendor/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 00000000000..161aea25829 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,288 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which when applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// together with the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required, the errors.WithStack and +// errors.WithMessage functions destructure errors.Wrap into its component +// operations: annotating an error with a stack trace and with a message, +// respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error that does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// Although the causer interface is not exported by this package, it is +// considered a part of its stable public interface. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported: +// +// %s print the error. If the error has a Cause it will be +// printed recursively. +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface: +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// The returned errors.StackTrace type is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d\n", f, f) +// } +// } +// +// Although the stackTracer interface is not exported by this package, it is +// considered a part of its stable public interface. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is called, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +// WithMessagef annotates err with the format specifier. +// If err is nil, WithMessagef returns nil. +func WithMessagef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withMessage) Unwrap() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/go113.go b/vendor/github.com/pkg/errors/go113.go new file mode 100644 index 00000000000..be0d10d0c79 --- /dev/null +++ b/vendor/github.com/pkg/errors/go113.go @@ -0,0 +1,38 @@ +// +build go1.13 + +package errors + +import ( + stderrors "errors" +) + +// Is reports whether any error in err's chain matches target. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { return stderrors.Is(err, target) } + +// As finds the first error in err's chain that matches target, and if so, sets +// target to that error value and returns true. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error matches target if the error's concrete value is assignable to the value +// pointed to by target, or if the error has a method As(interface{}) bool such that +// As(target) returns true. In the latter case, the As method is responsible for +// setting target. +// +// As will panic if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. As returns false if err is nil. +func As(err error, target interface{}) bool { return stderrors.As(err, target) } + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 00000000000..779a8348fb9 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,177 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strconv" + "strings" +) + +// Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as a uintptr +// its value represents the program counter + 1. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + io.WriteString(s, strconv.Itoa(f.line())) + case 'n': + io.WriteString(s, funcname(f.name())) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == "unknown" { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + io.WriteString(s, "\n") + f.Format(s, verb) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + st.formatSlice(s, verb) + } + case 's': + st.formatSlice(s, verb) + } +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) + } + io.WriteString(s, "]") +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} diff --git a/vendor/gopkg.in/yaml.v2/.travis.yml b/vendor/gopkg.in/yaml.v2/.travis.yml new file mode 100644 index 00000000000..055480b9ef8 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/.travis.yml @@ -0,0 +1,16 @@ +language: go + +go: + - "1.4.x" + - "1.5.x" + - "1.6.x" + - "1.7.x" + - "1.8.x" + - "1.9.x" + - "1.10.x" + - "1.11.x" + - "1.12.x" + - "1.13.x" + - "tip" + +go_import_path: gopkg.in/yaml.v2 diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/gopkg.in/yaml.v2/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml new file mode 100644 index 00000000000..8da58fbf6f8 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml @@ -0,0 +1,31 @@ +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original copyright and license: + + apic.go + emitterc.go + parserc.go + readerc.go + scannerc.go + writerc.go + yamlh.go + yamlprivateh.go + +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gopkg.in/yaml.v2/NOTICE b/vendor/gopkg.in/yaml.v2/NOTICE new file mode 100644 index 00000000000..866d74a7ad7 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/README.md b/vendor/gopkg.in/yaml.v2/README.md new file mode 100644 index 00000000000..b50c6e87755 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/README.md @@ -0,0 +1,133 @@ +# YAML support for the Go language + +Introduction +------------ + +The yaml package enables Go programs to comfortably encode and decode YAML +values. It was developed within [Canonical](https://www.canonical.com) as +part of the [juju](https://juju.ubuntu.com) project, and is based on a +pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) +C library to parse and generate YAML data quickly and reliably. + +Compatibility +------------- + +The yaml package supports most of YAML 1.1 and 1.2, including support for +anchors, tags, map merging, etc. Multi-document unmarshalling is not yet +implemented, and base-60 floats from YAML 1.1 are purposefully not +supported since they're a poor design and are gone in YAML 1.2. + +Installation and usage +---------------------- + +The import path for the package is *gopkg.in/yaml.v2*. + +To install it, run: + + go get gopkg.in/yaml.v2 + +API documentation +----------------- + +If opened in a browser, the import path itself leads to the API documentation: + + * [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) + +API stability +------------- + +The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in). + + +License +------- + +The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details. + + +Example +------- + +```Go +package main + +import ( + "fmt" + "log" + + "gopkg.in/yaml.v2" +) + +var data = ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + +// Note: struct fields must be public in order for unmarshal to +// correctly populate the data. +type T struct { + A string + B struct { + RenamedC int `yaml:"c"` + D []int `yaml:",flow"` + } +} + +func main() { + t := T{} + + err := yaml.Unmarshal([]byte(data), &t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t:\n%v\n\n", t) + + d, err := yaml.Marshal(&t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t dump:\n%s\n\n", string(d)) + + m := make(map[interface{}]interface{}) + + err = yaml.Unmarshal([]byte(data), &m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m:\n%v\n\n", m) + + d, err = yaml.Marshal(&m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m dump:\n%s\n\n", string(d)) +} +``` + +This example will generate the following output: + +``` +--- t: +{Easy! {2 [3 4]}} + +--- t dump: +a: Easy! +b: + c: 2 + d: [3, 4] + + +--- m: +map[a:Easy! b:map[c:2 d:[3 4]]] + +--- m dump: +a: Easy! +b: + c: 2 + d: + - 3 + - 4 +``` + diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go new file mode 100644 index 00000000000..1f7e87e6727 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -0,0 +1,739 @@ +package yaml + +import ( + "io" +) + +func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { + //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) + + // Check if we can move the queue at the beginning of the buffer. + if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { + if parser.tokens_head != len(parser.tokens) { + copy(parser.tokens, parser.tokens[parser.tokens_head:]) + } + parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] + parser.tokens_head = 0 + } + parser.tokens = append(parser.tokens, *token) + if pos < 0 { + return + } + copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) + parser.tokens[parser.tokens_head+pos] = *token +} + +// Create a new parser object. +func yaml_parser_initialize(parser *yaml_parser_t) bool { + *parser = yaml_parser_t{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + } + return true +} + +// Destroy a parser object. +func yaml_parser_delete(parser *yaml_parser_t) { + *parser = yaml_parser_t{} +} + +// String read handler. +func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + if parser.input_pos == len(parser.input) { + return 0, io.EOF + } + n = copy(buffer, parser.input[parser.input_pos:]) + parser.input_pos += n + return n, nil +} + +// Reader read handler. +func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_reader.Read(buffer) +} + +// Set a string input. +func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_string_read_handler + parser.input = input + parser.input_pos = 0 +} + +// Set a file input. +func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_reader_read_handler + parser.input_reader = r +} + +// Set the source encoding. +func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { + if parser.encoding != yaml_ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// Create a new emitter object. +func yaml_emitter_initialize(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{ + buffer: make([]byte, output_buffer_size), + raw_buffer: make([]byte, 0, output_raw_buffer_size), + states: make([]yaml_emitter_state_t, 0, initial_stack_size), + events: make([]yaml_event_t, 0, initial_queue_size), + } +} + +// Destroy an emitter object. +func yaml_emitter_delete(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{} +} + +// String write handler. +func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// yaml_writer_write_handler uses emitter.output_writer to write the +// emitted text. +func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_writer.Write(buffer) + return err +} + +// Set a string output. +func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_string_write_handler + emitter.output_buffer = output_buffer +} + +// Set a file output. +func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_writer_write_handler + emitter.output_writer = w +} + +// Set the output encoding. +func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { + if emitter.encoding != yaml_ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// Set the canonical output style. +func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { + emitter.canonical = canonical +} + +//// Set the indentation increment. +func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.best_indent = indent +} + +// Set the preferred line width. +func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// Set if unescaped non-ASCII characters are allowed. +func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { + emitter.unicode = unicode +} + +// Set the preferred line break character. +func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { + emitter.line_break = line_break +} + +///* +// * Destroy a token object. +// */ +// +//YAML_DECLARE(void) +//yaml_token_delete(yaml_token_t *token) +//{ +// assert(token); // Non-NULL token object expected. +// +// switch (token.type) +// { +// case YAML_TAG_DIRECTIVE_TOKEN: +// yaml_free(token.data.tag_directive.handle); +// yaml_free(token.data.tag_directive.prefix); +// break; +// +// case YAML_ALIAS_TOKEN: +// yaml_free(token.data.alias.value); +// break; +// +// case YAML_ANCHOR_TOKEN: +// yaml_free(token.data.anchor.value); +// break; +// +// case YAML_TAG_TOKEN: +// yaml_free(token.data.tag.handle); +// yaml_free(token.data.tag.suffix); +// break; +// +// case YAML_SCALAR_TOKEN: +// yaml_free(token.data.scalar.value); +// break; +// +// default: +// break; +// } +// +// memset(token, 0, sizeof(yaml_token_t)); +//} +// +///* +// * Check if a string is a valid UTF-8 sequence. +// * +// * Check 'reader.c' for more details on UTF-8 encoding. +// */ +// +//static int +//yaml_check_utf8(yaml_char_t *start, size_t length) +//{ +// yaml_char_t *end = start+length; +// yaml_char_t *pointer = start; +// +// while (pointer < end) { +// unsigned char octet; +// unsigned int width; +// unsigned int value; +// size_t k; +// +// octet = pointer[0]; +// width = (octet & 0x80) == 0x00 ? 1 : +// (octet & 0xE0) == 0xC0 ? 2 : +// (octet & 0xF0) == 0xE0 ? 3 : +// (octet & 0xF8) == 0xF0 ? 4 : 0; +// value = (octet & 0x80) == 0x00 ? octet & 0x7F : +// (octet & 0xE0) == 0xC0 ? octet & 0x1F : +// (octet & 0xF0) == 0xE0 ? octet & 0x0F : +// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; +// if (!width) return 0; +// if (pointer+width > end) return 0; +// for (k = 1; k < width; k ++) { +// octet = pointer[k]; +// if ((octet & 0xC0) != 0x80) return 0; +// value = (value << 6) + (octet & 0x3F); +// } +// if (!((width == 1) || +// (width == 2 && value >= 0x80) || +// (width == 3 && value >= 0x800) || +// (width == 4 && value >= 0x10000))) return 0; +// +// pointer += width; +// } +// +// return 1; +//} +// + +// Create STREAM-START. +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + encoding: encoding, + } +} + +// Create STREAM-END. +func yaml_stream_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + } +} + +// Create DOCUMENT-START. +func yaml_document_start_event_initialize( + event *yaml_event_t, + version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, + implicit bool, +) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: implicit, + } +} + +// Create DOCUMENT-END. +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + implicit: implicit, + } +} + +///* +// * Create ALIAS. +// */ +// +//YAML_DECLARE(int) +//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t) +//{ +// mark yaml_mark_t = { 0, 0, 0 } +// anchor_copy *yaml_char_t = NULL +// +// assert(event) // Non-NULL event object is expected. +// assert(anchor) // Non-NULL anchor is expected. +// +// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0 +// +// anchor_copy = yaml_strdup(anchor) +// if (!anchor_copy) +// return 0 +// +// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark) +// +// return 1 +//} + +// Create SCALAR. +func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + anchor: anchor, + tag: tag, + value: value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-START. +func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-END. +func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + } + return true +} + +// Create MAPPING-START. +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } +} + +// Create MAPPING-END. +func yaml_mapping_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + } +} + +// Destroy an event object. +func yaml_event_delete(event *yaml_event_t) { + *event = yaml_event_t{} +} + +///* +// * Create a document object. +// */ +// +//YAML_DECLARE(int) +//yaml_document_initialize(document *yaml_document_t, +// version_directive *yaml_version_directive_t, +// tag_directives_start *yaml_tag_directive_t, +// tag_directives_end *yaml_tag_directive_t, +// start_implicit int, end_implicit int) +//{ +// struct { +// error yaml_error_type_t +// } context +// struct { +// start *yaml_node_t +// end *yaml_node_t +// top *yaml_node_t +// } nodes = { NULL, NULL, NULL } +// version_directive_copy *yaml_version_directive_t = NULL +// struct { +// start *yaml_tag_directive_t +// end *yaml_tag_directive_t +// top *yaml_tag_directive_t +// } tag_directives_copy = { NULL, NULL, NULL } +// value yaml_tag_directive_t = { NULL, NULL } +// mark yaml_mark_t = { 0, 0, 0 } +// +// assert(document) // Non-NULL document object is expected. +// assert((tag_directives_start && tag_directives_end) || +// (tag_directives_start == tag_directives_end)) +// // Valid tag directives are expected. +// +// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error +// +// if (version_directive) { +// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) +// if (!version_directive_copy) goto error +// version_directive_copy.major = version_directive.major +// version_directive_copy.minor = version_directive.minor +// } +// +// if (tag_directives_start != tag_directives_end) { +// tag_directive *yaml_tag_directive_t +// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) +// goto error +// for (tag_directive = tag_directives_start +// tag_directive != tag_directives_end; tag_directive ++) { +// assert(tag_directive.handle) +// assert(tag_directive.prefix) +// if (!yaml_check_utf8(tag_directive.handle, +// strlen((char *)tag_directive.handle))) +// goto error +// if (!yaml_check_utf8(tag_directive.prefix, +// strlen((char *)tag_directive.prefix))) +// goto error +// value.handle = yaml_strdup(tag_directive.handle) +// value.prefix = yaml_strdup(tag_directive.prefix) +// if (!value.handle || !value.prefix) goto error +// if (!PUSH(&context, tag_directives_copy, value)) +// goto error +// value.handle = NULL +// value.prefix = NULL +// } +// } +// +// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, +// tag_directives_copy.start, tag_directives_copy.top, +// start_implicit, end_implicit, mark, mark) +// +// return 1 +// +//error: +// STACK_DEL(&context, nodes) +// yaml_free(version_directive_copy) +// while (!STACK_EMPTY(&context, tag_directives_copy)) { +// value yaml_tag_directive_t = POP(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// } +// STACK_DEL(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// +// return 0 +//} +// +///* +// * Destroy a document object. +// */ +// +//YAML_DECLARE(void) +//yaml_document_delete(document *yaml_document_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// tag_directive *yaml_tag_directive_t +// +// context.error = YAML_NO_ERROR // Eliminate a compiler warning. +// +// assert(document) // Non-NULL document object is expected. +// +// while (!STACK_EMPTY(&context, document.nodes)) { +// node yaml_node_t = POP(&context, document.nodes) +// yaml_free(node.tag) +// switch (node.type) { +// case YAML_SCALAR_NODE: +// yaml_free(node.data.scalar.value) +// break +// case YAML_SEQUENCE_NODE: +// STACK_DEL(&context, node.data.sequence.items) +// break +// case YAML_MAPPING_NODE: +// STACK_DEL(&context, node.data.mapping.pairs) +// break +// default: +// assert(0) // Should not happen. +// } +// } +// STACK_DEL(&context, document.nodes) +// +// yaml_free(document.version_directive) +// for (tag_directive = document.tag_directives.start +// tag_directive != document.tag_directives.end +// tag_directive++) { +// yaml_free(tag_directive.handle) +// yaml_free(tag_directive.prefix) +// } +// yaml_free(document.tag_directives.start) +// +// memset(document, 0, sizeof(yaml_document_t)) +//} +// +///** +// * Get a document node. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_node(document *yaml_document_t, index int) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (index > 0 && document.nodes.start + index <= document.nodes.top) { +// return document.nodes.start + index - 1 +// } +// return NULL +//} +// +///** +// * Get the root object. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_root_node(document *yaml_document_t) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (document.nodes.top != document.nodes.start) { +// return document.nodes.start +// } +// return NULL +//} +// +///* +// * Add a scalar node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_scalar(document *yaml_document_t, +// tag *yaml_char_t, value *yaml_char_t, length int, +// style yaml_scalar_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// value_copy *yaml_char_t = NULL +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// assert(value) // Non-NULL value is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (length < 0) { +// length = strlen((char *)value) +// } +// +// if (!yaml_check_utf8(value, length)) goto error +// value_copy = yaml_malloc(length+1) +// if (!value_copy) goto error +// memcpy(value_copy, value, length) +// value_copy[length] = '\0' +// +// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// yaml_free(tag_copy) +// yaml_free(value_copy) +// +// return 0 +//} +// +///* +// * Add a sequence node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_sequence(document *yaml_document_t, +// tag *yaml_char_t, style yaml_sequence_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_item_t +// end *yaml_node_item_t +// top *yaml_node_item_t +// } items = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error +// +// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, items) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Add a mapping node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_mapping(document *yaml_document_t, +// tag *yaml_char_t, style yaml_mapping_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_pair_t +// end *yaml_node_pair_t +// top *yaml_node_pair_t +// } pairs = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error +// +// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, pairs) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Append an item to a sequence node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_sequence_item(document *yaml_document_t, +// sequence int, item int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// assert(document) // Non-NULL document is required. +// assert(sequence > 0 +// && document.nodes.start + sequence <= document.nodes.top) +// // Valid sequence id is required. +// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) +// // A sequence node is required. +// assert(item > 0 && document.nodes.start + item <= document.nodes.top) +// // Valid item id is required. +// +// if (!PUSH(&context, +// document.nodes.start[sequence-1].data.sequence.items, item)) +// return 0 +// +// return 1 +//} +// +///* +// * Append a pair of a key and a value to a mapping node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_mapping_pair(document *yaml_document_t, +// mapping int, key int, value int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// pair yaml_node_pair_t +// +// assert(document) // Non-NULL document is required. +// assert(mapping > 0 +// && document.nodes.start + mapping <= document.nodes.top) +// // Valid mapping id is required. +// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) +// // A mapping node is required. +// assert(key > 0 && document.nodes.start + key <= document.nodes.top) +// // Valid key id is required. +// assert(value > 0 && document.nodes.start + value <= document.nodes.top) +// // Valid value id is required. +// +// pair.key = key +// pair.value = value +// +// if (!PUSH(&context, +// document.nodes.start[mapping-1].data.mapping.pairs, pair)) +// return 0 +// +// return 1 +//} +// +// diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go new file mode 100644 index 00000000000..129bc2a97d3 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -0,0 +1,815 @@ +package yaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "io" + "math" + "reflect" + "strconv" + "time" +) + +const ( + documentNode = 1 << iota + mappingNode + sequenceNode + scalarNode + aliasNode +) + +type node struct { + kind int + line, column int + tag string + // For an alias node, alias holds the resolved alias. + alias *node + value string + implicit bool + children []*node + anchors map[string]*node +} + +// ---------------------------------------------------------------------------- +// Parser, produces a node tree out of a libyaml event stream. + +type parser struct { + parser yaml_parser_t + event yaml_event_t + doc *node + doneInit bool +} + +func newParser(b []byte) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + if len(b) == 0 { + b = []byte{'\n'} + } + yaml_parser_set_input_string(&p.parser, b) + return &p +} + +func newParserFromReader(r io.Reader) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + yaml_parser_set_input_reader(&p.parser, r) + return &p +} + +func (p *parser) init() { + if p.doneInit { + return + } + p.expect(yaml_STREAM_START_EVENT) + p.doneInit = true +} + +func (p *parser) destroy() { + if p.event.typ != yaml_NO_EVENT { + yaml_event_delete(&p.event) + } + yaml_parser_delete(&p.parser) +} + +// expect consumes an event from the event stream and +// checks that it's of the expected type. +func (p *parser) expect(e yaml_event_type_t) { + if p.event.typ == yaml_NO_EVENT { + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + } + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + if p.event.typ != e { + p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ) + p.fail() + } + yaml_event_delete(&p.event) + p.event.typ = yaml_NO_EVENT +} + +// peek peeks at the next event in the event stream, +// puts the results into p.event and returns the event type. +func (p *parser) peek() yaml_event_type_t { + if p.event.typ != yaml_NO_EVENT { + return p.event.typ + } + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + return p.event.typ +} + +func (p *parser) fail() { + var where string + var line int + if p.parser.problem_mark.line != 0 { + line = p.parser.problem_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } else if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + } + if line != 0 { + where = "line " + strconv.Itoa(line) + ": " + } + var msg string + if len(p.parser.problem) > 0 { + msg = p.parser.problem + } else { + msg = "unknown problem parsing YAML content" + } + failf("%s%s", where, msg) +} + +func (p *parser) anchor(n *node, anchor []byte) { + if anchor != nil { + p.doc.anchors[string(anchor)] = n + } +} + +func (p *parser) parse() *node { + p.init() + switch p.peek() { + case yaml_SCALAR_EVENT: + return p.scalar() + case yaml_ALIAS_EVENT: + return p.alias() + case yaml_MAPPING_START_EVENT: + return p.mapping() + case yaml_SEQUENCE_START_EVENT: + return p.sequence() + case yaml_DOCUMENT_START_EVENT: + return p.document() + case yaml_STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer. + return nil + default: + panic("attempted to parse unknown event: " + p.event.typ.String()) + } +} + +func (p *parser) node(kind int) *node { + return &node{ + kind: kind, + line: p.event.start_mark.line, + column: p.event.start_mark.column, + } +} + +func (p *parser) document() *node { + n := p.node(documentNode) + n.anchors = make(map[string]*node) + p.doc = n + p.expect(yaml_DOCUMENT_START_EVENT) + n.children = append(n.children, p.parse()) + p.expect(yaml_DOCUMENT_END_EVENT) + return n +} + +func (p *parser) alias() *node { + n := p.node(aliasNode) + n.value = string(p.event.anchor) + n.alias = p.doc.anchors[n.value] + if n.alias == nil { + failf("unknown anchor '%s' referenced", n.value) + } + p.expect(yaml_ALIAS_EVENT) + return n +} + +func (p *parser) scalar() *node { + n := p.node(scalarNode) + n.value = string(p.event.value) + n.tag = string(p.event.tag) + n.implicit = p.event.implicit + p.anchor(n, p.event.anchor) + p.expect(yaml_SCALAR_EVENT) + return n +} + +func (p *parser) sequence() *node { + n := p.node(sequenceNode) + p.anchor(n, p.event.anchor) + p.expect(yaml_SEQUENCE_START_EVENT) + for p.peek() != yaml_SEQUENCE_END_EVENT { + n.children = append(n.children, p.parse()) + } + p.expect(yaml_SEQUENCE_END_EVENT) + return n +} + +func (p *parser) mapping() *node { + n := p.node(mappingNode) + p.anchor(n, p.event.anchor) + p.expect(yaml_MAPPING_START_EVENT) + for p.peek() != yaml_MAPPING_END_EVENT { + n.children = append(n.children, p.parse(), p.parse()) + } + p.expect(yaml_MAPPING_END_EVENT) + return n +} + +// ---------------------------------------------------------------------------- +// Decoder, unmarshals a node into a provided value. + +type decoder struct { + doc *node + aliases map[*node]bool + mapType reflect.Type + terrors []string + strict bool + + decodeCount int + aliasCount int + aliasDepth int +} + +var ( + mapItemType = reflect.TypeOf(MapItem{}) + durationType = reflect.TypeOf(time.Duration(0)) + defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) + ifaceType = defaultMapType.Elem() + timeType = reflect.TypeOf(time.Time{}) + ptrTimeType = reflect.TypeOf(&time.Time{}) +) + +func newDecoder(strict bool) *decoder { + d := &decoder{mapType: defaultMapType, strict: strict} + d.aliases = make(map[*node]bool) + return d +} + +func (d *decoder) terror(n *node, tag string, out reflect.Value) { + if n.tag != "" { + tag = n.tag + } + value := n.value + if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type())) +} + +func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) { + terrlen := len(d.terrors) + err := u.UnmarshalYAML(func(v interface{}) (err error) { + defer handleErr(&err) + d.unmarshal(n, reflect.ValueOf(v)) + if len(d.terrors) > terrlen { + issues := d.terrors[terrlen:] + d.terrors = d.terrors[:terrlen] + return &TypeError{issues} + } + return nil + }) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +// d.prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// unmarshalling was already done by UnmarshalYAML, and if so whether +// its types unmarshalled appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { + if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Ptr { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + if u, ok := out.Addr().Interface().(Unmarshaler); ok { + good = d.callUnmarshaler(n, u) + return out, true, good + } + } + } + return out, false, false +} + +const ( + // 400,000 decode operations is ~500kb of dense object declarations, or + // ~5kb of dense object declarations with 10000% alias expansion + alias_ratio_range_low = 400000 + + // 4,000,000 decode operations is ~5MB of dense object declarations, or + // ~4.5MB of dense object declarations with 10% alias expansion + alias_ratio_range_high = 4000000 + + // alias_ratio_range is the range over which we scale allowed alias ratios + alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low) +) + +func allowedAliasRatio(decodeCount int) float64 { + switch { + case decodeCount <= alias_ratio_range_low: + // allow 99% to come from alias expansion for small-to-medium documents + return 0.99 + case decodeCount >= alias_ratio_range_high: + // allow 10% to come from alias expansion for very large documents + return 0.10 + default: + // scale smoothly from 99% down to 10% over the range. + // this maps to 396,000 - 400,000 allowed alias-driven decodes over the range. + // 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps). + return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range) + } +} + +func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { + failf("document contains excessive aliasing") + } + switch n.kind { + case documentNode: + return d.document(n, out) + case aliasNode: + return d.alias(n, out) + } + out, unmarshaled, good := d.prepare(n, out) + if unmarshaled { + return good + } + switch n.kind { + case scalarNode: + good = d.scalar(n, out) + case mappingNode: + good = d.mapping(n, out) + case sequenceNode: + good = d.sequence(n, out) + default: + panic("internal error: unknown node kind: " + strconv.Itoa(n.kind)) + } + return good +} + +func (d *decoder) document(n *node, out reflect.Value) (good bool) { + if len(n.children) == 1 { + d.doc = n + d.unmarshal(n.children[0], out) + return true + } + return false +} + +func (d *decoder) alias(n *node, out reflect.Value) (good bool) { + if d.aliases[n] { + // TODO this could actually be allowed in some circumstances. + failf("anchor '%s' value contains itself", n.value) + } + d.aliases[n] = true + d.aliasDepth++ + good = d.unmarshal(n.alias, out) + d.aliasDepth-- + delete(d.aliases, n) + return good +} + +var zeroValue reflect.Value + +func resetMap(out reflect.Value) { + for _, k := range out.MapKeys() { + out.SetMapIndex(k, zeroValue) + } +} + +func (d *decoder) scalar(n *node, out reflect.Value) bool { + var tag string + var resolved interface{} + if n.tag == "" && !n.implicit { + tag = yaml_STR_TAG + resolved = n.value + } else { + tag, resolved = resolve(n.tag, n.value) + if tag == yaml_BINARY_TAG { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + failf("!!binary value contains invalid base64 data") + } + resolved = string(data) + } + } + if resolved == nil { + if out.Kind() == reflect.Map && !out.CanAddr() { + resetMap(out) + } else { + out.Set(reflect.Zero(out.Type())) + } + return true + } + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + // We've resolved to exactly the type we want, so use that. + out.Set(resolvedv) + return true + } + // Perhaps we can use the value as a TextUnmarshaler to + // set its value. + if out.CanAddr() { + u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + if ok { + var text []byte + if tag == yaml_BINARY_TAG { + text = []byte(resolved.(string)) + } else { + // We let any value be unmarshaled into TextUnmarshaler. + // That might be more lax than we'd like, but the + // TextUnmarshaler itself should bowl out any dubious values. + text = []byte(n.value) + } + err := u.UnmarshalText(text) + if err != nil { + fail(err) + } + return true + } + } + switch out.Kind() { + case reflect.String: + if tag == yaml_BINARY_TAG { + out.SetString(resolved.(string)) + return true + } + if resolved != nil { + out.SetString(n.value) + return true + } + case reflect.Interface: + if resolved == nil { + out.Set(reflect.Zero(out.Type())) + } else if tag == yaml_TIMESTAMP_TAG { + // It looks like a timestamp but for backward compatibility + // reasons we set it as a string, so that code that unmarshals + // timestamp-like values into interface{} will continue to + // see a string and not a time.Time. + // TODO(v3) Drop this. + out.Set(reflect.ValueOf(n.value)) + } else { + out.Set(reflect.ValueOf(resolved)) + } + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch resolved := resolved.(type) { + case int: + if !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case int64: + if !out.OverflowInt(resolved) { + out.SetInt(resolved) + return true + } + case uint64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case float64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + return true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case uint64: + if !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case float64: + if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + } + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + return true + } + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + return true + case int64: + out.SetFloat(float64(resolved)) + return true + case uint64: + out.SetFloat(float64(resolved)) + return true + case float64: + out.SetFloat(resolved) + return true + } + case reflect.Struct: + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + case reflect.Ptr: + if out.Type().Elem() == reflect.TypeOf(resolved) { + // TODO DOes this make sense? When is out a Ptr except when decoding a nil value? + elem := reflect.New(out.Type().Elem()) + elem.Elem().Set(reflect.ValueOf(resolved)) + out.Set(elem) + return true + } + } + d.terror(n, tag, out) + return false +} + +func settableValueOf(i interface{}) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { + l := len(n.children) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Array: + if l != out.Len() { + failf("invalid array: want %d elements but got %d", out.Len(), l) + } + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]interface{}, l)) + default: + d.terror(n, yaml_SEQ_TAG, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if ok := d.unmarshal(n.children[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + if out.Kind() != reflect.Array { + out.Set(out.Slice(0, j)) + } + if iface.IsValid() { + iface.Set(out) + } + return true +} + +func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { + switch out.Kind() { + case reflect.Struct: + return d.mappingStruct(n, out) + case reflect.Slice: + return d.mappingSlice(n, out) + case reflect.Map: + // okay + case reflect.Interface: + if d.mapType.Kind() == reflect.Map { + iface := out + out = reflect.MakeMap(d.mapType) + iface.Set(out) + } else { + slicev := reflect.New(d.mapType).Elem() + if !d.mappingSlice(n, slicev) { + return false + } + out.Set(slicev) + return true + } + default: + d.terror(n, yaml_MAP_TAG, out) + return false + } + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + mapType := d.mapType + if outt.Key() == ifaceType && outt.Elem() == ifaceType { + d.mapType = outt + } + + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + } + l := len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + k := reflect.New(kt).Elem() + if d.unmarshal(n.children[i], k) { + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + failf("invalid map key: %#v", k.Interface()) + } + e := reflect.New(et).Elem() + if d.unmarshal(n.children[i+1], e) { + d.setMapIndex(n.children[i+1], out, k, e) + } + } + } + d.mapType = mapType + return true +} + +func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) { + if d.strict && out.MapIndex(k) != zeroValue { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface())) + return + } + out.SetMapIndex(k, v) +} + +func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { + outt := out.Type() + if outt.Elem() != mapItemType { + d.terror(n, yaml_MAP_TAG, out) + return false + } + + mapType := d.mapType + d.mapType = outt + + var slice []MapItem + var l = len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + item := MapItem{} + k := reflect.ValueOf(&item.Key).Elem() + if d.unmarshal(n.children[i], k) { + v := reflect.ValueOf(&item.Value).Elem() + if d.unmarshal(n.children[i+1], v) { + slice = append(slice, item) + } + } + } + out.Set(reflect.ValueOf(slice)) + d.mapType = mapType + return true +} + +func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + name := settableValueOf("") + l := len(n.children) + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) + elemType = inlineMap.Type().Elem() + } + + var doneFields []bool + if d.strict { + doneFields = make([]bool, len(sinfo.FieldsList)) + } + for i := 0; i < l; i += 2 { + ni := n.children[i] + if isMerge(ni) { + d.merge(n.children[i+1], out) + continue + } + if !d.unmarshal(ni, name) { + continue + } + if info, ok := sinfo.FieldsMap[name.String()]; ok { + if d.strict { + if doneFields[info.Id] { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type())) + continue + } + doneFields[info.Id] = true + } + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = out.FieldByIndex(info.Inline) + } + d.unmarshal(n.children[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.children[i+1], value) + d.setMapIndex(n.children[i+1], inlineMap, name, value) + } else if d.strict { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type())) + } + } + return true +} + +func failWantMap() { + failf("map merge requires map or sequence of maps as the value") +} + +func (d *decoder) merge(n *node, out reflect.Value) { + switch n.kind { + case mappingNode: + d.unmarshal(n, out) + case aliasNode: + if n.alias != nil && n.alias.kind != mappingNode { + failWantMap() + } + d.unmarshal(n, out) + case sequenceNode: + // Step backwards as earlier nodes take precedence. + for i := len(n.children) - 1; i >= 0; i-- { + ni := n.children[i] + if ni.kind == aliasNode { + if ni.alias != nil && ni.alias.kind != mappingNode { + failWantMap() + } + } else if ni.kind != mappingNode { + failWantMap() + } + d.unmarshal(ni, out) + } + default: + failWantMap() + } +} + +func isMerge(n *node) bool { + return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG) +} diff --git a/vendor/gopkg.in/yaml.v2/emitterc.go b/vendor/gopkg.in/yaml.v2/emitterc.go new file mode 100644 index 00000000000..a1c2cc52627 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/emitterc.go @@ -0,0 +1,1685 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Flush the buffer if needed. +func flush(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return yaml_emitter_flush(emitter) + } + return true +} + +// Put a character to the output buffer. +func put(emitter *yaml_emitter_t, value byte) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return true +} + +// Put a line break to the output buffer. +func put_break(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + switch emitter.line_break { + case yaml_CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case yaml_LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case yaml_CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + emitter.column = 0 + emitter.line++ + return true +} + +// Copy a character from a string into buffer. +func write(emitter *yaml_emitter_t, s []byte, i *int) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return true +} + +// Write a whole string into buffer. +func write_all(emitter *yaml_emitter_t, s []byte) bool { + for i := 0; i < len(s); { + if !write(emitter, s, &i) { + return false + } + } + return true +} + +// Copy a line break character from a string into buffer. +func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { + if s[*i] == '\n' { + if !put_break(emitter) { + return false + } + *i++ + } else { + if !write(emitter, s, i) { + return false + } + emitter.column = 0 + emitter.line++ + } + return true +} + +// Set an emitter error and return false. +func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_EMITTER_ERROR + emitter.problem = problem + return false +} + +// Emit an event. +func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.events = append(emitter.events, *event) + for !yaml_emitter_need_more_events(emitter) { + event := &emitter.events[emitter.events_head] + if !yaml_emitter_analyze_event(emitter, event) { + return false + } + if !yaml_emitter_state_machine(emitter, event) { + return false + } + yaml_event_delete(event) + emitter.events_head++ + } + return true +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +// +func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].typ { + case yaml_DOCUMENT_START_EVENT: + accumulate = 1 + break + case yaml_SEQUENCE_START_EVENT: + accumulate = 2 + break + case yaml_MAPPING_START_EVENT: + accumulate = 3 + break + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].typ { + case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: + level++ + case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Append a directive to the directives stack. +func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return true +} + +// Increase the indentation level. +func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.best_indent + } else { + emitter.indent = 0 + } + } else if !indentless { + emitter.indent += emitter.best_indent + } + return true +} + +// State dispatcher. +func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { + switch emitter.state { + default: + case yaml_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event) + + case yaml_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, true) + + case yaml_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, false) + + case yaml_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event) + + case yaml_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event) + + case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, true) + + case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, false) + + case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, true) + + case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, false) + + case yaml_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") + } + panic("invalid emitter state") +} + +// Expect STREAM-START. +func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_STREAM_START_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") + } + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = yaml_UTF8_ENCODING + } + } + if emitter.best_indent < 2 || emitter.best_indent > 9 { + emitter.best_indent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == yaml_ANY_BREAK { + emitter.line_break = yaml_LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + + if emitter.encoding != yaml_UTF8_ENCODING { + if !yaml_emitter_write_bom(emitter) { + return false + } + } + emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE + return true +} + +// Expect DOCUMENT-START or STREAM-END. +func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + + if event.typ == yaml_DOCUMENT_START_EVENT { + + if event.version_directive != nil { + if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { + return false + } + } + + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { + return false + } + if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { + return false + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { + return false + } + } + + implicit := event.implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if event.version_directive != nil { + implicit = false + if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if len(event.tag_directives) > 0 { + implicit = false + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { + return false + } + if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if yaml_emitter_check_empty_document(emitter) { + implicit = false + } + if !implicit { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { + return false + } + if emitter.canonical { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE + return true + } + + if event.typ == yaml_STREAM_END_EVENT { + if emitter.open_ended { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_END_STATE + return true + } + + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") +} + +// Expect the root node. +func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) + return yaml_emitter_emit_node(emitter, event, true, false, false, false) +} + +// Expect DOCUMENT-END. +func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_DOCUMENT_END_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !event.implicit { + // [Go] Allocate the slice elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return true +} + +// Expect a flow item node. +func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a flow key node. +func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_MAPPING_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a flow value node. +func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block item node. +func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, emitter.mapping_context && !emitter.indention) { + return false + } + } + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a block key node. +func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if event.typ == yaml_MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block value node. +func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a node. +func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, + root bool, sequence bool, mapping bool, simple_key bool) bool { + + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.typ { + case yaml_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event) + case yaml_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event) + case yaml_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event) + case yaml_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event) + default: + return yaml_emitter_set_emitter_error(emitter, + fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ)) + } +} + +// Expect ALIAS. +func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SCALAR. +func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_select_scalar_style(emitter, event) { + return false + } + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + if !yaml_emitter_process_scalar(emitter) { + return false + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SEQUENCE-START. +func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || + yaml_emitter_check_empty_sequence(emitter) { + emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return true +} + +// Expect MAPPING-START. +func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || + yaml_emitter_check_empty_mapping(emitter) { + emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return true +} + +// Check if the document content is an empty scalar. +func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { + length := 0 + switch emitter.events[emitter.events_head].typ { + case yaml_ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case yaml_SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case yaml_SEQUENCE_START_EVENT: + if !yaml_emitter_check_empty_sequence(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case yaml_MAPPING_START_EVENT: + if !yaml_emitter_check_empty_mapping(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Determine an acceptable scalar style. +func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.implicit && !event.quoted_implicit { + return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") + } + + style := event.scalar_style() + if style == yaml_ANY_SCALAR_STYLE { + style = yaml_PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == yaml_PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if no_tag && !event.implicit { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return true +} + +// Write an anchor. +func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { + if emitter.anchor_data.anchor == nil { + return true + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if !yaml_emitter_write_indicator(emitter, c, true, false, false) { + return false + } + return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) +} + +// Write a tag. +func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return true + } + if len(emitter.tag_data.handle) > 0 { + if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { + return false + } + if len(emitter.tag_data.suffix) > 0 { + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + } + } else { + // [Go] Allocate these slices elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { + return false + } + } + return true +} + +// Write a scalar. +func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { + switch emitter.scalar_data.style { + case yaml_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) + + case yaml_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Check if a %YAML directive is valid. +func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { + if version_directive.major != 1 || version_directive.minor != 1 { + return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") + } + return true +} + +// Check if a %TAG directive is valid. +func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") + } + if handle[0] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") + } + if handle[len(handle)-1] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !is_alpha(handle, i) { + return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") + } + } + if len(prefix) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") + } + return true +} + +// Check if an anchor is valid. +func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !is_alpha(anchor, i) { + problem := "anchor value must contain alphanumerical characters only" + if alias { + problem = "alias value must contain alphanumerical characters only" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return true +} + +// Check if a tag is valid. +func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { + if len(tag) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return true + } + } + emitter.tag_data.suffix = tag + return true +} + +// Check if a scalar is valid. +func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { + var ( + block_indicators = false + flow_indicators = false + line_breaks = false + special_characters = false + + leading_space = false + leading_break = false + trailing_space = false + trailing_break = false + break_space = false + space_break = false + + preceded_by_whitespace = false + followed_by_whitespace = false + previous_space = false + previous_break = false + ) + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return true + } + + if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { + special_characters = true + } + if is_space(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if is_break(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. + preceded_by_whitespace = is_blankz(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return true +} + +// Check if the event data is valid. +func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + switch event.typ { + case yaml_ALIAS_EVENT: + if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { + return false + } + + case yaml_SCALAR_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + if !yaml_emitter_analyze_scalar(emitter, event.value) { + return false + } + + case yaml_SEQUENCE_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + + case yaml_MAPPING_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + } + return true +} + +// Write the BOM character. +func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { + if !flush(emitter) { + return false + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return true +} + +func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { + if !put_break(emitter) { + return false + } + } + for emitter.column < indent { + if !put(emitter, ' ') { + return false + } + } + emitter.whitespace = true + emitter.indention = true + return true +} + +func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, indicator) { + return false + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.open_ended = false + return true +} + +func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = is_alpha(value, i) + } + if must_write { + if !write(emitter, value, &i) { + return false + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if !put(emitter, '%') { + return false + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + + emitter.whitespace = false + emitter.indention = false + if emitter.root_context { + emitter.open_ended = true + } + + return true +} + +func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { + return false + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if value[i] == '\'' { + if !put(emitter, '\'') { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + spaces := false + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { + return false + } + + for i := 0; i < len(value); { + if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || + is_bom(value, i) || is_break(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if !put(emitter, '\\') { + return false + } + + var ok bool + switch v { + case 0x00: + ok = put(emitter, '0') + case 0x07: + ok = put(emitter, 'a') + case 0x08: + ok = put(emitter, 'b') + case 0x09: + ok = put(emitter, 't') + case 0x0A: + ok = put(emitter, 'n') + case 0x0b: + ok = put(emitter, 'v') + case 0x0c: + ok = put(emitter, 'f') + case 0x0d: + ok = put(emitter, 'r') + case 0x1b: + ok = put(emitter, 'e') + case 0x22: + ok = put(emitter, '"') + case 0x5c: + ok = put(emitter, '\\') + case 0x85: + ok = put(emitter, 'N') + case 0xA0: + ok = put(emitter, '_') + case 0x2028: + ok = put(emitter, 'L') + case 0x2029: + ok = put(emitter, 'P') + default: + if v <= 0xFF { + ok = put(emitter, 'x') + w = 2 + } else if v <= 0xFFFF { + ok = put(emitter, 'u') + w = 4 + } else { + ok = put(emitter, 'U') + w = 8 + } + for k := (w - 1) * 4; ok && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + ok = put(emitter, digit+'0') + } else { + ok = put(emitter, digit+'A'-10) + } + } + } + if !ok { + return false + } + spaces = false + } else if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if is_space(value, i+1) { + if !put(emitter, '\\') { + return false + } + } + i += width(value[i]) + } else if !write(emitter, value, &i) { + return false + } + spaces = true + } else { + if !write(emitter, value, &i) { + return false + } + spaces = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { + if is_space(value, 0) || is_break(value, 0) { + indent_hint := []byte{'0' + byte(emitter.best_indent)} + if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { + return false + } + } + + emitter.open_ended = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !is_break(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.open_ended = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if is_break(value, i) { + chomp_hint[0] = '+' + emitter.open_ended = true + } + } + } + if chomp_hint[0] != 0 { + if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { + return false + } + } + return true +} + +func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + + return true +} + +func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for is_break(value, k) { + k += width(value[k]) + } + if !is_blankz(value, k) { + if !put_break(emitter) { + return false + } + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + leading_spaces = is_blank(value, i) + } + if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + emitter.indention = false + breaks = false + } + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go new file mode 100644 index 00000000000..0ee738e11b6 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/encode.go @@ -0,0 +1,390 @@ +package yaml + +import ( + "encoding" + "fmt" + "io" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +// jsonNumber is the interface of the encoding/json.Number datatype. +// Repeating the interface here avoids a dependency on encoding/json, and also +// supports other libraries like jsoniter, which use a similar datatype with +// the same interface. Detecting this interface is useful when dealing with +// structures containing json.Number, which is a string under the hood. The +// encoder should prefer the use of Int64(), Float64() and string(), in that +// order, when encoding this type. +type jsonNumber interface { + Float64() (float64, error) + Int64() (int64, error) + String() string +} + +type encoder struct { + emitter yaml_emitter_t + event yaml_event_t + out []byte + flow bool + // doneInit holds whether the initial stream_start_event has been + // emitted. + doneInit bool +} + +func newEncoder() *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_string(&e.emitter, &e.out) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func newEncoderWithWriter(w io.Writer) *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_writer(&e.emitter, w) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func (e *encoder) init() { + if e.doneInit { + return + } + yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING) + e.emit() + e.doneInit = true +} + +func (e *encoder) finish() { + e.emitter.open_ended = false + yaml_stream_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) destroy() { + yaml_emitter_delete(&e.emitter) +} + +func (e *encoder) emit() { + // This will internally delete the e.event value. + e.must(yaml_emitter_emit(&e.emitter, &e.event)) +} + +func (e *encoder) must(ok bool) { + if !ok { + msg := e.emitter.problem + if msg == "" { + msg = "unknown problem generating YAML content" + } + failf("%s", msg) + } +} + +func (e *encoder) marshalDoc(tag string, in reflect.Value) { + e.init() + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.emit() + e.marshal(tag, in) + yaml_document_end_event_initialize(&e.event, true) + e.emit() +} + +func (e *encoder) marshal(tag string, in reflect.Value) { + if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() { + e.nilv() + return + } + iface := in.Interface() + switch m := iface.(type) { + case jsonNumber: + integer, err := m.Int64() + if err == nil { + // In this case the json.Number is a valid int64 + in = reflect.ValueOf(integer) + break + } + float, err := m.Float64() + if err == nil { + // In this case the json.Number is a valid float64 + in = reflect.ValueOf(float) + break + } + // fallback case - no number could be obtained + in = reflect.ValueOf(m.String()) + case time.Time, *time.Time: + // Although time.Time implements TextMarshaler, + // we don't want to treat it as a string for YAML + // purposes because YAML has special support for + // timestamps. + case Marshaler: + v, err := m.MarshalYAML() + if err != nil { + fail(err) + } + if v == nil { + e.nilv() + return + } + in = reflect.ValueOf(v) + case encoding.TextMarshaler: + text, err := m.MarshalText() + if err != nil { + fail(err) + } + in = reflect.ValueOf(string(text)) + case nil: + e.nilv() + return + } + switch in.Kind() { + case reflect.Interface: + e.marshal(tag, in.Elem()) + case reflect.Map: + e.mapv(tag, in) + case reflect.Ptr: + if in.Type() == ptrTimeType { + e.timev(tag, in.Elem()) + } else { + e.marshal(tag, in.Elem()) + } + case reflect.Struct: + if in.Type() == timeType { + e.timev(tag, in) + } else { + e.structv(tag, in) + } + case reflect.Slice, reflect.Array: + if in.Type().Elem() == mapItemType { + e.itemsv(tag, in) + } else { + e.slicev(tag, in) + } + case reflect.String: + e.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if in.Type() == durationType { + e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String())) + } else { + e.intv(tag, in) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.uintv(tag, in) + case reflect.Float32, reflect.Float64: + e.floatv(tag, in) + case reflect.Bool: + e.boolv(tag, in) + default: + panic("cannot marshal type: " + in.Type().String()) + } +} + +func (e *encoder) mapv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + keys := keyList(in.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + e.marshal("", k) + e.marshal("", in.MapIndex(k)) + } + }) +} + +func (e *encoder) itemsv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem) + for _, item := range slice { + e.marshal("", reflect.ValueOf(item.Key)) + e.marshal("", reflect.ValueOf(item.Value)) + } + }) +} + +func (e *encoder) structv(tag string, in reflect.Value) { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + panic(err) + } + e.mappingv(tag, func() { + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = in.FieldByIndex(info.Inline) + } + if info.OmitEmpty && isZero(value) { + continue + } + e.marshal("", reflect.ValueOf(info.Key)) + e.flow = info.Flow + e.marshal("", value) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } + }) +} + +func (e *encoder) mappingv(tag string, f func()) { + implicit := tag == "" + style := yaml_BLOCK_MAPPING_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_MAPPING_STYLE + } + yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style) + e.emit() + f() + yaml_mapping_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) slicev(tag string, in reflect.Value) { + implicit := tag == "" + style := yaml_BLOCK_SEQUENCE_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + n := in.Len() + for i := 0; i < n; i++ { + e.marshal("", in.Index(i)) + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.emit() +} + +// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. +// +// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported +// in YAML 1.2 and by this package, but these should be marshalled quoted for +// the time being for compatibility with other parsers. +func isBase60Float(s string) (result bool) { + // Fast path. + if s == "" { + return false + } + c := s[0] + if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { + return false + } + // Do the full match. + return base60float.MatchString(s) +} + +// From http://yaml.org/type/float.html, except the regular expression there +// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. +var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +func (e *encoder) stringv(tag string, in reflect.Value) { + var style yaml_scalar_style_t + s := in.String() + canUsePlain := true + switch { + case !utf8.ValidString(s): + if tag == yaml_BINARY_TAG { + failf("explicitly tagged !!binary data must be base64-encoded") + } + if tag != "" { + failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = yaml_BINARY_TAG + s = encodeBase64(s) + case tag == "": + // Check to see if it would resolve to a specific + // tag when encoded unquoted. If it doesn't, + // there's no need to quote it. + rtag, _ := resolve("", s) + canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s) + } + // Note: it's possible for user code to emit invalid YAML + // if they explicitly specify a tag and a string containing + // text that's incompatible with that tag. + switch { + case strings.Contains(s, "\n"): + style = yaml_LITERAL_SCALAR_STYLE + case canUsePlain: + style = yaml_PLAIN_SCALAR_STYLE + default: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + e.emitScalar(s, "", tag, style) +} + +func (e *encoder) boolv(tag string, in reflect.Value) { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) intv(tag string, in reflect.Value) { + s := strconv.FormatInt(in.Int(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) uintv(tag string, in reflect.Value) { + s := strconv.FormatUint(in.Uint(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) timev(tag string, in reflect.Value) { + t := in.Interface().(time.Time) + s := t.Format(time.RFC3339Nano) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) floatv(tag string, in reflect.Value) { + // Issue #352: When formatting, use the precision of the underlying value + precision := 64 + if in.Kind() == reflect.Float32 { + precision = 32 + } + + s := strconv.FormatFloat(in.Float(), 'g', -1, precision) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) nilv() { + e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) { + implicit := tag == "" + e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) + e.emit() +} diff --git a/vendor/gopkg.in/yaml.v2/go.mod b/vendor/gopkg.in/yaml.v2/go.mod new file mode 100644 index 00000000000..1934e876945 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/go.mod @@ -0,0 +1,5 @@ +module "gopkg.in/yaml.v2" + +require ( + "gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405 +) diff --git a/vendor/gopkg.in/yaml.v2/parserc.go b/vendor/gopkg.in/yaml.v2/parserc.go new file mode 100644 index 00000000000..81d05dfe573 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/parserc.go @@ -0,0 +1,1095 @@ +package yaml + +import ( + "bytes" +) + +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +// Peek the next token in the token queue. +func peek_token(parser *yaml_parser_t) *yaml_token_t { + if parser.token_available || yaml_parser_fetch_more_tokens(parser) { + return &parser.tokens[parser.tokens_head] + } + return nil +} + +// Remove the next token from the queue (must be called after peek_token). +func skip_token(parser *yaml_parser_t) { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN + parser.tokens_head++ +} + +// Get the next event. +func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { + // Erase the event object. + *event = yaml_event_t{} + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { + return true + } + + // Generate the next event. + return yaml_parser_state_machine(parser, event) +} + +// Set parser error. +func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +// State dispatcher. +func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { + //trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case yaml_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event) + + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, true) + + case yaml_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, false) + + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event) + + case yaml_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event) + + case yaml_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, true, false) + + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, true, true) + + case yaml_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, false, false) + + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, true) + + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, false) + + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event) + + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, true) + + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, false) + + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, true) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, false) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) + + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, true) + + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, true) + + default: + panic("invalid parser state") + } +} + +// Parse the production: +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ +func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_STREAM_START_TOKEN { + return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) + } + parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + encoding: token.encoding, + } + skip_token(parser) + return true +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* +func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { + + token := peek_token(parser) + if token == nil { + return false + } + + // Parse extra document end indicators. + if !implicit { + for token.typ == yaml_DOCUMENT_END_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && + token.typ != yaml_TAG_DIRECTIVE_TOKEN && + token.typ != yaml_DOCUMENT_START_TOKEN && + token.typ != yaml_STREAM_END_TOKEN { + // Parse an implicit document. + if !yaml_parser_process_directives(parser, nil, nil) { + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_BLOCK_NODE_STATE + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + } else if token.typ != yaml_STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + start_mark := token.start_mark + if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { + return false + } + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_DOCUMENT_START_TOKEN { + yaml_parser_set_parser_error(parser, + "did not find expected ", token.start_mark) + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.end_mark + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: false, + } + skip_token(parser) + + } else { + // Parse the stream end. + parser.state = yaml_PARSE_END_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + } + + return true +} + +// Parse the productions: +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** +// +func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || + token.typ == yaml_TAG_DIRECTIVE_TOKEN || + token.typ == yaml_DOCUMENT_START_TOKEN || + token.typ == yaml_DOCUMENT_END_TOKEN || + token.typ == yaml_STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return yaml_parser_process_empty_scalar(parser, event, + token.start_mark) + } + return yaml_parser_parse_node(parser, event, true, false) +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + start_mark := token.start_mark + end_mark := token.start_mark + + implicit := true + if token.typ == yaml_DOCUMENT_END_TOKEN { + end_mark = token.end_mark + skip_token(parser) + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = yaml_PARSE_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + start_mark: start_mark, + end_mark: end_mark, + implicit: implicit, + } + return true +} + +// Parse the productions: +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** +func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { + //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + anchor: token.value, + } + skip_token(parser) + return true + } + + start_mark := token.start_mark + end_mark := token.start_mark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark yaml_mark_t + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + start_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } else if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + start_mark = token.start_mark + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + tag_suffix = nil + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + return false + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.end_mark + if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + value: token.value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(token.style), + } + skip_token(parser) + return true + } + if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_FLOW_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), + } + return true + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + quoted_implicit: false, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + yaml_parser_set_parser_error_context(parser, context, start_mark, + "did not find expected node content", token.start_mark) + return false +} + +// Parse the productions: +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* +// +func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } else { + parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } + if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.start_mark) +} + +// Parse the productions: +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * +func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && + token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? + } + return true +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +// +func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_KEY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } else { + parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } else if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", context_mark, + "did not find expected key", token.start_mark) +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +// +// +func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + implicit: true, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + skip_token(parser) + return true + } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true +} + +// +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + mark := token.end_mark + skip_token(parser) + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? + } + return true +} + +// Parse the productions: +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * +// +func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } else { + parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true +} + +// Parse the productions: +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * +// +func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { + token := peek_token(parser) + if token == nil { + return false + } + if empty { + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Generate an empty scalar event. +func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: mark, + end_mark: mark, + value: nil, // Empty + implicit: true, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true +} + +var default_tag_directives = []yaml_tag_directive_t{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// Parse directives. +func yaml_parser_process_directives(parser *yaml_parser_t, + version_directive_ref **yaml_version_directive_t, + tag_directives_ref *[]yaml_tag_directive_t) bool { + + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + + token := peek_token(parser) + if token == nil { + return false + } + + for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { + if version_directive != nil { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token.start_mark) + return false + } + if token.major != 1 || token.minor != 1 { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token.start_mark) + return false + } + version_directive = &yaml_version_directive_t{ + major: token.major, + minor: token.minor, + } + } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { + value := yaml_tag_directive_t{ + handle: token.value, + prefix: token.prefix, + } + if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { + return false + } + tag_directives = append(tag_directives, value) + } + + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + + for i := range default_tag_directives { + if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { + return false + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return true +} + +// Append a tag directive to the directives stack. +func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return true +} diff --git a/vendor/gopkg.in/yaml.v2/readerc.go b/vendor/gopkg.in/yaml.v2/readerc.go new file mode 100644 index 00000000000..7c1f5fac3db --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/readerc.go @@ -0,0 +1,412 @@ +package yaml + +import ( + "io" +) + +// Set the reader error and return 0. +func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { + parser.error = yaml_READER_ERROR + parser.problem = problem + parser.problem_offset = offset + parser.problem_value = value + return false +} + +// Byte order marks. +const ( + bom_UTF8 = "\xef\xbb\xbf" + bom_UTF16LE = "\xff\xfe" + bom_UTF16BE = "\xfe\xff" +) + +// Determine the input stream encoding by checking the BOM symbol. If no BOM is +// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. +func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if !yaml_parser_update_raw_buffer(parser) { + return false + } + } + + // Determine the encoding. + buf := parser.raw_buffer + pos := parser.raw_buffer_pos + avail := len(buf) - pos + if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { + parser.encoding = yaml_UTF16LE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { + parser.encoding = yaml_UTF16BE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { + parser.encoding = yaml_UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = yaml_UTF8_ENCODING + } + return true +} + +// Update the raw buffer. +func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { + size_read := 0 + + // Return if the raw buffer is full. + if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { + return true + } + + // Return on EOF. + if parser.eof { + return true + } + + // Move the remaining bytes in the raw buffer to the beginning. + if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { + copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) + } + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] + parser.raw_buffer_pos = 0 + + // Call the read handler to fill the buffer. + size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] + if err == io.EOF { + parser.eof = true + } else if err != nil { + return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) + } + return true +} + +// Ensure that the buffer contains at least `length` characters. +// Return true on success, false on failure. +// +// The length is supposed to be significantly less that the buffer size. +func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { + if parser.read_handler == nil { + panic("read handler must be set") + } + + // [Go] This function was changed to guarantee the requested length size at EOF. + // The fact we need to do this is pretty awful, but the description above implies + // for that to be the case, and there are tests + + // If the EOF flag is set and the raw buffer is empty, do nothing. + if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { + // [Go] ACTUALLY! Read the documentation of this function above. + // This is just broken. To return true, we need to have the + // given length in the buffer. Not doing that means every single + // check that calls this function to make sure the buffer has a + // given length is Go) panicking; or C) accessing invalid memory. + //return true + } + + // Return if the buffer contains enough characters. + if parser.unread >= length { + return true + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == yaml_ANY_ENCODING { + if !yaml_parser_determine_encoding(parser) { + return false + } + } + + // Move the unread characters to the beginning of the buffer. + buffer_len := len(parser.buffer) + if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { + copy(parser.buffer, parser.buffer[parser.buffer_pos:]) + buffer_len -= parser.buffer_pos + parser.buffer_pos = 0 + } else if parser.buffer_pos == buffer_len { + buffer_len = 0 + parser.buffer_pos = 0 + } + + // Open the whole buffer for writing, and cut it before returning. + parser.buffer = parser.buffer[:cap(parser.buffer)] + + // Fill the buffer until it has enough characters. + first := true + for parser.unread < length { + + // Fill the raw buffer if necessary. + if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { + if !yaml_parser_update_raw_buffer(parser) { + parser.buffer = parser.buffer[:buffer_len] + return false + } + } + first = false + + // Decode the raw buffer. + inner: + for parser.raw_buffer_pos != len(parser.raw_buffer) { + var value rune + var width int + + raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos + + // Decode the next character. + switch parser.encoding { + case yaml_UTF8_ENCODING: + // Decode a UTF-8 character. Check RFC 3629 + // (http://www.ietf.org/rfc/rfc3629.txt) for more details. + // + // The following table (taken from the RFC) is used for + // decoding. + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+------------------------------------ + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // Additionally, the characters in the range 0xD800-0xDFFF + // are prohibited as they are reserved for use with UTF-16 + // surrogate pairs. + + // Determine the length of the UTF-8 sequence. + octet := parser.raw_buffer[parser.raw_buffer_pos] + switch { + case octet&0x80 == 0x00: + width = 1 + case octet&0xE0 == 0xC0: + width = 2 + case octet&0xF0 == 0xE0: + width = 3 + case octet&0xF8 == 0xF0: + width = 4 + default: + // The leading octet is invalid. + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser.offset, int(octet)) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser.offset, -1) + } + break inner + } + + // Decode the leading octet. + switch { + case octet&0x80 == 0x00: + value = rune(octet & 0x7F) + case octet&0xE0 == 0xC0: + value = rune(octet & 0x1F) + case octet&0xF0 == 0xE0: + value = rune(octet & 0x0F) + case octet&0xF8 == 0xF0: + value = rune(octet & 0x07) + default: + value = 0 + } + + // Check and decode the trailing octets. + for k := 1; k < width; k++ { + octet = parser.raw_buffer[parser.raw_buffer_pos+k] + + // Check if the octet is valid. + if (octet & 0xC0) != 0x80 { + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser.offset+k, int(octet)) + } + + // Decode the octet. + value = (value << 6) + rune(octet&0x3F) + } + + // Check the length of the sequence against the value. + switch { + case width == 1: + case width == 2 && value >= 0x80: + case width == 3 && value >= 0x800: + case width == 4 && value >= 0x10000: + default: + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser.offset, -1) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser.offset, int(value)) + } + + case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: + var low, high int + if parser.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + low, high = 1, 0 + } + + // The UTF-16 encoding is not as simple as one might + // naively think. Check RFC 2781 + // (http://www.ietf.org/rfc/rfc2781.txt). + // + // Normally, two subsequent bytes describe a Unicode + // character. However a special technique (called a + // surrogate pair) is used for specifying character + // values larger than 0xFFFF. + // + // A surrogate pair consists of two pseudo-characters: + // high surrogate area (0xD800-0xDBFF) + // low surrogate area (0xDC00-0xDFFF) + // + // The following formulas are used for decoding + // and encoding characters using surrogate pairs: + // + // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + // W1 = 110110yyyyyyyyyy + // W2 = 110111xxxxxxxxxx + // + // where U is the character value, W1 is the high surrogate + // area, W2 is the low surrogate area. + + // Check for incomplete UTF-16 character. + if raw_unread < 2 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser.offset, -1) + } + break inner + } + + // Get the character. + value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) + + // Check for unexpected low surrogate area. + if value&0xFC00 == 0xDC00 { + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser.offset, int(value)) + } + + // Check for a high surrogate area. + if value&0xFC00 == 0xD800 { + width = 4 + + // Check for incomplete surrogate pair. + if raw_unread < 4 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser.offset, -1) + } + break inner + } + + // Get the next character. + value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) + + // Check for a low surrogate area. + if value2&0xFC00 != 0xDC00 { + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser.offset+2, int(value2)) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // Check if the character is in the allowed range: + // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + // | [#x10000-#x10FFFF] (32 bit) + switch { + case value == 0x09: + case value == 0x0A: + case value == 0x0D: + case value >= 0x20 && value <= 0x7E: + case value == 0x85: + case value >= 0xA0 && value <= 0xD7FF: + case value >= 0xE000 && value <= 0xFFFD: + case value >= 0x10000 && value <= 0x10FFFF: + default: + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser.offset, int(value)) + } + + // Move the raw pointers. + parser.raw_buffer_pos += width + parser.offset += width + + // Finally put the character into the buffer. + if value <= 0x7F { + // 0000 0000-0000 007F . 0xxxxxxx + parser.buffer[buffer_len+0] = byte(value) + buffer_len += 1 + } else if value <= 0x7FF { + // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) + parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) + buffer_len += 2 + } else if value <= 0xFFFF { + // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) + buffer_len += 3 + } else { + // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) + buffer_len += 4 + } + + parser.unread++ + } + + // On EOF, put NUL into the buffer and return. + if parser.eof { + parser.buffer[buffer_len] = 0 + buffer_len++ + parser.unread++ + break + } + } + // [Go] Read the documentation of this function above. To return true, + // we need to have the given length in the buffer. Not doing that means + // every single check that calls this function to make sure the buffer + // has a given length is Go) panicking; or C) accessing invalid memory. + // This happens here due to the EOF above breaking early. + for buffer_len < length { + parser.buffer[buffer_len] = 0 + buffer_len++ + } + parser.buffer = parser.buffer[:buffer_len] + return true +} diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go new file mode 100644 index 00000000000..4120e0c9160 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -0,0 +1,258 @@ +package yaml + +import ( + "encoding/base64" + "math" + "regexp" + "strconv" + "strings" + "time" +) + +type resolveMapItem struct { + value interface{} + tag string +} + +var resolveTable = make([]byte, 256) +var resolveMap = make(map[string]resolveMapItem) + +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~" { + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + var resolveMapList = []struct { + v interface{} + tag string + l []string + }{ + {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}}, + {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}}, + {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}}, + {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}}, + {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}}, + {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}}, + {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}}, + {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}}, + {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}}, + {"<<", yaml_MERGE_TAG, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +const longTagPrefix = "tag:yaml.org,2002:" + +func shortTag(tag string) string { + // TODO This can easily be made faster and produce less garbage. + if strings.HasPrefix(tag, longTagPrefix) { + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + return longTagPrefix + tag[2:] + } + return tag +} + +func resolvableTag(tag string) bool { + switch tag { + case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG: + return true + } + return false +} + +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) + +func resolve(tag string, in string) (rtag string, out interface{}) { + if !resolvableTag(tag) { + return tag, in + } + + defer func() { + switch tag { + case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG: + return + case yaml_FLOAT_TAG: + if rtag == yaml_INT_TAG { + switch v := out.(type) { + case int64: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + case int: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + } + } + } + failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) + }() + + // Any data is accepted as a !!str or !!binary. + // Otherwise, the prefix is enough of a hint about what it might be. + hint := byte('N') + if in != "" { + hint = resolveTable[in[0]] + } + if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG { + // Handle things we can lookup in a map. + if item, ok := resolveMap[in]; ok { + return item.tag, item.value + } + + // Base 60 floats are a bad idea, were dropped in YAML 1.2, and + // are purposefully unsupported here. They're still quoted on + // the way out for compatibility with other parser, though. + + switch hint { + case 'M': + // We've already checked the map above. + + case '.': + // Not in the map, so maybe a normal float. + floatv, err := strconv.ParseFloat(in, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + + case 'D', 'S': + // Int, float, or timestamp. + // Only try values as a timestamp if the value is unquoted or there's an explicit + // !!timestamp tag. + if tag == "" || tag == yaml_TIMESTAMP_TAG { + t, ok := parseTimestamp(in) + if ok { + return yaml_TIMESTAMP_TAG, t + } + } + + plain := strings.Replace(in, "_", "", -1) + intv, err := strconv.ParseInt(plain, 0, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain, 0, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + if yamlStyleFloat.MatchString(plain) { + floatv, err := strconv.ParseFloat(plain, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + } + if strings.HasPrefix(plain, "0b") { + intv, err := strconv.ParseInt(plain[2:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 2, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + } else if strings.HasPrefix(plain, "-0b") { + intv, err := strconv.ParseInt("-" + plain[3:], 2, 64) + if err == nil { + if true || intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + } + default: + panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") + } + } + return yaml_STR_TAG, in +} + +// encodeBase64 encodes s as base64 that is broken up into multiple lines +// as appropriate for the resulting length. +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} + +// This is a subset of the formats allowed by the regular expression +// defined at http://yaml.org/type/timestamp.html. +var allowedTimestampFormats = []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only + // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" + // from the set of examples. +} + +// parseTimestamp parses s as a timestamp string and +// returns the timestamp and reports whether it succeeded. +// Timestamp formats are defined at http://yaml.org/type/timestamp.html +func parseTimestamp(s string) (time.Time, bool) { + // TODO write code to check all the formats supported by + // http://yaml.org/type/timestamp.html instead of using time.Parse. + + // Quick check: all date formats start with YYYY-. + i := 0 + for ; i < len(s); i++ { + if c := s[i]; c < '0' || c > '9' { + break + } + } + if i != 4 || i == len(s) || s[i] != '-' { + return time.Time{}, false + } + for _, format := range allowedTimestampFormats { + if t, err := time.Parse(format, s); err == nil { + return t, true + } + } + return time.Time{}, false +} diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go new file mode 100644 index 00000000000..0b9bb6030a0 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -0,0 +1,2711 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The correspoding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax pecularities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Ensure that the buffer contains the required number of characters. +// Return true on success, false on failure (reader error or memory error). +func cache(parser *yaml_parser_t, length int) bool { + // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) + return parser.unread >= length || yaml_parser_update_buffer(parser, length) +} + +// Advance the buffer pointer. +func skip(parser *yaml_parser_t) { + parser.mark.index++ + parser.mark.column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +func skip_line(parser *yaml_parser_t) { + if is_crlf(parser.buffer, parser.buffer_pos) { + parser.mark.index += 2 + parser.mark.column = 0 + parser.mark.line++ + parser.unread -= 2 + parser.buffer_pos += 2 + } else if is_break(parser.buffer, parser.buffer_pos) { + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + } +} + +// Copy a character to a string buffer and advance pointers. +func read(parser *yaml_parser_t, s []byte) []byte { + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func read_line(parser *yaml_parser_t, s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + return s +} + +// Get the next token. +func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { + // Erase the token object. + *token = yaml_token_t{} // [Go] Is this necessary? + + // No tokens after STREAM-END or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR { + return true + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if !yaml_parser_fetch_more_tokens(parser) { + return false + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.typ == yaml_STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return true +} + +// Set the scanner error and return false. +func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { + parser.error = yaml_SCANNER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = parser.mark + return false +} + +func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return yaml_parser_set_scanner_error(parser, context, context_mark, problem) +} + +func trace(args ...interface{}) func() { + pargs := append([]interface{}{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]interface{}{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. +func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { + // While we need more tokens to fetch, do it. + for { + if parser.tokens_head != len(parser.tokens) { + // If queue is non-empty, check if any potential simple key may + // occupy the head position. + head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed] + if !ok { + break + } else if valid, ok := yaml_simple_key_is_valid(parser, &parser.simple_keys[head_tok_idx]); !ok { + return false + } else if !valid { + break + } + } + // Fetch the next token. + if !yaml_parser_fetch_next_token(parser) { + return false + } + } + + parser.token_available = true + return true +} + +// The dispatcher for token fetchers. +func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { + // Ensure that the buffer is initialized. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return yaml_parser_fetch_stream_start(parser) + } + + // Eat whitespaces and comments until we reach the next token. + if !yaml_parser_scan_to_next_token(parser) { + return false + } + + // Check the indentation level against the current column. + if !yaml_parser_unroll_indent(parser, parser.mark.column) { + return false + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + // Is it the end of the stream? + if is_z(parser.buffer, parser.buffer_pos) { + return yaml_parser_fetch_stream_end(parser) + } + + // Is it a directive? + if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { + return yaml_parser_fetch_directive(parser) + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) + } + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return yaml_parser_fetch_flow_entry(parser) + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { + return yaml_parser_fetch_block_entry(parser) + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_key(parser) + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_value(parser) + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return yaml_parser_fetch_tag(parser) + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return yaml_parser_fetch_flow_scalar(parser, true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return yaml_parser_fetch_flow_scalar(parser, false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level == 0 && + (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_plain_scalar(parser) + } + + // If we don't determine the token type so far, it is an error. + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser.mark, + "found character that cannot start any token") +} + +func yaml_simple_key_is_valid(parser *yaml_parser_t, simple_key *yaml_simple_key_t) (valid, ok bool) { + if !simple_key.possible { + return false, true + } + + // The 1.2 specification says: + // + // "If the ? indicator is omitted, parsing needs to see past the + // implicit key to recognize it as such. To limit the amount of + // lookahead required, the “:” indicator must appear at most 1024 + // Unicode characters beyond the start of the key. In addition, the key + // is restricted to a single line." + // + if simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index { + // Check if the potential simple key to be removed is required. + if simple_key.required { + return false, yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + return false, true + } + return true, true +} + +// Check if a simple key may start at the current position and add it if +// needed. +func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.column + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + simple_key := yaml_simple_key_t{ + possible: true, + required: required, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + } + + if !yaml_parser_remove_simple_key(parser) { + return false + } + parser.simple_keys[len(parser.simple_keys)-1] = simple_key + parser.simple_keys_by_tok[simple_key.token_number] = len(parser.simple_keys) - 1 + } + return true +} + +// Remove a potential simple key at the current flow level. +func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { + i := len(parser.simple_keys) - 1 + if parser.simple_keys[i].possible { + // If the key is required, it is an error. + if parser.simple_keys[i].required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", parser.simple_keys[i].mark, + "could not find expected ':'") + } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + delete(parser.simple_keys_by_tok, parser.simple_keys[i].token_number) + } + return true +} + +// max_flow_level limits the flow_level +const max_flow_level = 10000 + +// Increase the flow level and resize the simple key list if needed. +func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { + // Reset the simple key on the next level. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{ + possible: false, + required: false, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + }) + + // Increase the flow level. + parser.flow_level++ + if parser.flow_level > max_flow_level { + return yaml_parser_set_scanner_error(parser, + "while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_flow_level)) + } + return true +} + +// Decrease the flow level. +func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { + if parser.flow_level > 0 { + parser.flow_level-- + last := len(parser.simple_keys) - 1 + delete(parser.simple_keys_by_tok, parser.simple_keys[last].token_number) + parser.simple_keys = parser.simple_keys[:last] + } + return true +} + +// max_indents limits the indents stack size +const max_indents = 10000 + +// Push the current indentation level to the stack and set the new level +// the current column is greater than the indentation level. In this case, +// append or insert the specified token into the token queue. +func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + if len(parser.indents) > max_indents { + return yaml_parser_set_scanner_error(parser, + "while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_indents)) + } + + // Create a token and insert it into the queue. + token := yaml_token_t{ + typ: typ, + start_mark: mark, + end_mark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + yaml_insert_token(parser, number, &token) + } + return true +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func yaml_parser_unroll_indent(parser *yaml_parser_t, column int) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + // Loop through the indentation levels in the stack. + for parser.indent > column { + // Create a token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return true +} + +// Initialize the scanner and produce the STREAM-START token. +func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { + + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + parser.simple_keys_by_tok = make(map[int]int) + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_START_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + encoding: parser.encoding, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the STREAM-END token and shut down the scanner. +func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { + + // Force new line. + if parser.mark.column != 0 { + parser.mark.column = 0 + parser.mark.line++ + } + + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := yaml_token_t{} + if !yaml_parser_scan_directive(parser, &token) { + return false + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + skip(parser) + skip(parser) + skip(parser) + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // The indicators '[' and '{' may start a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // Increase the flow level. + if !yaml_parser_increase_flow_level(parser) { + return false + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset any potential simple key on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Decrease the flow level. + if !yaml_parser_decrease_flow_level(parser) { + return false + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-ENTRY token. +func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_FLOW_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the BLOCK-ENTRY token. +func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "block sequence entries are not allowed in this context") + } + // Add the BLOCK-SEQUENCE-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { + return false + } + } else { + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the KEY token. +func yaml_parser_fetch_key(parser *yaml_parser_t) bool { + + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not nessesary simple). + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping keys are not allowed in this context") + } + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the VALUE token. +func yaml_parser_fetch_value(parser *yaml_parser_t) bool { + + simple_key := &parser.simple_keys[len(parser.simple_keys)-1] + + // Have we found a simple key? + if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok { + return false + + } else if valid { + + // Create the KEY token and insert it into the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: simple_key.mark, + end_mark: simple_key.mark, + } + yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if !yaml_parser_roll_indent(parser, simple_key.mark.column, + simple_key.token_number, + yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { + return false + } + + // Remove the simple key. + simple_key.possible = false + delete(parser.simple_keys_by_tok, simple_key.token_number) + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping values are not allowed in this context") + } + + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := yaml_token_t{ + typ: yaml_VALUE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the ALIAS or ANCHOR token. +func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // An anchor or an alias could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_anchor(parser, &token, typ) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the TAG token. +func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { + // A tag could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_tag(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { + // Remove any potential simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_block_scalar(parser, &token, literal) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_flow_scalar(parser, &token, single) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,plain) token. +func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_plain_scalar(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Eat whitespaces and comments until the next token is found. +func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { + skip(parser) + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // If it is a line break, eat it. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return true +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { + // Eat '%'. + start_mark := parser.mark + skip(parser) + + // Scan the directive name. + var name []byte + if !yaml_parser_scan_directive_name(parser, start_mark, &name) { + return false + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { + return false + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_VERSION_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { + return false + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_TAG_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name") + return false + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + return true +} + +// Scan the directive name. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +// +func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { + // Consume the directive name. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + var s []byte + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the name is empty. + if len(s) == 0 { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name") + return false + } + + // Check for an blank character after the name. + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character") + return false + } + *name = s + return true +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^ +func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the major version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { + return false + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character") + } + + skip(parser) + + // Consume the minor version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { + return false + } + return true +} + +const max_number_length = 2 + +// Scan the version number of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^ +// %YAML 1.1 # a comment \n +// ^ +func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { + + // Repeat while the next character is digit. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var value, length int8 + for is_digit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number") + } + value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the number was present. + if length == 0 { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number") + } + *number = value + return true +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a handle. + if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { + return false + } + + // Expect a whitespace. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blank(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace") + return false + } + + // Eat whitespaces. + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a prefix. + if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { + return false + } + + // Expect a whitespace or line break. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break") + return false + } + + *handle = handle_value + *prefix = prefix_value + return true +} + +func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + skip(parser) + + // Consume the value. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == yaml_ANCHOR_TOKEN { + context = "while scanning an anchor" + } + yaml_parser_set_scanner_error(parser, context, start_mark, + "did not find expected alphabetic or numeric character") + return false + } + + // Create a token. + *token = yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + value: s, + } + + return true +} + +/* + * Scan a TAG token. + */ + +func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + skip(parser) + skip(parser) + + // Consume the tag value. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'") + return false + } + + skip(parser) + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { + return false + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + } else { + // It wasn't a handle after all. Scan the rest of the tag. + if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { + return false + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' and the + // suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break") + return false + } + + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_TAG_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + suffix: suffix, + } + return true +} + +// Scan a tag handle. +func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { + // Check the initial '!' character. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] != '!' { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + + var s []byte + + // Copy the '!' character. + s = read(parser, s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = read(parser, s) + } else { + // It's either the '!' tag or not really a tag handle. If it's a %TAG + // directive, it's an error. If it's a tag token, it must be a part of URI. + if directive && string(s) != "!" { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + } + + *handle = s + return true +} + +// Scan a tag. +func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { + //size_t length = head ? strlen((char *)head) : 0 + var s []byte + hasTag := len(head) > 0 + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', + // '%'. + // [Go] Convert this into more reasonable logic. + for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || + parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || + parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || + parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || + parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || + parser.buffer[parser.buffer_pos] == '%' { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { + return false + } + } else { + s = read(parser, s) + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + hasTag = true + } + + if !hasTag { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected tag URI") + return false + } + *uri = s + return true +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { + + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + is_hex(parser.buffer, parser.buffer_pos+1) && + is_hex(parser.buffer, parser.buffer_pos+2)) { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + skip(parser) + skip(parser) + skip(parser) + w-- + } + return true +} + +// Scan a block scalar. +func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + skip(parser) + + // Scan the additional block scalar indicators. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + + // Check for an indentation indicator. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_digit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + + // Get the indentation level and eat the indicator. + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + } + + } else if is_digit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + indent = increment + } + } + + // Scan the leading line breaks and determine the indentation level if needed. + var s, leading_break, trailing_breaks []byte + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + + // Scan the block scalar content. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var leading_blank, trailing_blank bool + for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !is_breakz(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + leading_break = read_line(parser, leading_break) + + // Eat the following indentation spaces and line breaks. + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_LITERAL_SCALAR_STYLE, + } + if !literal { + token.style = yaml_FOLDED_SCALAR_STYLE + } + return true +} + +// Scan indentation spaces and line breaks for a block scalar. Determine the +// indentation level if needed. +func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.mark.column > max_indent { + max_indent = parser.mark.column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected") + } + + // Have we found a non-empty line? + if !is_break(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + // [Go] Should really be returning breaks instead. + *breaks = read_line(parser, *breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return true +} + +// Scan a quoted scalar. +func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { + // Eat the left quote. + start_mark := parser.mark + skip(parser) + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning of the line. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator") + return false + } + + // Check for EOF. + if is_z(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream") + return false + } + + // Consume non-blank characters. + leading_blanks := false + for !is_blankz(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + skip(parser) + skip(parser) + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + skip(parser) + skip_line(parser) + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character") + return false + } + + skip(parser) + skip(parser) + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { + return false + } + for k := 0; k < code_length; k++ { + if !is_hex(parser.buffer, parser.buffer_pos+k) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number") + return false + } + value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code") + return false + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + skip(parser) + } + } + } else { + // It is a non-escaped non-blank character. + s = read(parser, s) + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + skip(parser) + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + return true +} + +// Scan a plain scalar. +func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { + + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + var indent = parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !is_blankz(parser.buffer, parser.buffer_pos) { + + // Check for indicators that may end a plain scalar. + if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && + (parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}')) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = read(parser, s) + + end_mark = parser.mark + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Is it the end? + if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + + // Check for tab characters that abuse indentation. + if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violates indentation") + return false + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.column < indent { + break + } + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/sorter.go b/vendor/gopkg.in/yaml.v2/sorter.go new file mode 100644 index 00000000000..4c45e660a8f --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/sorter.go @@ -0,0 +1,113 @@ +package yaml + +import ( + "reflect" + "unicode" +) + +type keyList []reflect.Value + +func (l keyList) Len() int { return len(l) } +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyList) Less(i, j int) bool { + a := l[i] + b := l[j] + ak := a.Kind() + bk := b.Kind() + for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { + b = b.Elem() + bk = b.Kind() + } + af, aok := keyFloat(a) + bf, bok := keyFloat(b) + if aok && bok { + if af != bf { + return af < bf + } + if ak != bk { + return ak < bk + } + return numLess(a, b) + } + if ak != reflect.String || bk != reflect.String { + return ak < bk + } + ar, br := []rune(a.String()), []rune(b.String()) + for i := 0; i < len(ar) && i < len(br); i++ { + if ar[i] == br[i] { + continue + } + al := unicode.IsLetter(ar[i]) + bl := unicode.IsLetter(br[i]) + if al && bl { + return ar[i] < br[i] + } + if al || bl { + return bl + } + var ai, bi int + var an, bn int64 + if ar[i] == '0' || br[i] == '0' { + for j := i-1; j >= 0 && unicode.IsDigit(ar[j]); j-- { + if ar[j] != '0' { + an = 1 + bn = 1 + break + } + } + } + for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { + an = an*10 + int64(ar[ai]-'0') + } + for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { + bn = bn*10 + int64(br[bi]-'0') + } + if an != bn { + return an < bn + } + if ai != bi { + return ai < bi + } + return ar[i] < br[i] + } + return len(ar) < len(br) +} + +// keyFloat returns a float value for v if it is a number/bool +// and whether it is a number/bool or not. +func keyFloat(v reflect.Value) (f float64, ok bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()), true + case reflect.Float32, reflect.Float64: + return v.Float(), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(v.Uint()), true + case reflect.Bool: + if v.Bool() { + return 1, true + } + return 0, true + } + return 0, false +} + +// numLess returns whether a < b. +// a and b must necessarily have the same kind. +func numLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return a.Int() < b.Int() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Bool: + return !a.Bool() && b.Bool() + } + panic("not a number") +} diff --git a/vendor/gopkg.in/yaml.v2/writerc.go b/vendor/gopkg.in/yaml.v2/writerc.go new file mode 100644 index 00000000000..a2dde608cb7 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/writerc.go @@ -0,0 +1,26 @@ +package yaml + +// Set the writer error and return false. +func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_WRITER_ERROR + emitter.problem = problem + return false +} + +// Flush the output buffer. +func yaml_emitter_flush(emitter *yaml_emitter_t) bool { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return true + } + + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + return true +} diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go new file mode 100644 index 00000000000..89650e293ac --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -0,0 +1,466 @@ +// Package yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/go-yaml/yaml +// +package yaml + +import ( + "errors" + "fmt" + "io" + "reflect" + "strings" + "sync" +) + +// MapSlice encodes and decodes as a YAML map. +// The order of keys is preserved when encoding and decoding. +type MapSlice []MapItem + +// MapItem is an item in a MapSlice. +type MapItem struct { + Key, Value interface{} +} + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. The UnmarshalYAML +// method receives a function that may be called to unmarshal the original +// YAML value into a field or variable. It is safe to call the unmarshal +// function parameter more than once if necessary. +type Unmarshaler interface { + UnmarshalYAML(unmarshal func(interface{}) error) error +} + +// The Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type Marshaler interface { + MarshalYAML() (interface{}, error) +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as out +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary for unmarshalling +// the provided data. The out parameter must not be nil. +// +// The type of the decoded values should be compatible with the respective +// values in out. If one or more values cannot be decoded due to a type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.TypeError is returned with details for all +// missed values. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +// +func Unmarshal(in []byte, out interface{}) (err error) { + return unmarshal(in, out, false) +} + +// UnmarshalStrict is like Unmarshal except that any fields that are found +// in the data that do not have corresponding struct members, or mapping +// keys that are duplicates, will result in +// an error. +func UnmarshalStrict(in []byte, out interface{}) (err error) { + return unmarshal(in, out, true) +} + +// A Decoder reads and decodes YAML values from an input stream. +type Decoder struct { + strict bool + parser *parser +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read +// data from r beyond the YAML values requested. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + parser: newParserFromReader(r), + } +} + +// SetStrict sets whether strict decoding behaviour is enabled when +// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict. +func (dec *Decoder) SetStrict(strict bool) { + dec.strict = strict +} + +// Decode reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (dec *Decoder) Decode(v interface{}) (err error) { + d := newDecoder(dec.strict) + defer handleErr(&err) + node := dec.parser.parse() + if node == nil { + return io.EOF + } + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(node, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +func unmarshal(in []byte, out interface{}, strict bool) (err error) { + defer handleErr(&err) + d := newDecoder(strict) + p := newParser(in) + defer p.destroy() + node := p.parse() + if node != nil { + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + d.unmarshal(node, v) + } + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only marshalled if they are exported (have an upper case +// first letter), and are marshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Zero valued structs will be omitted if all their public +// fields are zero, unless they implement an IsZero +// method (see the IsZeroer interface type), in which +// case the field will be included if that method returns true. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" +// +func Marshal(in interface{}) (out []byte, err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(in)) + e.finish() + out = e.out + return +} + +// An Encoder writes YAML values to an output stream. +type Encoder struct { + encoder *encoder +} + +// NewEncoder returns a new encoder that writes to w. +// The Encoder should be closed after use to flush all data +// to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + encoder: newEncoderWithWriter(w), + } +} + +// Encode writes the YAML encoding of v to the stream. +// If multiple items are encoded to the stream, the +// second and subsequent document will be preceded +// with a "---" document separator, but the first will not. +// +// See the documentation for Marshal for details about the conversion of Go +// values to YAML. +func (e *Encoder) Encode(v interface{}) (err error) { + defer handleErr(&err) + e.encoder.marshalDoc("", reflect.ValueOf(v)) + return nil +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() (err error) { + defer handleErr(&err) + e.encoder.finish() + return nil +} + +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(yamlError); ok { + *err = e.err + } else { + panic(v) + } + } +} + +type yamlError struct { + err error +} + +func fail(err error) { + panic(yamlError{err}) +} + +func failf(format string, args ...interface{}) { + panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) +} + +// A TypeError is returned by Unmarshal when one or more fields in +// the YAML document cannot be properly decoded into the requested +// types. When this error is returned, the value is still +// unmarshaled partially. +type TypeError struct { + Errors []string +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) +} + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from mgo/bson. + +// structInfo holds details for the serialization of fields of +// a given struct. +type structInfo struct { + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + + // InlineMap is the number of the field in the struct that + // contains an ,inline map, or -1 if there's none. + InlineMap int +} + +type fieldInfo struct { + Key string + Num int + OmitEmpty bool + Flow bool + // Id holds the unique field identifier, so we can cheaply + // check for field duplicates without maintaining an extra map. + Id int + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int +} + +var structMap = make(map[reflect.Type]*structInfo) +var fieldMapMutex sync.RWMutex + +func getStructInfo(st reflect.Type) (*structInfo, error) { + fieldMapMutex.RLock() + sinfo, found := structMap[st] + fieldMapMutex.RUnlock() + if found { + return sinfo, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue // Private field + } + + info := fieldInfo{Num: i} + + tag := field.Tag.Get("yaml") + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { + tag = string(field.Tag) + } + if tag == "-" { + continue + } + + inline := false + fields := strings.Split(tag, ",") + if len(fields) > 1 { + for _, flag := range fields[1:] { + switch flag { + case "omitempty": + info.OmitEmpty = true + case "flow": + info.Flow = true + case "inline": + inline = true + default: + return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)) + } + } + tag = fields[0] + } + + if inline { + switch field.Type.Kind() { + case reflect.Map: + if inlineMap >= 0 { + return nil, errors.New("Multiple ,inline maps in struct " + st.String()) + } + if field.Type.Key() != reflect.TypeOf("") { + return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) + } + inlineMap = info.Num + case reflect.Struct: + sinfo, err := getStructInfo(field.Type) + if err != nil { + return nil, err + } + for _, finfo := range sinfo.FieldsList { + if _, found := fieldsMap[finfo.Key]; found { + msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + if finfo.Inline == nil { + finfo.Inline = []int{i, finfo.Num} + } else { + finfo.Inline = append([]int{i}, finfo.Inline...) + } + finfo.Id = len(fieldsList) + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + default: + //return nil, errors.New("Option ,inline needs a struct value or map field") + return nil, errors.New("Option ,inline needs a struct value field") + } + continue + } + + if tag != "" { + info.Key = tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "Duplicated key '" + info.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + + info.Id = len(fieldsList) + fieldsList = append(fieldsList, info) + fieldsMap[info.Key] = info + } + + sinfo = &structInfo{ + FieldsMap: fieldsMap, + FieldsList: fieldsList, + InlineMap: inlineMap, + } + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} + +// IsZeroer is used to check whether an object is zero to +// determine whether it should be omitted when marshaling +// with the omitempty flag. One notable implementation +// is time.Time. +type IsZeroer interface { + IsZero() bool +} + +func isZero(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // Private field + } + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v2/yamlh.go new file mode 100644 index 00000000000..f6a9c8e34b1 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlh.go @@ -0,0 +1,739 @@ +package yaml + +import ( + "fmt" + "io" +) + +// The version directive data. +type yaml_version_directive_t struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// The tag directive data. +type yaml_tag_directive_t struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +type yaml_encoding_t int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + yaml_ANY_ENCODING yaml_encoding_t = iota + + yaml_UTF8_ENCODING // The default UTF-8 encoding. + yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +type yaml_break_t int + +// Line break types. +const ( + // Let the parser choose the break type. + yaml_ANY_BREAK yaml_break_t = iota + + yaml_CR_BREAK // Use CR for line breaks (Mac style). + yaml_LN_BREAK // Use LN for line breaks (Unix style). + yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +type yaml_error_type_t int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + yaml_NO_ERROR yaml_error_type_t = iota + + yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + yaml_READER_ERROR // Cannot read or decode the input stream. + yaml_SCANNER_ERROR // Cannot scan the input stream. + yaml_PARSER_ERROR // Cannot parse the input stream. + yaml_COMPOSER_ERROR // Cannot compose a YAML document. + yaml_WRITER_ERROR // Cannot write to the output stream. + yaml_EMITTER_ERROR // Cannot emit a YAML stream. +) + +// The pointer position. +type yaml_mark_t struct { + index int // The position index. + line int // The position line. + column int // The position column. +} + +// Node Styles + +type yaml_style_t int8 + +type yaml_scalar_style_t yaml_style_t + +// Scalar styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota + + yaml_PLAIN_SCALAR_STYLE // The plain scalar style. + yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + yaml_LITERAL_SCALAR_STYLE // The literal scalar style. + yaml_FOLDED_SCALAR_STYLE // The folded scalar style. +) + +type yaml_sequence_style_t yaml_style_t + +// Sequence styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota + + yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. + yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +type yaml_mapping_style_t yaml_style_t + +// Mapping styles. +const ( + // Let the emitter choose the style. + yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota + + yaml_BLOCK_MAPPING_STYLE // The block mapping style. + yaml_FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +type yaml_token_type_t int + +// Token types. +const ( + // An empty token. + yaml_NO_TOKEN yaml_token_type_t = iota + + yaml_STREAM_START_TOKEN // A STREAM-START token. + yaml_STREAM_END_TOKEN // A STREAM-END token. + + yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. + yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + yaml_BLOCK_END_TOKEN // A BLOCK-END token. + + yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + yaml_KEY_TOKEN // A KEY token. + yaml_VALUE_TOKEN // A VALUE token. + + yaml_ALIAS_TOKEN // An ALIAS token. + yaml_ANCHOR_TOKEN // An ANCHOR token. + yaml_TAG_TOKEN // A TAG token. + yaml_SCALAR_TOKEN // A SCALAR token. +) + +func (tt yaml_token_type_t) String() string { + switch tt { + case yaml_NO_TOKEN: + return "yaml_NO_TOKEN" + case yaml_STREAM_START_TOKEN: + return "yaml_STREAM_START_TOKEN" + case yaml_STREAM_END_TOKEN: + return "yaml_STREAM_END_TOKEN" + case yaml_VERSION_DIRECTIVE_TOKEN: + return "yaml_VERSION_DIRECTIVE_TOKEN" + case yaml_TAG_DIRECTIVE_TOKEN: + return "yaml_TAG_DIRECTIVE_TOKEN" + case yaml_DOCUMENT_START_TOKEN: + return "yaml_DOCUMENT_START_TOKEN" + case yaml_DOCUMENT_END_TOKEN: + return "yaml_DOCUMENT_END_TOKEN" + case yaml_BLOCK_SEQUENCE_START_TOKEN: + return "yaml_BLOCK_SEQUENCE_START_TOKEN" + case yaml_BLOCK_MAPPING_START_TOKEN: + return "yaml_BLOCK_MAPPING_START_TOKEN" + case yaml_BLOCK_END_TOKEN: + return "yaml_BLOCK_END_TOKEN" + case yaml_FLOW_SEQUENCE_START_TOKEN: + return "yaml_FLOW_SEQUENCE_START_TOKEN" + case yaml_FLOW_SEQUENCE_END_TOKEN: + return "yaml_FLOW_SEQUENCE_END_TOKEN" + case yaml_FLOW_MAPPING_START_TOKEN: + return "yaml_FLOW_MAPPING_START_TOKEN" + case yaml_FLOW_MAPPING_END_TOKEN: + return "yaml_FLOW_MAPPING_END_TOKEN" + case yaml_BLOCK_ENTRY_TOKEN: + return "yaml_BLOCK_ENTRY_TOKEN" + case yaml_FLOW_ENTRY_TOKEN: + return "yaml_FLOW_ENTRY_TOKEN" + case yaml_KEY_TOKEN: + return "yaml_KEY_TOKEN" + case yaml_VALUE_TOKEN: + return "yaml_VALUE_TOKEN" + case yaml_ALIAS_TOKEN: + return "yaml_ALIAS_TOKEN" + case yaml_ANCHOR_TOKEN: + return "yaml_ANCHOR_TOKEN" + case yaml_TAG_TOKEN: + return "yaml_TAG_TOKEN" + case yaml_SCALAR_TOKEN: + return "yaml_SCALAR_TOKEN" + } + return "" +} + +// The token structure. +type yaml_token_t struct { + // The token type. + typ yaml_token_type_t + + // The start/end of the token. + start_mark, end_mark yaml_mark_t + + // The stream encoding (for yaml_STREAM_START_TOKEN). + encoding yaml_encoding_t + + // The alias/anchor/scalar value or tag/tag directive handle + // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). + value []byte + + // The tag suffix (for yaml_TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar style (for yaml_SCALAR_TOKEN). + style yaml_scalar_style_t + + // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +type yaml_event_type_t int8 + +// Event types. +const ( + // An empty event. + yaml_NO_EVENT yaml_event_type_t = iota + + yaml_STREAM_START_EVENT // A STREAM-START event. + yaml_STREAM_END_EVENT // A STREAM-END event. + yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. + yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. + yaml_ALIAS_EVENT // An ALIAS event. + yaml_SCALAR_EVENT // A SCALAR event. + yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. + yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. + yaml_MAPPING_START_EVENT // A MAPPING-START event. + yaml_MAPPING_END_EVENT // A MAPPING-END event. +) + +var eventStrings = []string{ + yaml_NO_EVENT: "none", + yaml_STREAM_START_EVENT: "stream start", + yaml_STREAM_END_EVENT: "stream end", + yaml_DOCUMENT_START_EVENT: "document start", + yaml_DOCUMENT_END_EVENT: "document end", + yaml_ALIAS_EVENT: "alias", + yaml_SCALAR_EVENT: "scalar", + yaml_SEQUENCE_START_EVENT: "sequence start", + yaml_SEQUENCE_END_EVENT: "sequence end", + yaml_MAPPING_START_EVENT: "mapping start", + yaml_MAPPING_END_EVENT: "mapping end", +} + +func (e yaml_event_type_t) String() string { + if e < 0 || int(e) >= len(eventStrings) { + return fmt.Sprintf("unknown event %d", e) + } + return eventStrings[e] +} + +// The event structure. +type yaml_event_t struct { + + // The event type. + typ yaml_event_type_t + + // The start and end of the event. + start_mark, end_mark yaml_mark_t + + // The document encoding (for yaml_STREAM_START_EVENT). + encoding yaml_encoding_t + + // The version directive (for yaml_DOCUMENT_START_EVENT). + version_directive *yaml_version_directive_t + + // The list of tag directives (for yaml_DOCUMENT_START_EVENT). + tag_directives []yaml_tag_directive_t + + // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). + anchor []byte + + // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + tag []byte + + // The scalar value (for yaml_SCALAR_EVENT). + value []byte + + // Is the document start/end indicator implicit, or the tag optional? + // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). + implicit bool + + // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). + quoted_implicit bool + + // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + style yaml_style_t +} + +func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } +func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } +func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } + +// Nodes + +const ( + yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + yaml_BINARY_TAG = "tag:yaml.org,2002:binary" + yaml_MERGE_TAG = "tag:yaml.org,2002:merge" + + yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. + yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. + yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. +) + +type yaml_node_type_t int + +// Node types. +const ( + // An empty node. + yaml_NO_NODE yaml_node_type_t = iota + + yaml_SCALAR_NODE // A scalar node. + yaml_SEQUENCE_NODE // A sequence node. + yaml_MAPPING_NODE // A mapping node. +) + +// An element of a sequence node. +type yaml_node_item_t int + +// An element of a mapping node. +type yaml_node_pair_t struct { + key int // The key of the element. + value int // The value of the element. +} + +// The node structure. +type yaml_node_t struct { + typ yaml_node_type_t // The node type. + tag []byte // The node tag. + + // The node data. + + // The scalar parameters (for yaml_SCALAR_NODE). + scalar struct { + value []byte // The scalar value. + length int // The length of the scalar value. + style yaml_scalar_style_t // The scalar style. + } + + // The sequence parameters (for YAML_SEQUENCE_NODE). + sequence struct { + items_data []yaml_node_item_t // The stack of sequence items. + style yaml_sequence_style_t // The sequence style. + } + + // The mapping parameters (for yaml_MAPPING_NODE). + mapping struct { + pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). + pairs_start *yaml_node_pair_t // The beginning of the stack. + pairs_end *yaml_node_pair_t // The end of the stack. + pairs_top *yaml_node_pair_t // The top of the stack. + style yaml_mapping_style_t // The mapping style. + } + + start_mark yaml_mark_t // The beginning of the node. + end_mark yaml_mark_t // The end of the node. + +} + +// The document structure. +type yaml_document_t struct { + + // The document nodes. + nodes []yaml_node_t + + // The version directive. + version_directive *yaml_version_directive_t + + // The list of tag directives. + tag_directives_data []yaml_tag_directive_t + tag_directives_start int // The beginning of the tag directives list. + tag_directives_end int // The end of the tag directives list. + + start_implicit int // Is the document start indicator implicit? + end_implicit int // Is the document end indicator implicit? + + // The start/end of the document. + start_mark, end_mark yaml_mark_t +} + +// The prototype of a read handler. +// +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. +// +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. +// +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. +type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) + +// This structure holds information about a potential simple key. +type yaml_simple_key_t struct { + possible bool // Is a simple key possible? + required bool // Is a simple key required? + token_number int // The number of the token. + mark yaml_mark_t // The position mark. +} + +// The states of the parser. +type yaml_parser_state_t int + +const ( + yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota + + yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. + yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. + yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. + yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + yaml_PARSE_END_STATE // Expect nothing. +) + +func (ps yaml_parser_state_t) String() string { + switch ps { + case yaml_PARSE_STREAM_START_STATE: + return "yaml_PARSE_STREAM_START_STATE" + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_START_STATE: + return "yaml_PARSE_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return "yaml_PARSE_DOCUMENT_CONTENT_STATE" + case yaml_PARSE_DOCUMENT_END_STATE: + return "yaml_PARSE_DOCUMENT_END_STATE" + case yaml_PARSE_BLOCK_NODE_STATE: + return "yaml_PARSE_BLOCK_NODE_STATE" + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" + case yaml_PARSE_FLOW_NODE_STATE: + return "yaml_PARSE_FLOW_NODE_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case yaml_PARSE_END_STATE: + return "yaml_PARSE_END_STATE" + } + return "" +} + +// This structure holds aliases data. +type yaml_alias_data_t struct { + anchor []byte // The anchor. + index int // The node id. + mark yaml_mark_t // The anchor mark. +} + +// The parser structure. +// +// All members are internal. Manage the structure using the +// yaml_parser_ family of functions. +type yaml_parser_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + + problem string // Error description. + + // The byte about which the problem occurred. + problem_offset int + problem_value int + problem_mark yaml_mark_t + + // The error context. + context string + context_mark yaml_mark_t + + // Reader stuff + + read_handler yaml_read_handler_t // Read handler. + + input_reader io.Reader // File input data. + input []byte // String input data. + input_pos int + + eof bool // EOF flag + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + unread int // The number of unread characters in the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The input encoding. + + offset int // The offset of the current position (in bytes). + mark yaml_mark_t // The mark of the current position. + + // Scanner stuff + + stream_start_produced bool // Have we started to scan the input stream? + stream_end_produced bool // Have we reached the end of the input stream? + + flow_level int // The number of unclosed '[' and '{' indicators. + + tokens []yaml_token_t // The tokens queue. + tokens_head int // The head of the tokens queue. + tokens_parsed int // The number of tokens fetched from the queue. + token_available bool // Does the tokens queue contain a token ready for dequeueing. + + indent int // The current indentation level. + indents []int // The indentation levels stack. + + simple_key_allowed bool // May a simple key occur at the current position? + simple_keys []yaml_simple_key_t // The stack of simple keys. + simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number + + // Parser stuff + + state yaml_parser_state_t // The current parser state. + states []yaml_parser_state_t // The parser states stack. + marks []yaml_mark_t // The stack of marks. + tag_directives []yaml_tag_directive_t // The list of TAG directives. + + // Dumper stuff + + aliases []yaml_alias_data_t // The alias data. + + document *yaml_document_t // The currently parsed document. +} + +// Emitter Definitions + +// The prototype of a write handler. +// +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. +// +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. +// +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. +// +type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error + +type yaml_emitter_state_t int + +// The emitter states. +const ( + // Expect STREAM-START. + yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota + + yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + yaml_EMIT_END_STATE // Expect nothing. +) + +// The emitter structure. +// +// All members are internal. Manage the structure using the @c yaml_emitter_ +// family of functions. +type yaml_emitter_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + problem string // Error description. + + // Writer stuff + + write_handler yaml_write_handler_t // Write handler. + + output_buffer *[]byte // String output data. + output_writer io.Writer // File output data. + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + best_indent int // The number of indentation spaces. + best_width int // The preferred width of the output lines. + unicode bool // Allow unescaped non-ASCII characters? + line_break yaml_break_t // The preferred line break. + + state yaml_emitter_state_t // The current emitter state. + states []yaml_emitter_state_t // The stack of states. + + events []yaml_event_t // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []yaml_tag_directive_t // The list of tag directives. + + indent int // The current indentation level. + + flow_level int // The current flow level. + + root_context bool // Is it the document root context? + sequence_context bool // Is it a sequence context? + mapping_context bool // Is it a mapping context? + simple_key_context bool // Is it a simple mapping key context? + + line int // The current line. + column int // The current column. + whitespace bool // If the last character was a whitespace? + indention bool // If the last character was an indentation character (' ', '-', '?', ':')? + open_ended bool // If an explicit document end is required? + + // Anchor analysis. + anchor_data struct { + anchor []byte // The anchor value. + alias bool // Is it an alias? + } + + // Tag analysis. + tag_data struct { + handle []byte // The tag handle. + suffix []byte // The tag suffix. + } + + // Scalar analysis. + scalar_data struct { + value []byte // The scalar value. + multiline bool // Does the scalar contain line breaks? + flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? + block_plain_allowed bool // Can the scalar be expressed in the block plain style? + single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? + block_allowed bool // Can the scalar be expressed in the literal or folded styles? + style yaml_scalar_style_t // The output style. + } + + // Dumper stuff + + opened bool // If the stream was already opened? + closed bool // If the stream was already closed? + + // The information associated with the document nodes. + anchors *struct { + references int // The number of references. + anchor int // The anchor id. + serialized bool // If the node has been emitted? + } + + last_anchor_id int // The last assigned anchor id. + + document *yaml_document_t // The currently emitted document. +} diff --git a/vendor/gopkg.in/yaml.v2/yamlprivateh.go b/vendor/gopkg.in/yaml.v2/yamlprivateh.go new file mode 100644 index 00000000000..8110ce3c37a --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlprivateh.go @@ -0,0 +1,173 @@ +package yaml + +const ( + // The size of the input raw buffer. + input_raw_buffer_size = 512 + + // The size of the input buffer. + // It should be possible to decode the whole raw buffer. + input_buffer_size = input_raw_buffer_size * 3 + + // The size of the output buffer. + output_buffer_size = 128 + + // The size of the output raw buffer. + // It should be possible to encode the whole output buffer. + output_raw_buffer_size = (output_buffer_size*2 + 2) + + // The size of other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func is_alpha(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' +} + +// Check if the character at the specified position is a digit. +func is_digit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func as_digit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func is_hex(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' +} + +// Get the value of a hex-digit. +func as_hex(b []byte, i int) int { + bi := b[i] + if bi >= 'A' && bi <= 'F' { + return int(bi) - 'A' + 10 + } + if bi >= 'a' && bi <= 'f' { + return int(bi) - 'a' + 10 + } + return int(bi) - '0' +} + +// Check if the character is ASCII. +func is_ascii(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func is_printable(b []byte, i int) bool { + return ((b[i] == 0x0A) || // . == #x0A + (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E + (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF + (b[i] > 0xC2 && b[i] < 0xED) || + (b[i] == 0xED && b[i+1] < 0xA0) || + (b[i] == 0xEE) || + (b[i] == 0xEF && // #xE000 <= . <= #xFFFD + !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF + !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) +} + +// Check if the character at the specified position is NUL. +func is_z(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func is_bom(b []byte, i int) bool { + return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF +} + +// Check if the character at the specified position is space. +func is_space(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func is_tab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func is_blank(b []byte, i int) bool { + //return is_space(b, i) || is_tab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func is_break(b []byte, i int) bool { + return (b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) +} + +func is_crlf(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func is_breakz(b []byte, i int) bool { + //return is_break(b, i) || is_z(b, i) + return ( // is_break: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + // is_z: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func is_spacez(b []byte, i int) bool { + //return is_space(b, i) || is_breakz(b, i) + return ( // is_space: + b[i] == ' ' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Check if the character is a line break, space, tab, or NUL. +func is_blankz(b []byte, i int) bool { + //return is_blank(b, i) || is_breakz(b, i) + return ( // is_blank: + b[i] == ' ' || b[i] == '\t' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Determine the width of the character. +func width(b byte) int { + // Don't replace these by a switch without first + // confirming that it is being inlined. + if b&0x80 == 0x00 { + return 1 + } + if b&0xE0 == 0xC0 { + return 2 + } + if b&0xF0 == 0xE0 { + return 3 + } + if b&0xF8 == 0xF0 { + return 4 + } + return 0 + +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 00000000000..c26047188b0 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,15 @@ +# github.com/blang/semver v3.5.1+incompatible +github.com/blang/semver +# github.com/elastic/go-ucfg v0.8.4-0.20200415140258-1232bd4774a6 +github.com/elastic/go-ucfg +github.com/elastic/go-ucfg/parse +github.com/elastic/go-ucfg/yaml +# github.com/elastic/package-registry v0.2.1-0.20200505085109-20f433bd57ec +github.com/elastic/package-registry/util +# github.com/magefile/mage v1.9.0 +github.com/magefile/mage/mg +github.com/magefile/mage/sh +# github.com/pkg/errors v0.9.1 +github.com/pkg/errors +# gopkg.in/yaml.v2 v2.2.8 +gopkg.in/yaml.v2