├── .circleci ├── config.yml └── scripts │ └── upload-artifacts ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── api ├── .openapi-generator-ignore ├── README.md ├── api_authorizations_api_tokens.gen.go ├── api_backup.gen.go ├── api_bucket_schemas.gen.go ├── api_buckets.gen.go ├── api_config.gen.go ├── api_dashboards.gen.go ├── api_dbrps.gen.go ├── api_delete.gen.go ├── api_health.gen.go ├── api_invokable_scripts.gen.go ├── api_legacy_authorizations.gen.go ├── api_legacy_query.gen.go ├── api_legacy_write.gen.go ├── api_organizations.gen.go ├── api_ping.gen.go ├── api_query.gen.go ├── api_remote_connections.gen.go ├── api_replications.gen.go ├── api_resources.gen.go ├── api_restore.gen.go ├── api_secrets.gen.go ├── api_setup.gen.go ├── api_signin.gen.go ├── api_stacks.gen.go ├── api_tasks.gen.go ├── api_telegrafs.gen.go ├── api_templates.gen.go ├── api_users.gen.go ├── api_write.gen.go ├── client.gen.go ├── configuration.gen.go ├── configuration_default.go ├── contract │ ├── README.md │ ├── cli-extras.yml │ ├── cli.yml │ └── overrides │ │ ├── paths │ │ ├── backup_metadata.yml │ │ ├── dashboards.yml │ │ ├── query.yml │ │ ├── scripts_scriptID_invoke.yml │ │ ├── stacks.yml │ │ ├── stacks_stack_id.yml │ │ ├── telegrafs_telegrafID.yml │ │ ├── templates_apply.yml │ │ └── templates_export.yml │ │ └── schemas │ │ ├── Extern.yml │ │ ├── Query.yml │ │ ├── ScriptInvocationParams.yml │ │ ├── Stack.yml │ │ ├── StackEvent.yml │ │ ├── StackEventResource.yml │ │ ├── StackEventResourceAssociation.yml │ │ ├── StackPatchRequest.yml │ │ ├── StackPatchRequestResource.yml │ │ ├── StackPostRequest.yml │ │ ├── Stacks.yml │ │ ├── Template.yml │ │ ├── TemplateApply.yml │ │ ├── TemplateApplyAction.yml │ │ ├── TemplateApplyActionKind.yml │ │ ├── TemplateApplyRemoteRef.yml │ │ ├── TemplateApplyTemplate.yml │ │ ├── TemplateEntry.yml │ │ ├── TemplateEnvReference.yml │ │ ├── TemplateExport.yml │ │ ├── TemplateSummary.yml │ │ ├── TemplateSummaryBucket.yml │ │ ├── TemplateSummaryCheck.yml │ │ ├── TemplateSummaryCommon.yml │ │ ├── TemplateSummaryCore.yml │ │ ├── TemplateSummaryDashboard.yml │ │ ├── TemplateSummaryDiff.yml │ │ ├── TemplateSummaryDiffBucket.yml │ │ ├── TemplateSummaryDiffBucketFields.yml │ │ ├── TemplateSummaryDiffCheck.yml │ │ ├── TemplateSummaryDiffCheckFields.yml │ │ ├── TemplateSummaryDiffDashboard.yml │ │ ├── TemplateSummaryDiffDashboardFields.yml │ │ ├── TemplateSummaryDiffLabel.yml │ │ ├── TemplateSummaryDiffLabelFields.yml │ │ ├── TemplateSummaryDiffLabelMapping.yml │ │ ├── TemplateSummaryDiffNotificationEndpoint.yml │ │ ├── TemplateSummaryDiffNotificationEndpointFields.yml │ │ ├── TemplateSummaryDiffNotificationRule.yml │ │ ├── TemplateSummaryDiffNotificationRuleFields.yml │ │ ├── TemplateSummaryDiffTask.yml │ │ ├── TemplateSummaryDiffTaskFields.yml │ │ ├── TemplateSummaryDiffTelegraf.yml │ │ ├── TemplateSummaryDiffVariable.yml │ │ ├── TemplateSummaryDiffVariableFields.yml │ │ ├── TemplateSummaryError.yml │ │ ├── TemplateSummaryErrors.yml │ │ ├── TemplateSummaryLabel.yml │ │ ├── TemplateSummaryLabelMapping.yml │ │ ├── TemplateSummaryNotificationEndpoint.yml │ │ ├── TemplateSummaryNotificationRule.yml │ │ ├── TemplateSummaryResources.yml │ │ ├── TemplateSummaryTask.yml │ │ ├── TemplateSummaryTelegraf.yml │ │ ├── TemplateSummaryTelegrafConfig.yml │ │ ├── TemplateSummaryVariable.yml │ │ └── TemplateSummaryVariableArgs.yml ├── error.go ├── extras │ ├── .openapi-generator-ignore │ ├── README.md │ ├── model_resource_enum_cloud.gen.go │ ├── model_resource_enum_oss.gen.go │ └── utils.gen.go ├── gunzip.go ├── gunzip_test.go ├── model_add_resource_member_request_body.gen.go ├── model_authorization.gen.go ├── model_authorization_all_of.gen.go ├── model_authorization_all_of_links.gen.go ├── model_authorization_post_request.gen.go ├── model_authorization_post_request_all_of.gen.go ├── model_authorization_update_request.gen.go ├── model_authorizations.gen.go ├── model_bucket.gen.go ├── model_bucket_links.gen.go ├── model_bucket_metadata_manifest.gen.go ├── model_bucket_shard_mapping.gen.go ├── model_buckets.gen.go ├── model_cell.gen.go ├── model_cell_links.gen.go ├── model_column_data_type.gen.go ├── model_column_semantic_type.gen.go ├── model_config.gen.go ├── model_create_dashboard_request.gen.go ├── model_dashboard.gen.go ├── model_dashboard_all_of.gen.go ├── model_dashboard_all_of_links.gen.go ├── model_dashboard_all_of_meta.gen.go ├── model_dashboards.gen.go ├── model_dbrp.gen.go ├── model_dbrp_create.gen.go ├── model_dbrp_get.gen.go ├── model_dbrp_update.gen.go ├── model_dbrps.gen.go ├── model_delete_predicate_request.gen.go ├── model_dialect.gen.go ├── model_error.gen.go ├── model_error_code.gen.go ├── model_extern.gen.go ├── model_health_check.gen.go ├── model_health_check_status.gen.go ├── model_influxql_json_response.gen.go ├── model_influxql_json_response_results.gen.go ├── model_influxql_json_response_series.gen.go ├── model_inline_response_200.gen.go ├── model_label.gen.go ├── model_legacy_authorization_post_request.gen.go ├── model_legacy_authorization_post_request_all_of.gen.go ├── model_line_protocol_error.gen.go ├── model_line_protocol_error_code.gen.go ├── model_line_protocol_length_error.gen.go ├── model_line_protocol_length_error_code.gen.go ├── model_links.gen.go ├── model_log_event.gen.go ├── model_logs.gen.go ├── model_measurement_schema.gen.go ├── model_measurement_schema_column.gen.go ├── model_measurement_schema_create_request.gen.go ├── model_measurement_schema_list.gen.go ├── model_measurement_schema_update_request.gen.go ├── model_metadata_backup.gen.go ├── model_onboarding_request.gen.go ├── model_onboarding_response.gen.go ├── model_organization.gen.go ├── model_organization_links.gen.go ├── model_organizations.gen.go ├── model_password_reset_body.gen.go ├── model_patch_bucket_request.gen.go ├── model_patch_organization_request.gen.go ├── model_patch_retention_rule.gen.go ├── model_permission.gen.go ├── model_permission_resource.gen.go ├── model_post_bucket_request.gen.go ├── model_post_organization_request.gen.go ├── model_post_restore_kv_response.gen.go ├── model_query.gen.go ├── model_remote_connection.gen.go ├── model_remote_connection_creation_request.gen.go ├── model_remote_connections.gen.go ├── model_remote_connenction_update_request.gen.go ├── model_replication.gen.go ├── model_replication_creation_request.gen.go ├── model_replication_update_request.gen.go ├── model_replications.gen.go ├── model_resource_member.gen.go ├── model_resource_member_all_of.gen.go ├── model_resource_members.gen.go ├── model_resource_owner.gen.go ├── model_resource_owner_all_of.gen.go ├── model_resource_owners.gen.go ├── model_restored_bucket_mappings.gen.go ├── model_retention_policy_manifest.gen.go ├── model_retention_rule.gen.go ├── model_run.gen.go ├── model_run_links.gen.go ├── model_run_manually.gen.go ├── model_runs.gen.go ├── model_schema_type.gen.go ├── model_script.gen.go ├── model_script_create_request.gen.go ├── model_script_invocation_params.gen.go ├── model_script_language.gen.go ├── model_script_update_request.gen.go ├── model_scripts.gen.go ├── model_secret_keys.gen.go ├── model_secret_keys_response.gen.go ├── model_secret_keys_response_all_of.gen.go ├── model_secret_keys_response_all_of_links.gen.go ├── model_shard_group_manifest.gen.go ├── model_shard_manifest.gen.go ├── model_shard_owner.gen.go ├── model_stack.gen.go ├── model_stack_event.gen.go ├── model_stack_event_resource.gen.go ├── model_stack_event_resource_association.gen.go ├── model_stack_event_resource_links.gen.go ├── model_stack_patch_request.gen.go ├── model_stack_patch_request_resource.gen.go ├── model_stack_post_request.gen.go ├── model_stacks.gen.go ├── model_subscription_manifest.gen.go ├── model_task.gen.go ├── model_task_create_request.gen.go ├── model_task_links.gen.go ├── model_task_status_type.gen.go ├── model_task_update_request.gen.go ├── model_tasks.gen.go ├── model_telegraf.gen.go ├── model_telegraf_all_of.gen.go ├── model_telegraf_all_of_links.gen.go ├── model_telegraf_plugin_request.gen.go ├── model_telegraf_plugin_request_plugins.gen.go ├── model_telegraf_request.gen.go ├── model_telegraf_request_metadata.gen.go ├── model_telegrafs.gen.go ├── model_template_apply.gen.go ├── model_template_apply_action.gen.go ├── model_template_apply_action_kind.gen.go ├── model_template_apply_action_properties.gen.go ├── model_template_apply_remote_ref.gen.go ├── model_template_apply_template.gen.go ├── model_template_entry.gen.go ├── model_template_entry_metadata.gen.go ├── model_template_env_reference.gen.go ├── model_template_export.gen.go ├── model_template_export_org_ids.gen.go ├── model_template_export_resource_filters.gen.go ├── model_template_export_resources.gen.go ├── model_template_summary.gen.go ├── model_template_summary_bucket.gen.go ├── model_template_summary_bucket_all_of.gen.go ├── model_template_summary_check.gen.go ├── model_template_summary_check_all_of.gen.go ├── model_template_summary_common.gen.go ├── model_template_summary_common_all_of.gen.go ├── model_template_summary_core.gen.go ├── model_template_summary_dashboard.gen.go ├── model_template_summary_diff.gen.go ├── model_template_summary_diff_bucket.gen.go ├── model_template_summary_diff_bucket_fields.gen.go ├── model_template_summary_diff_check.gen.go ├── model_template_summary_diff_check_fields.gen.go ├── model_template_summary_diff_dashboard.gen.go ├── model_template_summary_diff_dashboard_fields.gen.go ├── model_template_summary_diff_label.gen.go ├── model_template_summary_diff_label_fields.gen.go ├── model_template_summary_diff_label_mapping.gen.go ├── model_template_summary_diff_label_mapping_all_of.gen.go ├── model_template_summary_diff_notification_endpoint.gen.go ├── model_template_summary_diff_notification_endpoint_fields.gen.go ├── model_template_summary_diff_notification_rule.gen.go ├── model_template_summary_diff_notification_rule_fields.gen.go ├── model_template_summary_diff_task.gen.go ├── model_template_summary_diff_task_fields.gen.go ├── model_template_summary_diff_telegraf.gen.go ├── model_template_summary_diff_variable.gen.go ├── model_template_summary_diff_variable_fields.gen.go ├── model_template_summary_error.gen.go ├── model_template_summary_errors.gen.go ├── model_template_summary_label.gen.go ├── model_template_summary_label_all_of.gen.go ├── model_template_summary_label_all_of_properties.gen.go ├── model_template_summary_label_mapping.gen.go ├── model_template_summary_notification_endpoint.gen.go ├── model_template_summary_notification_endpoint_all_of.gen.go ├── model_template_summary_notification_rule.gen.go ├── model_template_summary_notification_rule_all_of.gen.go ├── model_template_summary_resources.gen.go ├── model_template_summary_task.gen.go ├── model_template_summary_task_all_of.gen.go ├── model_template_summary_telegraf.gen.go ├── model_template_summary_telegraf_all_of.gen.go ├── model_template_summary_telegraf_config.gen.go ├── model_template_summary_variable.gen.go ├── model_template_summary_variable_all_of.gen.go ├── model_template_summary_variable_args.gen.go ├── model_template_summary_variable_args_render.go ├── model_unauthorized_request_error.gen.go ├── model_user.gen.go ├── model_user_response.gen.go ├── model_user_response_links.gen.go ├── model_users.gen.go ├── model_users_links.gen.go ├── model_write_precision.gen.go ├── permission_string.go ├── response.gen.go ├── schema_type.go ├── templates │ ├── README.md │ ├── api.mustache │ ├── client.mustache │ ├── configuration.mustache │ ├── model_enum.mustache │ ├── model_oneof.mustache │ └── model_simple.mustache ├── unmarshal_csv.go ├── utils.gen.go └── write_precision.go ├── clients ├── apply │ └── apply.go ├── auth │ ├── auth.go │ └── auth_internal_test.go ├── backup │ ├── backup.go │ └── backup_internal_test.go ├── bucket │ ├── client.go │ ├── create.go │ ├── create_test.go │ ├── delete.go │ ├── delete_test.go │ ├── list.go │ ├── list_test.go │ ├── update.go │ └── update_test.go ├── bucket_schema │ ├── client.go │ ├── create.go │ ├── create_test.go │ ├── csv.go │ ├── csv_test.go │ ├── format.go │ ├── json.go │ ├── json_test.go │ ├── list.go │ ├── list_test.go │ ├── nsjson.go │ ├── nsjson_test.go │ ├── testdata │ │ ├── columns.csv │ │ ├── columns.json │ │ └── columns.ndjson │ ├── update.go │ └── update_test.go ├── cli.go ├── config │ ├── config.go │ └── config_test.go ├── dashboards │ └── dashboards.go ├── delete │ ├── delete.go │ └── delete_test.go ├── errors.go ├── export │ └── export.go ├── helpers.go ├── org │ ├── client.go │ ├── org.go │ ├── org_members.go │ ├── org_members_test.go │ └── org_test.go ├── params.go ├── ping │ ├── ping.go │ └── ping_test.go ├── query │ ├── formatting_printer.go │ ├── formatting_printer_test.go │ ├── query.go │ └── query_test.go ├── remote │ └── remote.go ├── replication │ ├── replication.go │ └── replication_test.go ├── restore │ ├── manifest.go │ └── restore.go ├── script │ ├── script.go │ └── script_test.go ├── secret │ ├── secret.go │ └── secret_test.go ├── server_config │ └── server_config.go ├── setup │ ├── setup.go │ └── setup_test.go ├── signin │ └── signin.go ├── stacks │ └── stacks.go ├── task │ └── task.go ├── telegrafs │ └── telegrafs.go ├── template │ └── template.go ├── user │ ├── user.go │ └── user_test.go ├── v1_auth │ └── v1_auth.go ├── v1_dbrps │ ├── v1_dbrps.go │ └── v1_dbrps_test.go ├── v1_shell │ ├── autocomplete.go │ ├── gopher.go │ ├── table_model.go │ ├── table_model_test.go │ ├── v1_shell.go │ └── v1_shell_test.go └── write │ ├── buffer_batcher.go │ ├── buffer_batcher_internal_test.go │ ├── buffer_batcher_test.go │ ├── dryrun.go │ ├── dryrun_test.go │ ├── multi_reader.go │ ├── multi_reader_test.go │ ├── throttler.go │ ├── throttler_test.go │ ├── write.go │ └── write_test.go ├── cmd └── influx │ ├── apply.go │ ├── auth.go │ ├── backup.go │ ├── bucket.go │ ├── bucket_schema.go │ ├── completion.go │ ├── config.go │ ├── dashboards.go │ ├── delete.go │ ├── export.go │ ├── global.go │ ├── main.go │ ├── main_test.go │ ├── org.go │ ├── org_members.go │ ├── ping.go │ ├── query.go │ ├── remote.go │ ├── replication.go │ ├── restore.go │ ├── scripts.go │ ├── secret.go │ ├── server_config.go │ ├── setup.go │ ├── stacks.go │ ├── task.go │ ├── telegrafs.go │ ├── template.go │ ├── user.go │ ├── v1_auth.go │ ├── v1_commands.go │ ├── v1_dbrp.go │ ├── v1_shell.go │ ├── version.go │ └── write.go ├── config ├── config.go └── local.go ├── etc ├── checkfmt.sh ├── checkgenerate.sh ├── checkopenapi.sh ├── checktidy.sh ├── generate-openapi.sh └── stripGroupTags.py ├── go.mod ├── go.sum ├── internal ├── backup_restore │ ├── README.md │ ├── api_check.go │ ├── api_check_test.go │ ├── bolt.go │ ├── bolt_test.go │ ├── manifest.go │ ├── manifest_test.go │ ├── meta.pb.go │ ├── meta.proto │ └── testdata │ │ └── test.bolt.gz ├── mock │ ├── README.md │ ├── api_backup.gen.go │ ├── api_bucket_schemas.gen.go │ ├── api_buckets.gen.go │ ├── api_delete.gen.go │ ├── api_health.gen.go │ ├── api_invokable_scripts.gen.go │ ├── api_organizations.gen.go │ ├── api_query.gen.go │ ├── api_secret.gen.go │ ├── api_setup.gen.go │ ├── api_users.gen.go │ ├── api_v1dbrps.gen.go │ ├── api_write.gen.go │ ├── config.gen.go │ ├── gen.go │ └── stdio.gen.go └── testutils │ └── utils.go ├── pkg ├── cli │ ├── context │ │ └── context.go │ └── middleware │ │ ├── handleexit.go │ │ ├── middleware.go │ │ ├── noargs.go │ │ └── noargs_test.go ├── csv2lp │ ├── README.md │ ├── csv2lp.go │ ├── csv2lp_test.go │ ├── csv_annotations.go │ ├── csv_annotations_test.go │ ├── csv_table.go │ ├── csv_table_test.go │ ├── data_conversion.go │ ├── data_conversion_test.go │ ├── examples_test.go │ ├── line_reader.go │ ├── line_reader_test.go │ ├── lp_reader.go │ ├── lp_reader_test.go │ ├── multi_closer.go │ ├── multi_closer_test.go │ ├── skip_header_lines.go │ └── skip_header_lines_test.go ├── duration │ ├── parser.go │ └── parser_test.go ├── fluxcsv │ ├── query_result.go │ ├── query_result_test.go │ ├── table.go │ └── table_test.go ├── github │ ├── normalize.go │ └── normalize_test.go ├── gzip │ ├── gunzip.go │ ├── pipe.go │ └── pipe_test.go ├── influxid │ ├── id.go │ └── id_test.go ├── jsonnet │ ├── decode.go │ └── decode_test.go ├── signals │ ├── context.go │ └── context_test.go ├── stdio │ ├── interactive.go │ ├── noninteractive.go │ ├── stdio.go │ └── stdio_internal_test.go ├── tabwriter │ ├── tabwriter.go │ └── tabwriter_test.go └── template │ ├── color.go │ ├── diff_printer.go │ ├── diff_printer_test.go │ ├── out.go │ ├── out_test.go │ ├── source.go │ ├── source_test.go │ ├── summary_printer.go │ ├── table_printer.go │ └── table_printer_test.go ├── scripts └── ci │ ├── CHANGELOG_frozen.md │ ├── build-packages │ ├── build-tests.sh │ ├── install-go.sh │ ├── install-rosetta.sh │ ├── run-prebuilt-tests.sh │ ├── run-race-tests.sh │ └── setup-system.sh ├── support.mk └── tools.go /.circleci/scripts/upload-artifacts: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit \ 3 | -o nounset \ 4 | -o pipefail 5 | 6 | # Determine if "${CIRCLE_TAG}" matches the semantic version regex. Otherwise, 7 | # assume that "${CIRCLE_TAG}" is not intended to tag a release. The regex is 8 | # permissive of what occurs after the semantic version. This allows for 9 | # alphas, betas, and release candidates. 10 | if [[ "${CIRCLE_TAG:-}" =~ ^v[0-9]+.[0-9]+.[0-9]+ ]] 11 | then 12 | VERSION_REGEX="${CIRCLE_TAG/#v/}" 13 | else 14 | # When "${CIRCLE_TAG}" cannot be used to construct the package version, 15 | # use "${CIRCLE_SHA1}". Since "${CIRCLE_SHA1}" can start with an alpha 16 | # (non-numeric) character, prefix it with "2.x-". 17 | VERSION_REGEX="2.x[-_]${CIRCLE_SHA1:0:8}" 18 | fi 19 | 20 | REGEX='^packages/influxdb2-client[-_]'"${VERSION_REGEX}"'(.*)' 21 | 22 | for target in packages/* 23 | do 24 | if [[ "${target}" =~ ${REGEX} ]] 25 | then 26 | # After renaming the artifact to the "latest/nightly" version, append 27 | # the artifact to the `invalidations` file. Since `dl.influxdata.com` 28 | # contains many 100GBs, this should only invalidate artifacts 29 | # that have changed. 30 | case ${1} in 31 | nightly) 32 | mv -v "${target}" "packages/influxdb2-client-nightly${BASH_REMATCH[1]}" 33 | printf '/platform/nightlies/influxdb2-client-nightly%s\n' "${BASH_REMATCH[1]}" >>invalidations 34 | ;; 35 | release) 36 | cp -v "${target}" "packages/influxdb2-client-latest${BASH_REMATCH[1]}" 37 | printf '/influxdb/releases/influxdb2-client-latest%s\n' "${BASH_REMATCH[1]}" >>invalidations 38 | ;; 39 | esac 40 | fi 41 | done 42 | 43 | case ${1} in 44 | nightly) 45 | aws s3 sync packages s3://dl.influxdata.com/platform/nightlies 46 | ;; 47 | release) 48 | aws s3 sync packages s3://dl.influxdata.com/influxdb/releases 49 | ;; 50 | esac 51 | 52 | aws cloudfront create-invalidation --distribution-id "${AWS_DISTRIBUTION_ID}" --paths $( 23 | ``` 24 | 25 | Next, decide if any modifications are needed in the source-of-truth `openapi` repo. If so, create a branch in the 26 | submodule to track changes there: 27 | ```shell 28 | cd api/contract/openapi && git checkout -b 29 | ``` 30 | 31 | Edit/add to the files under `api-contract/` to describe the new API contract. Run the following from the project 32 | root test your changes and see the outputs in Go code: 33 | ```shell 34 | make openapi 35 | # Use `git status` to see new/modified files under `api` 36 | ``` 37 | 38 | Once you're happy with the new API contract, submit your changes for review & merge. 39 | If you added/edited files within `openapi`, you'll first need to: 40 | 1. Push your submodule branch to GitHub 41 | ```shell 42 | cd api/contract/openapi && git push 43 | ``` 44 | 2. Create a PR in `openapi`, eventually merge to `master` there 45 | 3. Update your submodule to point at the merge result: 46 | ```shell 47 | cd api/contract/openapi && git fetch && git checkout master && git pull origin master 48 | ``` 49 | 4. Update the submodule reference from the main repo: 50 | ```shell 51 | git add api/contract/openapi 52 | git commit 53 | ``` 54 | -------------------------------------------------------------------------------- /api/contract/cli-extras.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | title: Extra configuration to force openapi to generate enum validators 4 | version: 2.0.0 5 | servers: [] 6 | paths: {} 7 | components: 8 | schemas: 9 | ResourceEnumOSS: 10 | $ref: "./openapi/src/oss/schemas/Resource.yml#/properties/type" 11 | ResourceEnumCloud: 12 | $ref: "./openapi/src/cloud/schemas/Resource.yml#/properties/type" 13 | 14 | -------------------------------------------------------------------------------- /api/contract/overrides/paths/backup_metadata.yml: -------------------------------------------------------------------------------- 1 | get: 2 | operationId: GetBackupMetadata 3 | tags: 4 | - Backup 5 | summary: Download snapshot of all metadata in the server 6 | parameters: 7 | - $ref: "../../openapi/src/common/parameters/TraceSpan.yml" 8 | - in: header 9 | name: Accept-Encoding 10 | description: The Accept-Encoding request HTTP header advertises which content encoding, usually a compression algorithm, the client is able to understand. 11 | schema: 12 | type: string 13 | description: Specifies that the query response in the body should be encoded with gzip or not encoded with identity. 14 | default: identity 15 | enum: 16 | - gzip 17 | - identity 18 | responses: 19 | "200": 20 | description: Snapshot of metadata 21 | headers: 22 | Content-Encoding: 23 | description: The Content-Encoding entity header is used to compress the media-type. When present, its value indicates which encodings were applied to the entity-body 24 | schema: 25 | type: string 26 | description: Specifies that the response in the body is encoded with gzip or not encoded with identity. 27 | default: identity 28 | enum: 29 | - gzip 30 | - identity 31 | content: 32 | multipart/mixed: 33 | schema: 34 | # NOTE: This has a proper schema in the `openapi` repo, but our codegen isn't smart enough to handle 35 | # multipart responses. Pretend it's a binary string here so we can decode the body ourselves. 36 | type: string 37 | format: binary 38 | default: 39 | description: Unexpected error 40 | $ref: '../../openapi/src/common/responses/ServerError.yml' 41 | -------------------------------------------------------------------------------- /api/contract/overrides/paths/dashboards.yml: -------------------------------------------------------------------------------- 1 | get: 2 | operationId: GetDashboards 3 | tags: 4 | - Dashboards 5 | summary: List all dashboards 6 | parameters: 7 | - $ref: "../../openapi/src/common/parameters/TraceSpan.yml" 8 | - $ref: "../../openapi/src/common/parameters/Offset.yml" 9 | - $ref: "../../openapi/src/common/parameters/Limit.yml" 10 | - $ref: "../../openapi/src/common/parameters/Descending.yml" 11 | - in: query 12 | name: owner 13 | description: A user identifier. Returns only dashboards where this user has the `owner` role. 14 | schema: 15 | type: string 16 | - in: query 17 | name: sortBy 18 | description: The column to sort by. 19 | schema: 20 | type: string 21 | enum: 22 | - "ID" 23 | - "CreatedAt" 24 | - "UpdatedAt" 25 | - in: query 26 | name: id 27 | description: A list of dashboard identifiers. Returns only the listed dashboards. If both `id` and `owner` are specified, only `id` is used. 28 | schema: 29 | type: array 30 | items: 31 | type: string 32 | - in: query 33 | name: orgID 34 | description: The identifier of the organization. 35 | schema: 36 | type: string 37 | - in: query 38 | name: org 39 | description: The name of the organization. 40 | schema: 41 | type: string 42 | responses: 43 | "200": 44 | description: All dashboards 45 | content: 46 | application/json: 47 | schema: 48 | $ref: "../../openapi/src/common/schemas/Dashboards.yml" 49 | default: 50 | description: Unexpected error 51 | content: 52 | application/json: 53 | schema: 54 | $ref: "../../openapi/src/common/schemas/Error.yml" 55 | 56 | # Cuts out the post section since it is not needed 57 | # for CLI and adds a large amount of extra code-gen. -------------------------------------------------------------------------------- /api/contract/overrides/paths/scripts_scriptID_invoke.yml: -------------------------------------------------------------------------------- 1 | post: 2 | operationId: PostScriptsIDInvoke 3 | tags: 4 | - Invokable Scripts 5 | summary: Invoke a script 6 | description: Invokes a script and substitutes `params` keys referenced in the script with `params` key-values sent in the request body. 7 | parameters: 8 | - in: path 9 | name: scriptID 10 | schema: 11 | type: string 12 | required: true 13 | requestBody: 14 | content: 15 | application/json: 16 | schema: 17 | $ref: '../schemas/ScriptInvocationParams.yml' 18 | responses: 19 | '200': 20 | description: The result of the script execution. 21 | content: 22 | application/json: 23 | schema: 24 | $ref: '../../openapi/src/svc/invocable-scripts/schemas/ScriptHTTPResponseData.yml' 25 | default: 26 | description: Unexpected error 27 | $ref: '../../openapi/src/common/responses/ServerError.yml' 28 | -------------------------------------------------------------------------------- /api/contract/overrides/paths/stacks.yml: -------------------------------------------------------------------------------- 1 | get: 2 | operationId: ListStacks 3 | tags: 4 | - Stacks 5 | summary: List all installed InfluxDB templates 6 | parameters: 7 | - in: query 8 | name: orgID 9 | required: true 10 | schema: 11 | type: string 12 | description: The organization id of the stacks 13 | - in: query 14 | name: name 15 | schema: 16 | type: array 17 | items: 18 | type: string 19 | description: A collection of names to filter the list by. 20 | - in: query 21 | name: stackID 22 | schema: 23 | type: array 24 | items: 25 | type: string 26 | description: A collection of stackIDs to filter the list by. 27 | responses: 28 | "200": 29 | description: Influx stacks found 30 | content: 31 | application/json: 32 | schema: 33 | $ref: "../schemas/Stacks.yml" 34 | default: 35 | description: Unexpected error 36 | content: 37 | application/json: 38 | schema: 39 | $ref: "../../openapi/src/common/schemas/Error.yml" 40 | post: 41 | operationId: CreateStack 42 | tags: 43 | - Stacks 44 | summary: Create a new stack 45 | requestBody: 46 | description: Stack to create. 47 | required: true 48 | content: 49 | application/json: 50 | schema: 51 | $ref: "../schemas/StackPostRequest.yml" 52 | responses: 53 | "201": 54 | description: InfluxDB Stack created 55 | content: 56 | application/json: 57 | schema: 58 | $ref: "../schemas/Stack.yml" 59 | default: 60 | description: Unexpected error 61 | content: 62 | application/json: 63 | schema: 64 | $ref: "../../openapi/src/common/schemas/Error.yml" 65 | -------------------------------------------------------------------------------- /api/contract/overrides/paths/stacks_stack_id.yml: -------------------------------------------------------------------------------- 1 | get: 2 | operationId: ReadStack 3 | tags: 4 | - Stacks 5 | summary: Retrieve a stack 6 | parameters: 7 | - in: path 8 | name: stack_id 9 | required: true 10 | schema: 11 | type: string 12 | description: The identifier of the stack. 13 | responses: 14 | "200": 15 | description: The InfluxDB stack 16 | content: 17 | application/json: 18 | schema: 19 | $ref: "../schemas/Stack.yml" 20 | default: 21 | description: Unexpected error 22 | content: 23 | application/json: 24 | schema: 25 | $ref: "../../openapi/src/common/schemas/Error.yml" 26 | patch: 27 | operationId: UpdateStack 28 | tags: 29 | - Stacks 30 | summary: Update an InfluxDB Stack 31 | parameters: 32 | - in: path 33 | name: stack_id 34 | required: true 35 | schema: 36 | type: string 37 | description: The identifier of the stack. 38 | requestBody: 39 | description: Influx stack to update. 40 | required: true 41 | content: 42 | application/json: 43 | schema: 44 | $ref: "../schemas/StackPatchRequest.yml" 45 | responses: 46 | "200": 47 | description: Influx stack updated 48 | content: 49 | application/json: 50 | schema: 51 | $ref: "../schemas/Stack.yml" 52 | default: 53 | description: Unexpected error 54 | content: 55 | application/json: 56 | schema: 57 | $ref: "../../openapi/src/common/schemas/Error.yml" 58 | delete: 59 | operationId: DeleteStack 60 | tags: 61 | - Stacks 62 | summary: Delete a stack and associated resources 63 | parameters: 64 | - in: path 65 | name: stack_id 66 | required: true 67 | schema: 68 | type: string 69 | description: The identifier of the stack. 70 | - in: query 71 | name: orgID 72 | required: true 73 | schema: 74 | type: string 75 | description: The identifier of the organization. 76 | responses: 77 | "204": 78 | description: The stack and its associated resources are deleted 79 | default: 80 | description: Unexpected error 81 | content: 82 | application/json: 83 | schema: 84 | $ref: "../../openapi/src/common/schemas/Error.yml" 85 | -------------------------------------------------------------------------------- /api/contract/overrides/paths/templates_apply.yml: -------------------------------------------------------------------------------- 1 | post: 2 | operationId: ApplyTemplate 3 | tags: 4 | - Templates 5 | summary: Apply or dry-run an InfluxDB Template 6 | requestBody: 7 | required: true 8 | content: 9 | application/json: 10 | schema: 11 | $ref: "../schemas/TemplateApply.yml" 12 | responses: 13 | "200": 14 | description: > 15 | Influx package dry-run successful, no new resources created. 16 | The provided diff and summary will not have IDs for resources 17 | that do not exist at the time of the dry run. 18 | content: 19 | application/json: 20 | schema: 21 | $ref: "../schemas/TemplateSummary.yml" 22 | "201": 23 | description: > 24 | Influx package applied successfully. Newly created resources created 25 | available in summary. The diff compares the state of the world before 26 | the package is applied with the changes the application will impose. 27 | This corresponds to `"dryRun": true` 28 | content: 29 | application/json: 30 | schema: 31 | $ref: "../schemas/TemplateSummary.yml" 32 | "422": 33 | description: Template failed validation 34 | content: 35 | application/json: 36 | schema: 37 | $ref: "../schemas/TemplateSummaryError.yml" 38 | default: 39 | description: Unexpected error 40 | content: 41 | application/json: 42 | schema: 43 | $ref: "../../openapi/src/common/schemas/Error.yml" 44 | -------------------------------------------------------------------------------- /api/contract/overrides/paths/templates_export.yml: -------------------------------------------------------------------------------- 1 | post: 2 | operationId: ExportTemplate 3 | tags: 4 | - Templates 5 | summary: Export a new Influx Template 6 | requestBody: 7 | description: Export resources as an InfluxDB template. 8 | required: false 9 | content: 10 | application/json: 11 | schema: 12 | $ref: "../schemas/TemplateExport.yml" 13 | responses: 14 | "200": 15 | description: InfluxDB template created 16 | content: 17 | application/json: 18 | schema: 19 | $ref: "../schemas/Template.yml" 20 | default: 21 | description: Unexpected error 22 | content: 23 | application/json: 24 | schema: 25 | $ref: "../../openapi/src/common/schemas/Error.yml" 26 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/Extern.yml: -------------------------------------------------------------------------------- 1 | description: Free-form Flux AST to prepend to query requests 2 | type: object 3 | properties: 4 | type: 5 | type: string 6 | enum: [File] 7 | default: File 8 | # NOTE: Intentionally type-less here because the boilerplate produced 9 | # by codegen off the Flux AST spec is unmangeable. The CLI only needs 10 | # to use a small subset of the AST and rarely changes what it sends, 11 | # so we can live with a generic map in the codegen request body. 12 | additionalProperties: true 13 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/Query.yml: -------------------------------------------------------------------------------- 1 | description: Query influx using the Flux language 2 | type: object 3 | required: 4 | - query 5 | properties: 6 | extern: 7 | $ref: "./Extern.yml" 8 | query: 9 | description: Query script to execute. 10 | type: string 11 | type: 12 | description: The type of query. Must be "flux". 13 | type: string 14 | enum: 15 | - flux 16 | default: flux 17 | dialect: 18 | $ref: "../../openapi/src/common/schemas/Dialect.yml" 19 | now: 20 | description: Specifies the time that should be reported as "now" in the query. Default is the server's now time. 21 | type: string 22 | format: date-time 23 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/ScriptInvocationParams.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | params: 4 | type: object 5 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/Stack.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | id: 4 | type: string 5 | orgID: 6 | type: string 7 | createdAt: 8 | type: string 9 | format: date-time 10 | events: 11 | type: array 12 | items: 13 | $ref: "./StackEvent.yml" 14 | required: [id, orgID, createdAt, events] 15 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/StackEvent.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | eventType: 4 | type: string 5 | name: 6 | type: string 7 | description: 8 | type: string 9 | sources: 10 | type: array 11 | items: 12 | type: string 13 | resources: 14 | type: array 15 | items: 16 | $ref: "./StackEventResource.yml" 17 | urls: 18 | type: array 19 | items: 20 | type: string 21 | updatedAt: 22 | type: string 23 | format: date-time 24 | required: [eventType, name, sources, resources, urls, updatedAt] 25 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/StackEventResource.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | apiVersion: 4 | type: string 5 | resourceID: 6 | type: string 7 | kind: 8 | type: string 9 | templateMetaName: 10 | type: string 11 | associations: 12 | type: array 13 | items: 14 | $ref: "./StackEventResourceAssociation.yml" 15 | links: 16 | type: object 17 | properties: 18 | self: 19 | type: string 20 | required: [apiVersion, resourceID, kind, templateMetaName, associations] 21 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/StackEventResourceAssociation.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | metaName: 6 | type: string 7 | required: [kind, metaName] 8 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/StackPatchRequest.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | description: 6 | type: string 7 | templateURLs: 8 | type: array 9 | items: 10 | type: string 11 | additionalResources: 12 | type: array 13 | items: 14 | $ref: "./StackPatchRequestResource.yml" 15 | required: [templateURLs, additionalResources] 16 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/StackPatchRequestResource.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | resourceID: 4 | type: string 5 | kind: 6 | type: string 7 | templateMetaName: 8 | type: string 9 | required: [kind, resourceID] 10 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/StackPostRequest.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | orgID: 4 | type: string 5 | name: 6 | type: string 7 | description: 8 | type: string 9 | urls: 10 | type: array 11 | items: 12 | type: string 13 | required: [orgID, name, urls] 14 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/Stacks.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | stacks: 4 | type: array 5 | items: 6 | $ref: "./Stack.yml" 7 | required: [stacks] 8 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/Template.yml: -------------------------------------------------------------------------------- 1 | type: array 2 | items: 3 | $ref: "./TemplateEntry.yml" 4 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateApply.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | dryRun: 4 | type: boolean 5 | orgID: 6 | type: string 7 | stackID: 8 | type: string 9 | template: 10 | $ref: "./TemplateApplyTemplate.yml" 11 | templates: 12 | type: array 13 | items: 14 | $ref: "./TemplateApplyTemplate.yml" 15 | envRefs: 16 | type: object 17 | additionalProperties: 18 | type: string 19 | secrets: 20 | type: object 21 | additionalProperties: 22 | type: string 23 | remotes: 24 | type: array 25 | items: 26 | $ref: "./TemplateApplyRemoteRef.yml" 27 | actions: 28 | type: array 29 | items: 30 | $ref: "./TemplateApplyAction.yml" 31 | required: [dryRun, orgID, templates, envRefs, secrets, remotes, actions] 32 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateApplyAction.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | action: 4 | $ref: "./TemplateApplyActionKind.yml" 5 | properties: 6 | type: object 7 | properties: 8 | kind: 9 | type: string 10 | resourceTemplateName: 11 | type: string 12 | required: [kind] 13 | required: [action, properties] 14 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateApplyActionKind.yml: -------------------------------------------------------------------------------- 1 | type: string 2 | enum: 3 | - skipKind 4 | - skipResource 5 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateApplyRemoteRef.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | url: 4 | type: string 5 | contentType: 6 | type: string 7 | required: [url] 8 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateApplyTemplate.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | sources: 4 | type: array 5 | items: 6 | type: string 7 | contents: 8 | $ref: "./Template.yml" 9 | contentType: 10 | type: string 11 | required: [sources, contents, contentType] 12 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateEntry.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | apiVersion: 4 | type: string 5 | kind: 6 | type: string 7 | metadata: 8 | type: object 9 | properties: 10 | name: 11 | type: string 12 | required: [name] 13 | spec: 14 | type: object 15 | required: [apiVersion, kind, metadata, spec] 16 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateEnvReference.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | resourceField: 4 | type: string 5 | description: Field the environment reference corresponds too 6 | envRefKey: 7 | type: string 8 | description: Key identified as environment reference and is the key identified in the template 9 | value: 10 | description: Value provided to fulfill reference 11 | nullable: true 12 | defaultValue: 13 | description: Default value that will be provided for the reference when no value is provided 14 | nullable: true 15 | required: [resourceField, envRefKey] 16 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateExport.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | stackID: 4 | type: string 5 | orgIDs: 6 | type: array 7 | items: 8 | type: object 9 | properties: 10 | orgID: 11 | type: string 12 | resourceFilters: 13 | type: object 14 | properties: 15 | byLabel: 16 | type: array 17 | items: 18 | type: string 19 | byResourceKind: 20 | type: array 21 | items: 22 | type: string 23 | resources: 24 | type: array 25 | items: 26 | type: object 27 | properties: 28 | id: 29 | type: string 30 | kind: 31 | type: string 32 | name: 33 | type: string 34 | required: [kind] 35 | required: [resources] 36 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummary.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | sources: 4 | type: array 5 | items: 6 | type: string 7 | stackID: 8 | type: string 9 | summary: 10 | $ref: "./TemplateSummaryResources.yml" 11 | diff: 12 | $ref: "./TemplateSummaryDiff.yml" 13 | errors: 14 | type: array 15 | items: 16 | $ref: "./TemplateSummaryErrors.yml" 17 | required: [sources, stackID, summary, diff, errors] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryBucket.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | name: 10 | type: string 11 | description: 12 | type: string 13 | retentionPeriod: 14 | type: integer 15 | format: int64 16 | schemaType: 17 | $ref: "../../openapi/src/common/schemas/SchemaType.yml" 18 | required: [id, name, retentionPeriod] 19 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryCheck.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | name: 10 | type: string 11 | description: 12 | type: string 13 | required: [id, name] 14 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryCommon.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCore.yml" 3 | - type: object 4 | properties: 5 | labelAssociations: 6 | type: array 7 | items: 8 | $ref: "./TemplateSummaryLabel.yml" 9 | required: [labelAssociations] 10 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryCore.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | templateMetaName: 6 | type: string 7 | envReferences: 8 | type: array 9 | items: 10 | $ref: "./TemplateEnvReference.yml" 11 | required: [kind, templateMetaName, envReferences] 12 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDashboard.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | name: 10 | type: "string" 11 | description: 12 | type: "string" 13 | required: [id, name] 14 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiff.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | buckets: 4 | type: array 5 | items: 6 | $ref: "./TemplateSummaryDiffBucket.yml" 7 | checks: 8 | type: array 9 | items: 10 | $ref: "./TemplateSummaryDiffCheck.yml" 11 | dashboards: 12 | type: array 13 | items: 14 | $ref: "./TemplateSummaryDiffDashboard.yml" 15 | labels: 16 | type: array 17 | items: 18 | $ref: "./TemplateSummaryDiffLabel.yml" 19 | labelMappings: 20 | type: array 21 | items: 22 | $ref: "./TemplateSummaryDiffLabelMapping.yml" 23 | notificationEndpoints: 24 | type: array 25 | items: 26 | $ref: "./TemplateSummaryDiffNotificationEndpoint.yml" 27 | notificationRules: 28 | type: array 29 | items: 30 | $ref: "./TemplateSummaryDiffNotificationRule.yml" 31 | tasks: 32 | type: array 33 | items: 34 | $ref: "./TemplateSummaryDiffTask.yml" 35 | telegrafConfigs: 36 | type: array 37 | items: 38 | $ref: "./TemplateSummaryDiffTelegraf.yml" 39 | variables: 40 | type: array 41 | items: 42 | $ref: "./TemplateSummaryDiffVariable.yml" 43 | required: 44 | - buckets 45 | - checks 46 | - dashboards 47 | - labels 48 | - labelMappings 49 | - notificationEndpoints 50 | - notificationRules 51 | - tasks 52 | - telegrafConfigs 53 | - variables 54 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffBucket.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | stateStatus: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffBucketFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffBucketFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffBucketFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | description: 6 | type: string 7 | retentionRules: 8 | $ref: "../../openapi/src/common/schemas/RetentionRules.yml" 9 | schemaType: 10 | $ref: "../../openapi/src/common/schemas/SchemaType.yml" 11 | measurementSchemas: 12 | type: array 13 | items: 14 | type: object 15 | required: [name, retentionRules, measurementSchemas] 16 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffCheck.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | stateStatus: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffCheckFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffCheckFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffCheckFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | description: 6 | type: string 7 | required: [name] 8 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffDashboard.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | stateStatus: 4 | type: string 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | kind: 10 | type: string 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffDashboardFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffDashboardFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffDashboardFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | description: 6 | type: string 7 | charts: 8 | type: array 9 | items: 10 | type: object 11 | required: [name, charts] 12 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffLabel.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | stateStatus: 4 | type: string 5 | kind: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffLabelFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffLabelFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffLabelFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | color: 6 | type: string 7 | description: 8 | type: string 9 | required: [name, color] 10 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffLabelMapping.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryLabelMapping.yml" 3 | - type: object 4 | properties: 5 | stateStatus: 6 | type: string 7 | required: [stateStatus] 8 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffNotificationEndpoint.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | stateStatus: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffNotificationEndpointFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffNotificationEndpointFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffNotificationEndpointFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | required: [name] 6 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffNotificationRule.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | stateStatus: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffNotificationRuleFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffNotificationRuleFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffNotificationRuleFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | description: 6 | type: string 7 | endpointName: 8 | type: string 9 | endpointID: 10 | type: integer 11 | format: int64 12 | x-go-field-type: uint64 13 | endpointType: 14 | type: string 15 | every: 16 | type: string 17 | offset: 18 | type: string 19 | messageTemplate: 20 | type: string 21 | required: [name, endpointName, endpointID, endpointType, every, offset] 22 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffTask.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | stateStatus: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffTaskFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffTaskFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffTaskFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | cron: 6 | type: string 7 | description: 8 | type: string 9 | every: 10 | type: string 11 | offset: 12 | type: string 13 | query: 14 | type: string 15 | status: 16 | type: string 17 | required: [name, status] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffTelegraf.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | stateStatus: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryTelegrafConfig.yml" 15 | old: 16 | $ref: "./TemplateSummaryTelegrafConfig.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffVariable.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | stateStatus: 6 | type: string 7 | id: 8 | type: integer 9 | format: int64 10 | x-go-field-type: uint64 11 | templateMetaName: 12 | type: string 13 | new: 14 | $ref: "./TemplateSummaryDiffVariableFields.yml" 15 | old: 16 | $ref: "./TemplateSummaryDiffVariableFields.yml" 17 | required: [kind, stateStatus, id, templateMetaName] 18 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryDiffVariableFields.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | name: 4 | type: string 5 | description: 6 | type: string 7 | args: 8 | $ref: "./TemplateSummaryVariableArgs.yml" 9 | required: [name] 10 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryError.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | message: 4 | type: string 5 | code: 6 | type: string 7 | sources: 8 | type: array 9 | items: 10 | type: string 11 | stackID: 12 | type: string 13 | summary: 14 | $ref: "./TemplateSummaryResources.yml" 15 | diff: 16 | $ref: "./TemplateSummaryDiff.yml" 17 | errors: 18 | type: array 19 | items: 20 | $ref: "./TemplateSummaryErrors.yml" 21 | required: [message, code] 22 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryErrors.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | kind: 4 | type: string 5 | reason: 6 | type: string 7 | fields: 8 | type: array 9 | items: 10 | type: string 11 | indexes: 12 | type: array 13 | items: 14 | type: integer 15 | x-go-field-type: '[]*int' 16 | required: [kind, reason, fields, indexes] 17 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryLabel.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCore.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | orgID: 10 | type: integer 11 | format: int64 12 | x-go-field-type: uint64 13 | name: 14 | type: string 15 | properties: 16 | type: object 17 | properties: 18 | color: 19 | type: string 20 | description: 21 | type: string 22 | required: [color] 23 | required: [id, name, properties] 24 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryLabelMapping.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | status: 4 | type: string 5 | resourceTemplateMetaName: 6 | type: string 7 | resourceName: 8 | type: string 9 | resourceID: 10 | type: integer 11 | format: int64 12 | x-go-field-type: uint64 13 | resourceType: 14 | type: string 15 | labelTemplateMetaName: 16 | type: string 17 | labelName: 18 | type: string 19 | labelID: 20 | type: integer 21 | format: int64 22 | x-go-field-type: uint64 23 | required: 24 | - status 25 | - resourceTemplateMetaName 26 | - resourceName 27 | - resourceID 28 | - resourceType 29 | - labelTemplateMetaName 30 | - labelName 31 | - labelID 32 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryNotificationEndpoint.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | name: 10 | type: string 11 | description: 12 | type: string 13 | status: 14 | type: string 15 | required: [id, name, status] 16 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryNotificationRule.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | name: 10 | type: string 11 | description: 12 | type: string 13 | endpointTemplateMetaName: 14 | type: string 15 | endpointID: 16 | type: integer 17 | format: int64 18 | x-go-field-type: uint64 19 | endpointType: 20 | type: string 21 | every: 22 | type: string 23 | offset: 24 | type: string 25 | required: [id, name, endpointTemplateMetaName, endpointID, endpointType, every, offset] 26 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryResources.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | buckets: 4 | type: array 5 | items: 6 | $ref: "./TemplateSummaryBucket.yml" 7 | checks: 8 | type: array 9 | items: 10 | $ref: "./TemplateSummaryCheck.yml" 11 | dashboards: 12 | type: array 13 | items: 14 | $ref: "./TemplateSummaryDashboard.yml" 15 | labels: 16 | type: array 17 | items: 18 | $ref: "./TemplateSummaryLabel.yml" 19 | labelMappings: 20 | type: array 21 | items: 22 | $ref: "./TemplateSummaryLabelMapping.yml" 23 | missingEnvRefs: 24 | type: array 25 | items: 26 | type: string 27 | missingSecrets: 28 | type: array 29 | items: 30 | type: string 31 | notificationEndpoints: 32 | type: array 33 | items: 34 | $ref: "./TemplateSummaryNotificationEndpoint.yml" 35 | notificationRules: 36 | type: array 37 | items: 38 | $ref: "./TemplateSummaryNotificationRule.yml" 39 | tasks: 40 | type: array 41 | items: 42 | $ref: "./TemplateSummaryTask.yml" 43 | telegrafConfigs: 44 | type: array 45 | items: 46 | $ref: "./TemplateSummaryTelegraf.yml" 47 | variables: 48 | type: array 49 | items: 50 | $ref: "./TemplateSummaryVariable.yml" 51 | required: 52 | - buckets 53 | - checks 54 | - dashboards 55 | - labels 56 | - labelMappings 57 | - missingEnvRefs 58 | - missingSecrets 59 | - notificationEndpoints 60 | - notificationRules 61 | - tasks 62 | - telegrafConfigs 63 | - variables 64 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryTask.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | name: 10 | type: string 11 | description: 12 | type: string 13 | cron: 14 | type: string 15 | every: 16 | type: string 17 | offset: 18 | type: string 19 | required: [id, name] 20 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryTelegraf.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | telegrafConfig: 6 | $ref: "./TemplateSummaryTelegrafConfig.yml" 7 | required: [telegrafConfig] 8 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryTelegrafConfig.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | id: 4 | type: string 5 | name: 6 | type: string 7 | description: 8 | type: string 9 | required: [id, name] 10 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryVariable.yml: -------------------------------------------------------------------------------- 1 | allOf: 2 | - $ref: "./TemplateSummaryCommon.yml" 3 | - type: object 4 | properties: 5 | id: 6 | type: integer 7 | format: int64 8 | x-go-field-type: uint64 9 | name: 10 | type: string 11 | description: 12 | type: string 13 | arguments: 14 | $ref: "./TemplateSummaryVariableArgs.yml" 15 | required: [id, name] 16 | -------------------------------------------------------------------------------- /api/contract/overrides/schemas/TemplateSummaryVariableArgs.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | type: 4 | type: string 5 | values: 6 | x-go-field-type: 'interface{}' 7 | required: [type, values] 8 | -------------------------------------------------------------------------------- /api/extras/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /api/extras/README.md: -------------------------------------------------------------------------------- 1 | # Influx CLI - extra files supporting HTTP client 2 | 3 | The `.go` files in this module are generated using [`OpenAPITools/openapi-generator`](https://github.com/OpenAPITools/openapi-generator), 4 | based off of our public API documentation. 5 | 6 | See https://github.com/OpenAPITools/openapi-generator/issues/2360 - enums are not generated by default if they are defined inline. 7 | Even if they were generated by default, there appears to be no way to turn off the validation, which would be bad if the client 8 | started receiving new string values that were unrecognized (using an old client with a newer InfluxDB instance). 9 | 10 | So the go files in this directory are generated from some enums where we want to know the list of 'known' valid values, but 11 | we don't want to always validate on marshal/unmarshal. 12 | -------------------------------------------------------------------------------- /api/gunzip.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/influxdata/influx-cli/v2/pkg/gzip" 8 | ) 9 | 10 | func GunzipIfNeeded(resp *http.Response) (io.ReadCloser, error) { 11 | if resp.Header.Get("Content-Encoding") == "gzip" { 12 | reader, err := gzip.NewGunzipReadCloser(resp.Body) 13 | if err != nil { 14 | resp.Body.Close() 15 | return nil, err 16 | } 17 | return reader, nil 18 | } 19 | return resp.Body, nil 20 | } 21 | -------------------------------------------------------------------------------- /api/gunzip_test.go: -------------------------------------------------------------------------------- 1 | package api_test 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/influxdata/influx-cli/v2/api" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | var exampleResponse = `result,table,_start,_stop,_time,region,host,_value 15 | mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:00Z,east,A,15.43 16 | mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:20Z,east,B,59.25 17 | mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:40Z,east,C,52.62` 18 | 19 | func TestGunzipIfNeeded(t *testing.T) { 20 | t.Parallel() 21 | 22 | testCases := []struct { 23 | name string 24 | encoding string 25 | getBody func(*testing.T) io.ReadCloser 26 | }{ 27 | { 28 | name: "no gzip", 29 | getBody: func(t *testing.T) io.ReadCloser { 30 | return io.NopCloser(strings.NewReader(exampleResponse)) 31 | }, 32 | }, 33 | { 34 | name: "gzip", 35 | encoding: "gzip", 36 | getBody: func(t *testing.T) io.ReadCloser { 37 | pr, pw := io.Pipe() 38 | gw := gzip.NewWriter(pw) 39 | 40 | go func() { 41 | _, err := io.Copy(gw, strings.NewReader(exampleResponse)) 42 | gw.Close() 43 | pw.Close() 44 | require.NoError(t, err) 45 | }() 46 | 47 | return pr 48 | }, 49 | }, 50 | } 51 | 52 | for _, tc := range testCases { 53 | tc := tc 54 | t.Run(tc.name, func(t *testing.T) { 55 | t.Parallel() 56 | 57 | headers := http.Header{} 58 | if tc.encoding != "" { 59 | headers.Add("Content-Encoding", tc.encoding) 60 | } 61 | resp := http.Response{ 62 | Header: headers, 63 | Body: tc.getBody(t), 64 | } 65 | 66 | raw, err := api.GunzipIfNeeded(&resp) 67 | require.NoError(t, err) 68 | defer raw.Close() 69 | body, err := io.ReadAll(raw) 70 | require.NoError(t, err) 71 | require.Equal(t, exampleResponse, string(body)) 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /api/model_column_data_type.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // ColumnDataType the model 'ColumnDataType' 19 | type ColumnDataType string 20 | 21 | // List of ColumnDataType 22 | const ( 23 | COLUMNDATATYPE_INTEGER ColumnDataType = "integer" 24 | COLUMNDATATYPE_FLOAT ColumnDataType = "float" 25 | COLUMNDATATYPE_BOOLEAN ColumnDataType = "boolean" 26 | COLUMNDATATYPE_STRING ColumnDataType = "string" 27 | COLUMNDATATYPE_UNSIGNED ColumnDataType = "unsigned" 28 | ) 29 | 30 | func ColumnDataTypeValues() []ColumnDataType { 31 | return []ColumnDataType{"integer", "float", "boolean", "string", "unsigned"} 32 | } 33 | 34 | func (v *ColumnDataType) UnmarshalJSON(src []byte) error { 35 | var value string 36 | err := json.Unmarshal(src, &value) 37 | if err != nil { 38 | return err 39 | } 40 | enumTypeValue := ColumnDataType(value) 41 | for _, existing := range []ColumnDataType{"integer", "float", "boolean", "string", "unsigned"} { 42 | if existing == enumTypeValue { 43 | *v = enumTypeValue 44 | return nil 45 | } 46 | } 47 | 48 | return fmt.Errorf("%+v is not a valid ColumnDataType", value) 49 | } 50 | 51 | // Ptr returns reference to ColumnDataType value 52 | func (v ColumnDataType) Ptr() *ColumnDataType { 53 | return &v 54 | } 55 | 56 | type NullableColumnDataType struct { 57 | value *ColumnDataType 58 | isSet bool 59 | } 60 | 61 | func (v NullableColumnDataType) Get() *ColumnDataType { 62 | return v.value 63 | } 64 | 65 | func (v *NullableColumnDataType) Set(val *ColumnDataType) { 66 | v.value = val 67 | v.isSet = true 68 | } 69 | 70 | func (v NullableColumnDataType) IsSet() bool { 71 | return v.isSet 72 | } 73 | 74 | func (v *NullableColumnDataType) Unset() { 75 | v.value = nil 76 | v.isSet = false 77 | } 78 | 79 | func NewNullableColumnDataType(val *ColumnDataType) *NullableColumnDataType { 80 | return &NullableColumnDataType{value: val, isSet: true} 81 | } 82 | 83 | func (v NullableColumnDataType) MarshalJSON() ([]byte, error) { 84 | return json.Marshal(v.value) 85 | } 86 | 87 | func (v *NullableColumnDataType) UnmarshalJSON(src []byte) error { 88 | v.isSet = true 89 | return json.Unmarshal(src, &v.value) 90 | } 91 | -------------------------------------------------------------------------------- /api/model_column_semantic_type.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // ColumnSemanticType the model 'ColumnSemanticType' 19 | type ColumnSemanticType string 20 | 21 | // List of ColumnSemanticType 22 | const ( 23 | COLUMNSEMANTICTYPE_TIMESTAMP ColumnSemanticType = "timestamp" 24 | COLUMNSEMANTICTYPE_TAG ColumnSemanticType = "tag" 25 | COLUMNSEMANTICTYPE_FIELD ColumnSemanticType = "field" 26 | ) 27 | 28 | func ColumnSemanticTypeValues() []ColumnSemanticType { 29 | return []ColumnSemanticType{"timestamp", "tag", "field"} 30 | } 31 | 32 | func (v *ColumnSemanticType) UnmarshalJSON(src []byte) error { 33 | var value string 34 | err := json.Unmarshal(src, &value) 35 | if err != nil { 36 | return err 37 | } 38 | enumTypeValue := ColumnSemanticType(value) 39 | for _, existing := range []ColumnSemanticType{"timestamp", "tag", "field"} { 40 | if existing == enumTypeValue { 41 | *v = enumTypeValue 42 | return nil 43 | } 44 | } 45 | 46 | return fmt.Errorf("%+v is not a valid ColumnSemanticType", value) 47 | } 48 | 49 | // Ptr returns reference to ColumnSemanticType value 50 | func (v ColumnSemanticType) Ptr() *ColumnSemanticType { 51 | return &v 52 | } 53 | 54 | type NullableColumnSemanticType struct { 55 | value *ColumnSemanticType 56 | isSet bool 57 | } 58 | 59 | func (v NullableColumnSemanticType) Get() *ColumnSemanticType { 60 | return v.value 61 | } 62 | 63 | func (v *NullableColumnSemanticType) Set(val *ColumnSemanticType) { 64 | v.value = val 65 | v.isSet = true 66 | } 67 | 68 | func (v NullableColumnSemanticType) IsSet() bool { 69 | return v.isSet 70 | } 71 | 72 | func (v *NullableColumnSemanticType) Unset() { 73 | v.value = nil 74 | v.isSet = false 75 | } 76 | 77 | func NewNullableColumnSemanticType(val *ColumnSemanticType) *NullableColumnSemanticType { 78 | return &NullableColumnSemanticType{value: val, isSet: true} 79 | } 80 | 81 | func (v NullableColumnSemanticType) MarshalJSON() ([]byte, error) { 82 | return json.Marshal(v.value) 83 | } 84 | 85 | func (v *NullableColumnSemanticType) UnmarshalJSON(src []byte) error { 86 | v.isSet = true 87 | return json.Unmarshal(src, &v.value) 88 | } 89 | -------------------------------------------------------------------------------- /api/model_health_check_status.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // HealthCheckStatus the model 'HealthCheckStatus' 19 | type HealthCheckStatus string 20 | 21 | // List of HealthCheckStatus 22 | const ( 23 | HEALTHCHECKSTATUS_PASS HealthCheckStatus = "pass" 24 | HEALTHCHECKSTATUS_FAIL HealthCheckStatus = "fail" 25 | ) 26 | 27 | func HealthCheckStatusValues() []HealthCheckStatus { 28 | return []HealthCheckStatus{"pass", "fail"} 29 | } 30 | 31 | func (v *HealthCheckStatus) UnmarshalJSON(src []byte) error { 32 | var value string 33 | err := json.Unmarshal(src, &value) 34 | if err != nil { 35 | return err 36 | } 37 | enumTypeValue := HealthCheckStatus(value) 38 | for _, existing := range []HealthCheckStatus{"pass", "fail"} { 39 | if existing == enumTypeValue { 40 | *v = enumTypeValue 41 | return nil 42 | } 43 | } 44 | 45 | return fmt.Errorf("%+v is not a valid HealthCheckStatus", value) 46 | } 47 | 48 | // Ptr returns reference to HealthCheckStatus value 49 | func (v HealthCheckStatus) Ptr() *HealthCheckStatus { 50 | return &v 51 | } 52 | 53 | type NullableHealthCheckStatus struct { 54 | value *HealthCheckStatus 55 | isSet bool 56 | } 57 | 58 | func (v NullableHealthCheckStatus) Get() *HealthCheckStatus { 59 | return v.value 60 | } 61 | 62 | func (v *NullableHealthCheckStatus) Set(val *HealthCheckStatus) { 63 | v.value = val 64 | v.isSet = true 65 | } 66 | 67 | func (v NullableHealthCheckStatus) IsSet() bool { 68 | return v.isSet 69 | } 70 | 71 | func (v *NullableHealthCheckStatus) Unset() { 72 | v.value = nil 73 | v.isSet = false 74 | } 75 | 76 | func NewNullableHealthCheckStatus(val *HealthCheckStatus) *NullableHealthCheckStatus { 77 | return &NullableHealthCheckStatus{value: val, isSet: true} 78 | } 79 | 80 | func (v NullableHealthCheckStatus) MarshalJSON() ([]byte, error) { 81 | return json.Marshal(v.value) 82 | } 83 | 84 | func (v *NullableHealthCheckStatus) UnmarshalJSON(src []byte) error { 85 | v.isSet = true 86 | return json.Unmarshal(src, &v.value) 87 | } 88 | -------------------------------------------------------------------------------- /api/model_line_protocol_length_error_code.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // LineProtocolLengthErrorCode Code is the machine-readable error code. 19 | type LineProtocolLengthErrorCode string 20 | 21 | // List of LineProtocolLengthErrorCode 22 | const ( 23 | LINEPROTOCOLLENGTHERRORCODE_INVALID LineProtocolLengthErrorCode = "invalid" 24 | ) 25 | 26 | func LineProtocolLengthErrorCodeValues() []LineProtocolLengthErrorCode { 27 | return []LineProtocolLengthErrorCode{"invalid"} 28 | } 29 | 30 | func (v *LineProtocolLengthErrorCode) UnmarshalJSON(src []byte) error { 31 | var value string 32 | err := json.Unmarshal(src, &value) 33 | if err != nil { 34 | return err 35 | } 36 | enumTypeValue := LineProtocolLengthErrorCode(value) 37 | for _, existing := range []LineProtocolLengthErrorCode{"invalid"} { 38 | if existing == enumTypeValue { 39 | *v = enumTypeValue 40 | return nil 41 | } 42 | } 43 | 44 | return fmt.Errorf("%+v is not a valid LineProtocolLengthErrorCode", value) 45 | } 46 | 47 | // Ptr returns reference to LineProtocolLengthErrorCode value 48 | func (v LineProtocolLengthErrorCode) Ptr() *LineProtocolLengthErrorCode { 49 | return &v 50 | } 51 | 52 | type NullableLineProtocolLengthErrorCode struct { 53 | value *LineProtocolLengthErrorCode 54 | isSet bool 55 | } 56 | 57 | func (v NullableLineProtocolLengthErrorCode) Get() *LineProtocolLengthErrorCode { 58 | return v.value 59 | } 60 | 61 | func (v *NullableLineProtocolLengthErrorCode) Set(val *LineProtocolLengthErrorCode) { 62 | v.value = val 63 | v.isSet = true 64 | } 65 | 66 | func (v NullableLineProtocolLengthErrorCode) IsSet() bool { 67 | return v.isSet 68 | } 69 | 70 | func (v *NullableLineProtocolLengthErrorCode) Unset() { 71 | v.value = nil 72 | v.isSet = false 73 | } 74 | 75 | func NewNullableLineProtocolLengthErrorCode(val *LineProtocolLengthErrorCode) *NullableLineProtocolLengthErrorCode { 76 | return &NullableLineProtocolLengthErrorCode{value: val, isSet: true} 77 | } 78 | 79 | func (v NullableLineProtocolLengthErrorCode) MarshalJSON() ([]byte, error) { 80 | return json.Marshal(v.value) 81 | } 82 | 83 | func (v *NullableLineProtocolLengthErrorCode) UnmarshalJSON(src []byte) error { 84 | v.isSet = true 85 | return json.Unmarshal(src, &v.value) 86 | } 87 | -------------------------------------------------------------------------------- /api/model_schema_type.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // SchemaType the model 'SchemaType' 19 | type SchemaType string 20 | 21 | // List of SchemaType 22 | const ( 23 | SCHEMATYPE_IMPLICIT SchemaType = "implicit" 24 | SCHEMATYPE_EXPLICIT SchemaType = "explicit" 25 | ) 26 | 27 | func SchemaTypeValues() []SchemaType { 28 | return []SchemaType{"implicit", "explicit"} 29 | } 30 | 31 | func (v *SchemaType) UnmarshalJSON(src []byte) error { 32 | var value string 33 | err := json.Unmarshal(src, &value) 34 | if err != nil { 35 | return err 36 | } 37 | enumTypeValue := SchemaType(value) 38 | for _, existing := range []SchemaType{"implicit", "explicit"} { 39 | if existing == enumTypeValue { 40 | *v = enumTypeValue 41 | return nil 42 | } 43 | } 44 | 45 | return fmt.Errorf("%+v is not a valid SchemaType", value) 46 | } 47 | 48 | // Ptr returns reference to SchemaType value 49 | func (v SchemaType) Ptr() *SchemaType { 50 | return &v 51 | } 52 | 53 | type NullableSchemaType struct { 54 | value *SchemaType 55 | isSet bool 56 | } 57 | 58 | func (v NullableSchemaType) Get() *SchemaType { 59 | return v.value 60 | } 61 | 62 | func (v *NullableSchemaType) Set(val *SchemaType) { 63 | v.value = val 64 | v.isSet = true 65 | } 66 | 67 | func (v NullableSchemaType) IsSet() bool { 68 | return v.isSet 69 | } 70 | 71 | func (v *NullableSchemaType) Unset() { 72 | v.value = nil 73 | v.isSet = false 74 | } 75 | 76 | func NewNullableSchemaType(val *SchemaType) *NullableSchemaType { 77 | return &NullableSchemaType{value: val, isSet: true} 78 | } 79 | 80 | func (v NullableSchemaType) MarshalJSON() ([]byte, error) { 81 | return json.Marshal(v.value) 82 | } 83 | 84 | func (v *NullableSchemaType) UnmarshalJSON(src []byte) error { 85 | v.isSet = true 86 | return json.Unmarshal(src, &v.value) 87 | } 88 | -------------------------------------------------------------------------------- /api/model_script_language.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // ScriptLanguage the model 'ScriptLanguage' 19 | type ScriptLanguage string 20 | 21 | // List of ScriptLanguage 22 | const ( 23 | SCRIPTLANGUAGE_FLUX ScriptLanguage = "flux" 24 | SCRIPTLANGUAGE_SQL ScriptLanguage = "sql" 25 | ) 26 | 27 | func ScriptLanguageValues() []ScriptLanguage { 28 | return []ScriptLanguage{"flux", "sql"} 29 | } 30 | 31 | func (v *ScriptLanguage) UnmarshalJSON(src []byte) error { 32 | var value string 33 | err := json.Unmarshal(src, &value) 34 | if err != nil { 35 | return err 36 | } 37 | enumTypeValue := ScriptLanguage(value) 38 | for _, existing := range []ScriptLanguage{"flux", "sql"} { 39 | if existing == enumTypeValue { 40 | *v = enumTypeValue 41 | return nil 42 | } 43 | } 44 | 45 | return fmt.Errorf("%+v is not a valid ScriptLanguage", value) 46 | } 47 | 48 | // Ptr returns reference to ScriptLanguage value 49 | func (v ScriptLanguage) Ptr() *ScriptLanguage { 50 | return &v 51 | } 52 | 53 | type NullableScriptLanguage struct { 54 | value *ScriptLanguage 55 | isSet bool 56 | } 57 | 58 | func (v NullableScriptLanguage) Get() *ScriptLanguage { 59 | return v.value 60 | } 61 | 62 | func (v *NullableScriptLanguage) Set(val *ScriptLanguage) { 63 | v.value = val 64 | v.isSet = true 65 | } 66 | 67 | func (v NullableScriptLanguage) IsSet() bool { 68 | return v.isSet 69 | } 70 | 71 | func (v *NullableScriptLanguage) Unset() { 72 | v.value = nil 73 | v.isSet = false 74 | } 75 | 76 | func NewNullableScriptLanguage(val *ScriptLanguage) *NullableScriptLanguage { 77 | return &NullableScriptLanguage{value: val, isSet: true} 78 | } 79 | 80 | func (v NullableScriptLanguage) MarshalJSON() ([]byte, error) { 81 | return json.Marshal(v.value) 82 | } 83 | 84 | func (v *NullableScriptLanguage) UnmarshalJSON(src []byte) error { 85 | v.isSet = true 86 | return json.Unmarshal(src, &v.value) 87 | } 88 | -------------------------------------------------------------------------------- /api/model_task_status_type.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // TaskStatusType `inactive` cancels scheduled runs and prevents manual runs of the task. 19 | type TaskStatusType string 20 | 21 | // List of TaskStatusType 22 | const ( 23 | TASKSTATUSTYPE_ACTIVE TaskStatusType = "active" 24 | TASKSTATUSTYPE_INACTIVE TaskStatusType = "inactive" 25 | ) 26 | 27 | func TaskStatusTypeValues() []TaskStatusType { 28 | return []TaskStatusType{"active", "inactive"} 29 | } 30 | 31 | func (v *TaskStatusType) UnmarshalJSON(src []byte) error { 32 | var value string 33 | err := json.Unmarshal(src, &value) 34 | if err != nil { 35 | return err 36 | } 37 | enumTypeValue := TaskStatusType(value) 38 | for _, existing := range []TaskStatusType{"active", "inactive"} { 39 | if existing == enumTypeValue { 40 | *v = enumTypeValue 41 | return nil 42 | } 43 | } 44 | 45 | return fmt.Errorf("%+v is not a valid TaskStatusType", value) 46 | } 47 | 48 | // Ptr returns reference to TaskStatusType value 49 | func (v TaskStatusType) Ptr() *TaskStatusType { 50 | return &v 51 | } 52 | 53 | type NullableTaskStatusType struct { 54 | value *TaskStatusType 55 | isSet bool 56 | } 57 | 58 | func (v NullableTaskStatusType) Get() *TaskStatusType { 59 | return v.value 60 | } 61 | 62 | func (v *NullableTaskStatusType) Set(val *TaskStatusType) { 63 | v.value = val 64 | v.isSet = true 65 | } 66 | 67 | func (v NullableTaskStatusType) IsSet() bool { 68 | return v.isSet 69 | } 70 | 71 | func (v *NullableTaskStatusType) Unset() { 72 | v.value = nil 73 | v.isSet = false 74 | } 75 | 76 | func NewNullableTaskStatusType(val *TaskStatusType) *NullableTaskStatusType { 77 | return &NullableTaskStatusType{value: val, isSet: true} 78 | } 79 | 80 | func (v NullableTaskStatusType) MarshalJSON() ([]byte, error) { 81 | return json.Marshal(v.value) 82 | } 83 | 84 | func (v *NullableTaskStatusType) UnmarshalJSON(src []byte) error { 85 | v.isSet = true 86 | return json.Unmarshal(src, &v.value) 87 | } 88 | -------------------------------------------------------------------------------- /api/model_template_apply_action_kind.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // TemplateApplyActionKind the model 'TemplateApplyActionKind' 19 | type TemplateApplyActionKind string 20 | 21 | // List of TemplateApplyActionKind 22 | const ( 23 | TEMPLATEAPPLYACTIONKIND_SKIP_KIND TemplateApplyActionKind = "skipKind" 24 | TEMPLATEAPPLYACTIONKIND_SKIP_RESOURCE TemplateApplyActionKind = "skipResource" 25 | ) 26 | 27 | func TemplateApplyActionKindValues() []TemplateApplyActionKind { 28 | return []TemplateApplyActionKind{"skipKind", "skipResource"} 29 | } 30 | 31 | func (v *TemplateApplyActionKind) UnmarshalJSON(src []byte) error { 32 | var value string 33 | err := json.Unmarshal(src, &value) 34 | if err != nil { 35 | return err 36 | } 37 | enumTypeValue := TemplateApplyActionKind(value) 38 | for _, existing := range []TemplateApplyActionKind{"skipKind", "skipResource"} { 39 | if existing == enumTypeValue { 40 | *v = enumTypeValue 41 | return nil 42 | } 43 | } 44 | 45 | return fmt.Errorf("%+v is not a valid TemplateApplyActionKind", value) 46 | } 47 | 48 | // Ptr returns reference to TemplateApplyActionKind value 49 | func (v TemplateApplyActionKind) Ptr() *TemplateApplyActionKind { 50 | return &v 51 | } 52 | 53 | type NullableTemplateApplyActionKind struct { 54 | value *TemplateApplyActionKind 55 | isSet bool 56 | } 57 | 58 | func (v NullableTemplateApplyActionKind) Get() *TemplateApplyActionKind { 59 | return v.value 60 | } 61 | 62 | func (v *NullableTemplateApplyActionKind) Set(val *TemplateApplyActionKind) { 63 | v.value = val 64 | v.isSet = true 65 | } 66 | 67 | func (v NullableTemplateApplyActionKind) IsSet() bool { 68 | return v.isSet 69 | } 70 | 71 | func (v *NullableTemplateApplyActionKind) Unset() { 72 | v.value = nil 73 | v.isSet = false 74 | } 75 | 76 | func NewNullableTemplateApplyActionKind(val *TemplateApplyActionKind) *NullableTemplateApplyActionKind { 77 | return &NullableTemplateApplyActionKind{value: val, isSet: true} 78 | } 79 | 80 | func (v NullableTemplateApplyActionKind) MarshalJSON() ([]byte, error) { 81 | return json.Marshal(v.value) 82 | } 83 | 84 | func (v *NullableTemplateApplyActionKind) UnmarshalJSON(src []byte) error { 85 | v.isSet = true 86 | return json.Unmarshal(src, &v.value) 87 | } 88 | -------------------------------------------------------------------------------- /api/model_template_summary_variable_args_render.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | func (args *TemplateSummaryVariableArgs) Render() string { 11 | if args == nil { 12 | return "" 13 | } 14 | switch args.Type { 15 | case "map": 16 | b, err := json.Marshal(args.Values) 17 | if err != nil { 18 | log.Printf("WARN: failed to parse map-variable args: expected JSON, got %v\n", args.Values) 19 | return "" 20 | } 21 | return string(b) 22 | case "constant": 23 | values, ok := args.Values.([]interface{}) 24 | if !ok { 25 | log.Printf("WARN: failed to parse constant-variable args: expected array, got %v\n", args.Values) 26 | return "" 27 | } 28 | var out []string 29 | for _, v := range values { 30 | out = append(out, fmt.Sprintf("%q", v)) 31 | } 32 | return fmt.Sprintf("[%s]", strings.Join(out, " ")) 33 | case "query": 34 | values, ok := args.Values.(map[string]interface{}) 35 | if !ok { 36 | log.Printf("WARN: failed to parse query-variable args: expected JSON object, got %v\n", args.Values) 37 | return "" 38 | } 39 | qVal, ok := values["query"] 40 | if !ok { 41 | log.Printf("WARN: failed to parse query-variable args: no 'query' key in %v\n", values) 42 | return "" 43 | } 44 | lVal, ok := values["language"] 45 | if !ok { 46 | log.Printf("WARN: failed to parse query-variable args: no 'language' key in %v\n", values) 47 | return "" 48 | } 49 | return fmt.Sprintf("language=%q query=%q", lVal, qVal) 50 | default: 51 | } 52 | return "" 53 | } 54 | -------------------------------------------------------------------------------- /api/model_write_precision.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | // WritePrecision the model 'WritePrecision' 19 | type WritePrecision string 20 | 21 | // List of WritePrecision 22 | const ( 23 | WRITEPRECISION_MS WritePrecision = "ms" 24 | WRITEPRECISION_S WritePrecision = "s" 25 | WRITEPRECISION_US WritePrecision = "us" 26 | WRITEPRECISION_NS WritePrecision = "ns" 27 | ) 28 | 29 | func WritePrecisionValues() []WritePrecision { 30 | return []WritePrecision{"ms", "s", "us", "ns"} 31 | } 32 | 33 | func (v *WritePrecision) UnmarshalJSON(src []byte) error { 34 | var value string 35 | err := json.Unmarshal(src, &value) 36 | if err != nil { 37 | return err 38 | } 39 | enumTypeValue := WritePrecision(value) 40 | for _, existing := range []WritePrecision{"ms", "s", "us", "ns"} { 41 | if existing == enumTypeValue { 42 | *v = enumTypeValue 43 | return nil 44 | } 45 | } 46 | 47 | return fmt.Errorf("%+v is not a valid WritePrecision", value) 48 | } 49 | 50 | // Ptr returns reference to WritePrecision value 51 | func (v WritePrecision) Ptr() *WritePrecision { 52 | return &v 53 | } 54 | 55 | type NullableWritePrecision struct { 56 | value *WritePrecision 57 | isSet bool 58 | } 59 | 60 | func (v NullableWritePrecision) Get() *WritePrecision { 61 | return v.value 62 | } 63 | 64 | func (v *NullableWritePrecision) Set(val *WritePrecision) { 65 | v.value = val 66 | v.isSet = true 67 | } 68 | 69 | func (v NullableWritePrecision) IsSet() bool { 70 | return v.isSet 71 | } 72 | 73 | func (v *NullableWritePrecision) Unset() { 74 | v.value = nil 75 | v.isSet = false 76 | } 77 | 78 | func NewNullableWritePrecision(val *WritePrecision) *NullableWritePrecision { 79 | return &NullableWritePrecision{value: val, isSet: true} 80 | } 81 | 82 | func (v NullableWritePrecision) MarshalJSON() ([]byte, error) { 83 | return json.Marshal(v.value) 84 | } 85 | 86 | func (v *NullableWritePrecision) UnmarshalJSON(src []byte) error { 87 | v.isSet = true 88 | return json.Unmarshal(src, &v.value) 89 | } 90 | -------------------------------------------------------------------------------- /api/permission_string.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | func (o Permission) String() string { 4 | ret := o.GetAction() + ":" 5 | r := o.GetResource() 6 | 7 | if r.GetOrgID() != "" { 8 | ret += "orgs/" + r.GetOrgID() 9 | } 10 | ret += "/" + r.GetType() 11 | if r.GetId() != "" { 12 | ret += "/" + r.GetId() 13 | } 14 | return ret 15 | } 16 | -------------------------------------------------------------------------------- /api/response.gen.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Subset of Influx API covered by Influx CLI 3 | * 4 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 5 | * 6 | * API version: 2.0.0 7 | */ 8 | 9 | // Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. 10 | 11 | package api 12 | 13 | import ( 14 | "net/http" 15 | ) 16 | 17 | // APIResponse stores the API response returned by the server. 18 | type APIResponse struct { 19 | *http.Response `json:"-"` 20 | Message string `json:"message,omitempty"` 21 | // Operation is the name of the OpenAPI operation. 22 | Operation string `json:"operation,omitempty"` 23 | // RequestURL is the request URL. This value is always available, even if the 24 | // embedded *http.Response is nil. 25 | RequestURL string `json:"url,omitempty"` 26 | // Method is the HTTP method used for the request. This value is always 27 | // available, even if the embedded *http.Response is nil. 28 | Method string `json:"method,omitempty"` 29 | // Payload holds the contents of the response body (which may be nil or empty). 30 | // This is provided here as the raw response.Body() reader will have already 31 | // been drained. 32 | Payload []byte `json:"-"` 33 | } 34 | 35 | // NewAPIResponse returns a new APIResponse object. 36 | func NewAPIResponse(r *http.Response) *APIResponse { 37 | 38 | response := &APIResponse{Response: r} 39 | return response 40 | } 41 | 42 | // NewAPIResponseWithError returns a new APIResponse object with the provided error message. 43 | func NewAPIResponseWithError(errorMessage string) *APIResponse { 44 | 45 | response := &APIResponse{Message: errorMessage} 46 | return response 47 | } 48 | -------------------------------------------------------------------------------- /api/schema_type.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func (v SchemaType) String() string { 8 | return string(v) 9 | } 10 | 11 | // Set implements the cli.Generic interface for parsing 12 | // flags. 13 | func (v *SchemaType) Set(s string) error { 14 | switch s { 15 | case string(SCHEMATYPE_IMPLICIT): 16 | *v = SCHEMATYPE_IMPLICIT 17 | case string(SCHEMATYPE_EXPLICIT): 18 | *v = SCHEMATYPE_EXPLICIT 19 | default: 20 | return fmt.Errorf("unsupported schema type: %q", s) 21 | } 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /api/templates/model_enum.mustache: -------------------------------------------------------------------------------- 1 | // {{{classname}}} {{#description}}{{{.}}}{{/description}}{{^description}}the model '{{{classname}}}'{{/description}} 2 | type {{{classname}}} {{^format}}{{dataType}}{{/format}}{{#format}}{{{format}}}{{/format}} 3 | 4 | // List of {{{name}}} 5 | const ( 6 | {{#allowableValues}} 7 | {{#enumVars}} 8 | {{^-first}} 9 | {{/-first}} 10 | {{#enumClassPrefix}}{{{classname.toUpperCase}}}_{{/enumClassPrefix}}{{name}} {{{classname}}} = {{{value}}} 11 | {{/enumVars}} 12 | {{/allowableValues}} 13 | ) 14 | 15 | func {{{classname}}}Values() []{{{classname}}} { 16 | return []{{classname}}{ {{#allowableValues}}{{#enumVars}}{{{value}}}, {{/enumVars}} {{/allowableValues}} } 17 | } 18 | 19 | func (v *{{{classname}}}) UnmarshalJSON(src []byte) error { 20 | var value {{^format}}{{dataType}}{{/format}}{{#format}}{{{format}}}{{/format}} 21 | err := json.Unmarshal(src, &value) 22 | if err != nil { 23 | return err 24 | } 25 | enumTypeValue := {{{classname}}}(value) 26 | for _, existing := range []{{classname}}{ {{#allowableValues}}{{#enumVars}}{{{value}}}, {{/enumVars}} {{/allowableValues}} } { 27 | if existing == enumTypeValue { 28 | *v = enumTypeValue 29 | return nil 30 | } 31 | } 32 | 33 | return fmt.Errorf("%+v is not a valid {{classname}}", value) 34 | } 35 | 36 | // Ptr returns reference to {{{name}}} value 37 | func (v {{{classname}}}) Ptr() *{{{classname}}} { 38 | return &v 39 | } 40 | 41 | type Nullable{{{classname}}} struct { 42 | value *{{{classname}}} 43 | isSet bool 44 | } 45 | 46 | func (v Nullable{{classname}}) Get() *{{classname}} { 47 | return v.value 48 | } 49 | 50 | func (v *Nullable{{classname}}) Set(val *{{classname}}) { 51 | v.value = val 52 | v.isSet = true 53 | } 54 | 55 | func (v Nullable{{classname}}) IsSet() bool { 56 | return v.isSet 57 | } 58 | 59 | func (v *Nullable{{classname}}) Unset() { 60 | v.value = nil 61 | v.isSet = false 62 | } 63 | 64 | func NewNullable{{classname}}(val *{{classname}}) *Nullable{{classname}} { 65 | return &Nullable{{classname}}{value: val, isSet: true} 66 | } 67 | 68 | func (v Nullable{{{classname}}}) MarshalJSON() ([]byte, error) { 69 | return json.Marshal(v.value) 70 | } 71 | 72 | func (v *Nullable{{{classname}}}) UnmarshalJSON(src []byte) error { 73 | v.isSet = true 74 | return json.Unmarshal(src, &v.value) 75 | } 76 | -------------------------------------------------------------------------------- /api/unmarshal_csv.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // UnmarshalCSV implements the gocsv.TypeUnmarshaller interface 9 | // for decoding CSV. 10 | func (v *ColumnSemanticType) UnmarshalCSV(s string) error { 11 | types := []string{string(COLUMNSEMANTICTYPE_TIMESTAMP), string(COLUMNSEMANTICTYPE_TAG), string(COLUMNSEMANTICTYPE_FIELD)} 12 | for _, t := range types { 13 | if s == t { 14 | *v = ColumnSemanticType(t) 15 | return nil 16 | } 17 | } 18 | return fmt.Errorf("%q is not a valid column type. Valid values are [%s]", s, strings.Join(types, ", ")) 19 | } 20 | 21 | // UnmarshalCSV implements the gocsv.TypeUnmarshaller interface 22 | // for decoding CSV. 23 | func (v *ColumnDataType) UnmarshalCSV(s string) error { 24 | types := []string{ 25 | string(COLUMNDATATYPE_INTEGER), 26 | string(COLUMNDATATYPE_FLOAT), 27 | string(COLUMNDATATYPE_BOOLEAN), 28 | string(COLUMNDATATYPE_STRING), 29 | string(COLUMNDATATYPE_UNSIGNED), 30 | } 31 | 32 | for _, t := range types { 33 | if s == t { 34 | *v = ColumnDataType(t) 35 | return nil 36 | } 37 | } 38 | return fmt.Errorf("%q is not a valid column data type. Valid values are [%s]", s, strings.Join(types, ", ")) 39 | } 40 | -------------------------------------------------------------------------------- /api/write_precision.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func (v WritePrecision) String() string { 8 | return string(v) 9 | } 10 | 11 | func (v *WritePrecision) Set(s string) error { 12 | switch s { 13 | case "ms": 14 | *v = WRITEPRECISION_MS 15 | case "s": 16 | *v = WRITEPRECISION_S 17 | case "us": 18 | *v = WRITEPRECISION_US 19 | case "ns": 20 | *v = WRITEPRECISION_NS 21 | default: 22 | return fmt.Errorf("unsupported precision: %q", s) 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /clients/auth/auth_internal_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/influxdata/influx-cli/v2/api" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_makePermResource(t *testing.T) { 11 | t.Parallel() 12 | 13 | testCases := []struct { 14 | name string 15 | inType string 16 | inId string 17 | inOrgId string 18 | expected api.PermissionResource 19 | }{ 20 | { 21 | name: "only type", 22 | inType: "foo", 23 | expected: api.PermissionResource{Type: "foo"}, 24 | }, 25 | { 26 | name: "type and ID", 27 | inType: "bar", 28 | inId: "12345", 29 | expected: api.PermissionResource{Type: "bar", Id: api.PtrString("12345")}, 30 | }, 31 | { 32 | name: "type and org ID", 33 | inType: "baz", 34 | inOrgId: "45678", 35 | expected: api.PermissionResource{Type: "baz", OrgID: api.PtrString("45678")}, 36 | }, 37 | { 38 | name: "type, ID, and org ID", 39 | inType: "qux", 40 | inId: "12345", 41 | inOrgId: "45678", 42 | expected: api.PermissionResource{Type: "qux", Id: api.PtrString("12345"), OrgID: api.PtrString("45678")}, 43 | }, 44 | { 45 | name: "users", 46 | inType: "users", 47 | inId: "12345", 48 | inOrgId: "45678", 49 | expected: api.PermissionResource{Type: "users", Id: api.PtrString("12345"), OrgID: nil}, 50 | }, 51 | { 52 | name: "orgs", 53 | inType: "orgs", 54 | inId: "12345", 55 | inOrgId: "45678", 56 | expected: api.PermissionResource{Type: "orgs", Id: api.PtrString("12345"), OrgID: nil}, 57 | }, 58 | } 59 | 60 | for _, tc := range testCases { 61 | tc := tc 62 | t.Run(tc.name, func(t *testing.T) { 63 | t.Parallel() 64 | require.Equal(t, tc.expected, makePermResource(tc.inType, tc.inId, tc.inOrgId)) 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /clients/bucket/client.go: -------------------------------------------------------------------------------- 1 | package bucket 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | "github.com/influxdata/influx-cli/v2/clients" 9 | ) 10 | 11 | const InfiniteRetention = 0 12 | 13 | var ErrMustSpecifyOrgDeleteByName = fmt.Errorf("%s when deleting a bucket by name", clients.ErrMustSpecifyOrg) 14 | 15 | type Client struct { 16 | clients.CLI 17 | api.OrganizationsApi 18 | api.BucketsApi 19 | } 20 | 21 | type bucketPrintOptions struct { 22 | deleted bool 23 | bucket *api.Bucket 24 | buckets []api.Bucket 25 | } 26 | 27 | func (c Client) printBuckets(options bucketPrintOptions) error { 28 | if c.PrintAsJSON { 29 | var v interface{} 30 | if options.bucket != nil { 31 | v = options.bucket 32 | } else { 33 | v = options.buckets 34 | } 35 | return c.PrintJSON(v) 36 | } 37 | 38 | headers := []string{"ID", "Name", "Retention", "Shard group duration", "Organization ID", "Schema Type"} 39 | if options.deleted { 40 | headers = append(headers, "Deleted") 41 | } 42 | 43 | if options.bucket != nil { 44 | options.buckets = append(options.buckets, *options.bucket) 45 | } 46 | 47 | var rows []map[string]interface{} 48 | for _, bkt := range options.buckets { 49 | var rpDuration time.Duration // zero value implies infinite retention policy 50 | var sgDuration time.Duration // zero value implies the server should pick a value 51 | 52 | if rules := bkt.GetRetentionRules(); len(rules) > 0 { 53 | rpDuration = time.Duration(rules[0].GetEverySeconds()) * time.Second 54 | sgDuration = time.Duration(rules[0].GetShardGroupDurationSeconds()) * time.Second 55 | } 56 | 57 | rp := rpDuration.String() 58 | if rpDuration == InfiniteRetention { 59 | rp = "infinite" 60 | } 61 | sgDur := sgDuration.String() 62 | // ShardGroupDuration will be zero if listing buckets from InfluxDB Cloud. 63 | // Show something more useful here in that case. 64 | if sgDuration == 0 { 65 | sgDur = "n/a" 66 | } 67 | 68 | schemaType := bkt.GetSchemaType() 69 | if schemaType == "" { 70 | // schemaType will be empty when querying OSS. 71 | schemaType = api.SCHEMATYPE_IMPLICIT 72 | } 73 | 74 | row := map[string]interface{}{ 75 | "ID": bkt.GetId(), 76 | "Name": bkt.GetName(), 77 | "Retention": rp, 78 | "Shard group duration": sgDur, 79 | "Organization ID": bkt.GetOrgID(), 80 | "Schema Type": schemaType, 81 | } 82 | if options.deleted { 83 | row["Deleted"] = true 84 | } 85 | rows = append(rows, row) 86 | } 87 | 88 | return c.PrintTable(headers, rows...) 89 | } 90 | -------------------------------------------------------------------------------- /clients/bucket/create.go: -------------------------------------------------------------------------------- 1 | package bucket 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/influxdata/influx-cli/v2/clients" 10 | "github.com/influxdata/influx-cli/v2/pkg/duration" 11 | ) 12 | 13 | type BucketsCreateParams struct { 14 | clients.OrgParams 15 | Name string 16 | Description string 17 | Retention string 18 | ShardGroupDuration string 19 | SchemaType api.SchemaType 20 | } 21 | 22 | func (c Client) Create(ctx context.Context, params *BucketsCreateParams) error { 23 | orgId, err := params.GetOrgID(ctx, c.ActiveConfig, c.OrganizationsApi) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | rp, err := duration.RawDurationToTimeDuration(params.Retention) 29 | if err != nil { 30 | return err 31 | } 32 | sgd, err := duration.RawDurationToTimeDuration(params.ShardGroupDuration) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | var rr []api.RetentionRule 38 | reqBody := api.PostBucketRequest{ 39 | OrgID: orgId, 40 | Name: params.Name, 41 | RetentionRules: &rr, 42 | SchemaType: ¶ms.SchemaType, 43 | } 44 | if params.Description != "" { 45 | reqBody.Description = ¶ms.Description 46 | } 47 | // Only append a retention rule if the user wants to explicitly set 48 | // a parameter on the rule. 49 | // 50 | // This is for backwards-compatibility with older versions of the API, 51 | // which didn't support setting shard-group durations and used an empty 52 | // array of rules to represent infinite retention. 53 | if rp > 0 || sgd > 0 { 54 | rule := api.NewRetentionRuleWithDefaults() 55 | if rp > 0 { 56 | rule.SetEverySeconds(int64(rp.Round(time.Second) / time.Second)) 57 | } 58 | if sgd > 0 { 59 | rule.SetShardGroupDurationSeconds(int64(sgd.Round(time.Second) / time.Second)) 60 | } 61 | *reqBody.RetentionRules = append(*reqBody.RetentionRules, *rule) 62 | } 63 | 64 | bucket, err := c.PostBuckets(ctx).PostBucketRequest(reqBody).Execute() 65 | if err != nil { 66 | return fmt.Errorf("failed to create bucket: %w", err) 67 | } 68 | 69 | return c.printBuckets(bucketPrintOptions{bucket: &bucket}) 70 | } 71 | -------------------------------------------------------------------------------- /clients/bucket/delete.go: -------------------------------------------------------------------------------- 1 | package bucket 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | "github.com/influxdata/influx-cli/v2/clients" 9 | ) 10 | 11 | type BucketsDeleteParams struct { 12 | clients.OrgBucketParams 13 | } 14 | 15 | func (c Client) Delete(ctx context.Context, params *BucketsDeleteParams) error { 16 | if params.BucketID == "" && params.BucketName == "" { 17 | return clients.ErrMustSpecifyBucket 18 | } 19 | 20 | var bucket api.Bucket 21 | var getReq api.ApiGetBucketsRequest 22 | if params.BucketID != "" { 23 | getReq = c.GetBuckets(ctx).Id(params.BucketID) 24 | } else { 25 | if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" { 26 | return ErrMustSpecifyOrgDeleteByName 27 | } 28 | getReq = c.GetBuckets(ctx) 29 | getReq = getReq.Name(params.BucketName) 30 | if params.OrgID != "" { 31 | getReq = getReq.OrgID(params.OrgID) 32 | } 33 | if params.OrgName != "" { 34 | getReq = getReq.Org(params.OrgName) 35 | } 36 | if params.OrgID == "" && params.OrgName == "" { 37 | getReq = getReq.Org(c.ActiveConfig.Org) 38 | } 39 | } 40 | 41 | displayId := params.BucketID 42 | if displayId == "" { 43 | displayId = params.BucketName 44 | } 45 | 46 | resp, err := getReq.Execute() 47 | if err != nil { 48 | return fmt.Errorf("failed to find bucket %q: %w", displayId, err) 49 | } 50 | buckets := resp.GetBuckets() 51 | if len(buckets) == 0 { 52 | return fmt.Errorf("bucket %q not found", displayId) 53 | } 54 | bucket = buckets[0] 55 | 56 | if err := c.DeleteBucketsID(ctx, bucket.GetId()).Execute(); err != nil { 57 | return fmt.Errorf("failed to delete bucket %q: %w", displayId, err) 58 | } 59 | 60 | return c.printBuckets(bucketPrintOptions{bucket: &bucket, deleted: true}) 61 | } 62 | -------------------------------------------------------------------------------- /clients/bucket/update.go: -------------------------------------------------------------------------------- 1 | package bucket 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/influxdata/influx-cli/v2/clients" 10 | "github.com/influxdata/influx-cli/v2/pkg/duration" 11 | ) 12 | 13 | type BucketsUpdateParams struct { 14 | clients.BucketParams 15 | Description string 16 | Retention string 17 | ShardGroupDuration string 18 | } 19 | 20 | func (c Client) Update(ctx context.Context, params *BucketsUpdateParams) error { 21 | reqBody := api.PatchBucketRequest{} 22 | if params.BucketName != "" { 23 | reqBody.SetName(params.BucketName) 24 | } 25 | if params.Description != "" { 26 | reqBody.SetDescription(params.Description) 27 | } 28 | if params.Retention != "" || params.ShardGroupDuration != "" { 29 | patchRule := api.NewPatchRetentionRuleWithDefaults() 30 | if params.Retention != "" { 31 | rp, err := duration.RawDurationToTimeDuration(params.Retention) 32 | if err != nil { 33 | return err 34 | } 35 | patchRule.SetEverySeconds(int64(rp.Round(time.Second) / time.Second)) 36 | } 37 | if params.ShardGroupDuration != "" { 38 | sgd, err := duration.RawDurationToTimeDuration(params.ShardGroupDuration) 39 | if err != nil { 40 | return err 41 | } 42 | patchRule.SetShardGroupDurationSeconds(int64(sgd.Round(time.Second) / time.Second)) 43 | } 44 | reqBody.SetRetentionRules([]api.PatchRetentionRule{*patchRule}) 45 | } 46 | 47 | bucket, err := c.PatchBucketsID(ctx, params.BucketID).PatchBucketRequest(reqBody).Execute() 48 | if err != nil { 49 | return fmt.Errorf("failed to update bucket %q: %w", params.BucketID, err) 50 | } 51 | 52 | return c.printBuckets(bucketPrintOptions{bucket: &bucket}) 53 | } 54 | -------------------------------------------------------------------------------- /clients/bucket_schema/create.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/influxdata/influx-cli/v2/clients" 10 | ) 11 | 12 | type CreateParams struct { 13 | clients.OrgBucketParams 14 | Name string 15 | Stdin io.Reader 16 | ColumnsFile string 17 | ColumnsFormat ColumnsFormat 18 | ExtendedOutput bool 19 | } 20 | 21 | func (c Client) Create(ctx context.Context, params CreateParams) error { 22 | cols, err := c.readColumns(params.Stdin, params.ColumnsFormat, params.ColumnsFile) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | res, err := c.CreateMeasurementSchema(ctx, ids.BucketID). 33 | OrgID(ids.OrgID). 34 | MeasurementSchemaCreateRequest(api.MeasurementSchemaCreateRequest{ 35 | Name: params.Name, 36 | Columns: cols, 37 | }). 38 | Execute() 39 | 40 | if err != nil { 41 | return fmt.Errorf("failed to create measurement: %w", err) 42 | } 43 | 44 | return c.printMeasurements(ids.BucketID, []api.MeasurementSchema{res}, params.ExtendedOutput) 45 | } 46 | -------------------------------------------------------------------------------- /clients/bucket_schema/csv.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/gocarina/gocsv" 8 | "github.com/influxdata/influx-cli/v2/api" 9 | ) 10 | 11 | type csvColumn struct { 12 | Name string `csv:"name"` 13 | Type api.ColumnSemanticType `csv:"type"` 14 | DataType *api.ColumnDataType `csv:"dataType,omitempty"` 15 | } 16 | 17 | func init() { 18 | gocsv.FailIfUnmatchedStructTags = true 19 | } 20 | 21 | func decodeCSV(r io.Reader) ([]api.MeasurementSchemaColumn, error) { 22 | var cols []csvColumn 23 | err := gocsv.Unmarshal(r, &cols) 24 | if err != nil { 25 | return nil, fmt.Errorf("failed to decode CSV: %w", err) 26 | } 27 | 28 | rows := make([]api.MeasurementSchemaColumn, 0, len(cols)) 29 | for i := range cols { 30 | c := &cols[i] 31 | rows = append(rows, api.MeasurementSchemaColumn{ 32 | Name: c.Name, 33 | Type: c.Type, 34 | DataType: c.DataType, 35 | }) 36 | } 37 | return rows, nil 38 | } 39 | -------------------------------------------------------------------------------- /clients/bucket_schema/csv_test.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/MakeNowJust/heredoc/v2" 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestDecodeCSV(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | data string 17 | exp []api.MeasurementSchemaColumn 18 | expErr string 19 | }{ 20 | { 21 | name: "valid", 22 | data: heredoc.Doc(` 23 | name,type,dataType 24 | time,timestamp, 25 | host,tag, 26 | usage_user,field,float 27 | `), 28 | exp: cols(colTs(), colT("host"), colFF("usage_user")), 29 | }, 30 | { 31 | name: "valid with alternate order", 32 | data: heredoc.Doc(` 33 | type,dataType,name 34 | timestamp,,time 35 | tag,,host 36 | field,float,usage_user 37 | `), 38 | exp: cols(colTs(), colT("host"), colFF("usage_user")), 39 | }, 40 | { 41 | name: "invalid column type", 42 | data: heredoc.Doc(` 43 | name,type,dataType 44 | time,foo, 45 | `), 46 | expErr: `failed to decode CSV: record on line 0; parse error on line 2, column 2: "foo" is not a valid column type. Valid values are [timestamp, tag, field]`, 47 | }, 48 | { 49 | name: "invalid column data type", 50 | data: heredoc.Doc(` 51 | name,type,dataType 52 | time,field,floaty 53 | `), 54 | expErr: `failed to decode CSV: record on line 0; parse error on line 2, column 3: "floaty" is not a valid column data type. Valid values are [integer, float, boolean, string, unsigned]`, 55 | }, 56 | { 57 | name: "invalid dataType header", 58 | data: heredoc.Doc(` 59 | name,type,data_type 60 | time,field,float 61 | time2,field, 62 | `), 63 | expErr: `failed to decode CSV: found unmatched struct field with tags [dataType]`, 64 | }, 65 | { 66 | name: "invalid headers", 67 | data: heredoc.Doc(` 68 | name,foo, 69 | time,field 70 | `), 71 | expErr: `failed to decode CSV: record on line 2: wrong number of fields`, 72 | }, 73 | { 74 | name: "invalid CSV", 75 | data: heredoc.Doc(` 76 | type,type,dataType 77 | time,timestamp 78 | `), 79 | expErr: `failed to decode CSV: record on line 2: wrong number of fields`, 80 | }, 81 | } 82 | for _, tt := range tests { 83 | t.Run(tt.name, func(t *testing.T) { 84 | r := strings.NewReader(tt.data) 85 | got, err := decodeCSV(r) 86 | if tt.expErr != "" { 87 | assert.EqualError(t, err, tt.expErr) 88 | } else { 89 | require.NoError(t, err) 90 | assert.Equal(t, tt.exp, got) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /clients/bucket_schema/format.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path/filepath" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/influxdata/influx-cli/v2/api" 11 | ) 12 | 13 | // ColumnsFormat is a type which defines the supported formats 14 | type ColumnsFormat int 15 | 16 | const ( 17 | ColumnsFormatAuto ColumnsFormat = iota 18 | ColumnsFormatCSV 19 | ColumnsFormatNDJson 20 | ColumnsFormatJson 21 | ) 22 | 23 | func (f *ColumnsFormat) Set(v string) error { 24 | switch v { 25 | case "auto": 26 | *f = ColumnsFormatAuto 27 | case "csv": 28 | *f = ColumnsFormatCSV 29 | case "ndjson": 30 | *f = ColumnsFormatNDJson 31 | case "json": 32 | *f = ColumnsFormatJson 33 | default: 34 | return fmt.Errorf("invalid columns-format: %s, expected [csv, ndjson, json, auto]", v) 35 | } 36 | return nil 37 | } 38 | 39 | func (f ColumnsFormat) String() string { 40 | switch f { 41 | case ColumnsFormatAuto: 42 | return "auto" 43 | case ColumnsFormatCSV: 44 | return "csv" 45 | case ColumnsFormatNDJson: 46 | return "ndjson" 47 | case ColumnsFormatJson: 48 | return "json" 49 | default: 50 | return "schema.Format(" + strconv.FormatInt(int64(f), 10) + ")" 51 | } 52 | } 53 | 54 | // DecoderFn uses f and path to return a function capable of decoding 55 | // measurement schema columns from a given io.Reader. If no combination 56 | // of decoder exists for f and path, DecoderFn returns an error. 57 | func (f ColumnsFormat) DecoderFn(path string) (ColumnsDecoderFn, error) { 58 | ff := f 59 | if ff == ColumnsFormatAuto { 60 | ext := filepath.Ext(path) 61 | switch { 62 | case strings.EqualFold(ext, ".csv"): 63 | ff = ColumnsFormatCSV 64 | case strings.EqualFold(ext, ".json"): 65 | ff = ColumnsFormatJson 66 | case strings.EqualFold(ext, ".ndjson") || strings.EqualFold(ext, ".jsonl"): 67 | ff = ColumnsFormatNDJson 68 | } 69 | } 70 | 71 | switch ff { 72 | case ColumnsFormatCSV: 73 | return decodeCSV, nil 74 | case ColumnsFormatNDJson: 75 | return decodeNDJson, nil 76 | case ColumnsFormatJson: 77 | return decodeJson, nil 78 | } 79 | 80 | return nil, fmt.Errorf("unable to guess format for file %q", path) 81 | } 82 | 83 | // ColumnsDecoderFn is a function which decodes a slice of api.MeasurementSchemaColumn 84 | // elements from r. 85 | type ColumnsDecoderFn func(r io.Reader) ([]api.MeasurementSchemaColumn, error) 86 | -------------------------------------------------------------------------------- /clients/bucket_schema/json.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/influxdata/influx-cli/v2/api" 9 | ) 10 | 11 | func decodeJson(r io.Reader) ([]api.MeasurementSchemaColumn, error) { 12 | var rows []api.MeasurementSchemaColumn 13 | if err := json.NewDecoder(r).Decode(&rows); err != nil { 14 | return nil, fmt.Errorf("error decoding JSON: %w", err) 15 | } 16 | return rows, nil 17 | } 18 | -------------------------------------------------------------------------------- /clients/bucket_schema/json_test.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/MakeNowJust/heredoc/v2" 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestDecodeJson(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | data string 17 | exp []api.MeasurementSchemaColumn 18 | expErr string 19 | }{ 20 | { 21 | name: "valid", 22 | data: heredoc.Doc(` 23 | [ 24 | { "name": "time", "type": "timestamp" }, 25 | { "name": "host", "type": "tag" }, 26 | { "name": "usage_user", "type": "field", "dataType": "float" } 27 | ] 28 | `), 29 | exp: cols(colTs(), colT("host"), colFF("usage_user")), 30 | }, 31 | { 32 | name: "invalid column type", 33 | data: heredoc.Doc(` 34 | [ 35 | { "name": "time", "type": "foo" } 36 | ] 37 | `), 38 | expErr: `error decoding JSON: foo is not a valid ColumnSemanticType`, 39 | }, 40 | { 41 | name: "invalid column data type", 42 | data: heredoc.Doc(` 43 | [ 44 | { "name": "time", "type": "field", "dataType": "floaty" } 45 | ] 46 | `), 47 | expErr: `error decoding JSON: floaty is not a valid ColumnDataType`, 48 | }, 49 | { 50 | name: "invalid JSON", 51 | data: heredoc.Doc(` 52 | [ 53 | { "name": "time", "type": "field", "dataType": "floaty" } 54 | `), 55 | expErr: `error decoding JSON: unexpected EOF`, 56 | }, 57 | } 58 | for _, tt := range tests { 59 | t.Run(tt.name, func(t *testing.T) { 60 | r := strings.NewReader(tt.data) 61 | got, err := decodeJson(r) 62 | if tt.expErr != "" { 63 | assert.EqualError(t, err, tt.expErr) 64 | } else { 65 | require.NoError(t, err) 66 | assert.Equal(t, tt.exp, got) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /clients/bucket_schema/list.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/influxdata/influx-cli/v2/clients" 8 | ) 9 | 10 | type ListParams struct { 11 | clients.OrgBucketParams 12 | Name string 13 | ExtendedOutput bool 14 | } 15 | 16 | func (c Client) List(ctx context.Context, params ListParams) error { 17 | ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | req := c.GetMeasurementSchemas(ctx, ids.BucketID).OrgID(ids.OrgID) 23 | 24 | if params.Name != "" { 25 | req = req.Name(params.Name) 26 | } 27 | 28 | res, err := req.Execute() 29 | if err != nil { 30 | return fmt.Errorf("failed to list measurement schemas: %w", err) 31 | } 32 | return c.printMeasurements(ids.BucketID, res.MeasurementSchemas, params.ExtendedOutput) 33 | } 34 | -------------------------------------------------------------------------------- /clients/bucket_schema/nsjson.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | 10 | "github.com/influxdata/influx-cli/v2/api" 11 | ) 12 | 13 | func decodeNDJson(r io.Reader) ([]api.MeasurementSchemaColumn, error) { 14 | scan := bufio.NewScanner(r) 15 | var rows []api.MeasurementSchemaColumn 16 | n := 1 17 | for scan.Scan() { 18 | if line := bytes.TrimSpace(scan.Bytes()); len(line) > 0 { 19 | var row api.MeasurementSchemaColumn 20 | if err := json.Unmarshal(line, &row); err != nil { 21 | return nil, fmt.Errorf("error decoding JSON at line %d: %w", n, err) 22 | } 23 | rows = append(rows, row) 24 | } 25 | n++ 26 | } 27 | return rows, nil 28 | } 29 | -------------------------------------------------------------------------------- /clients/bucket_schema/nsjson_test.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/MakeNowJust/heredoc/v2" 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func colTs() api.MeasurementSchemaColumn { 14 | return api.MeasurementSchemaColumn{Name: "time", Type: api.COLUMNSEMANTICTYPE_TIMESTAMP} 15 | } 16 | 17 | func colT(n string) api.MeasurementSchemaColumn { 18 | return api.MeasurementSchemaColumn{Name: n, Type: api.COLUMNSEMANTICTYPE_TAG} 19 | } 20 | 21 | func colFF(n string) api.MeasurementSchemaColumn { 22 | return api.MeasurementSchemaColumn{Name: n, Type: api.COLUMNSEMANTICTYPE_FIELD, DataType: api.COLUMNDATATYPE_FLOAT.Ptr()} 23 | } 24 | 25 | func cols(c ...api.MeasurementSchemaColumn) []api.MeasurementSchemaColumn { return c } 26 | 27 | func TestDecodeNDJson(t *testing.T) { 28 | tests := []struct { 29 | name string 30 | data string 31 | exp []api.MeasurementSchemaColumn 32 | expErr string 33 | }{ 34 | { 35 | name: "valid", 36 | data: heredoc.Doc(` 37 | { "name": "time", "type": "timestamp" } 38 | { "name": "host", "type": "tag" } 39 | { "name": "usage_user", "type": "field", "dataType": "float" } 40 | `), 41 | exp: cols(colTs(), colT("host"), colFF("usage_user")), 42 | }, 43 | { 44 | name: "invalid column type", 45 | data: heredoc.Doc(` 46 | { "name": "time", "type": "foo" } 47 | `), 48 | expErr: `error decoding JSON at line 1: foo is not a valid ColumnSemanticType`, 49 | }, 50 | { 51 | name: "invalid column data type", 52 | data: heredoc.Doc(` 53 | { "name": "time", "type": "field", "dataType": "floaty" } 54 | `), 55 | expErr: `error decoding JSON at line 1: floaty is not a valid ColumnDataType`, 56 | }, 57 | { 58 | name: "invalid JSON", 59 | data: heredoc.Doc(` 60 | { "name": "usage_user", "type": "field", "dataType": "float" } 61 | { "name": "time", "type": "field", "dataType": "float" 62 | `), 63 | expErr: `error decoding JSON at line 2: unexpected end of JSON input`, 64 | }, 65 | } 66 | for _, tt := range tests { 67 | t.Run(tt.name, func(t *testing.T) { 68 | r := strings.NewReader(tt.data) 69 | got, err := decodeNDJson(r) 70 | if tt.expErr != "" { 71 | assert.EqualError(t, err, tt.expErr) 72 | } else { 73 | require.NoError(t, err) 74 | assert.Equal(t, tt.exp, got) 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /clients/bucket_schema/testdata/columns.csv: -------------------------------------------------------------------------------- 1 | name,type,dataType 2 | time,timestamp, 3 | host,tag, 4 | usage_user,field,float -------------------------------------------------------------------------------- /clients/bucket_schema/testdata/columns.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "time", "type": "timestamp" }, 3 | { "name": "host", "type": "tag" }, 4 | { "name": "usage_user", "type": "field", "dataType": "float" } 5 | ] -------------------------------------------------------------------------------- /clients/bucket_schema/testdata/columns.ndjson: -------------------------------------------------------------------------------- 1 | { "name": "time", "type": "timestamp" } 2 | { "name": "host", "type": "tag" } 3 | { "name": "usage_user", "type": "field", "dataType": "float" } -------------------------------------------------------------------------------- /clients/bucket_schema/update.go: -------------------------------------------------------------------------------- 1 | package bucket_schema 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/influxdata/influx-cli/v2/api" 10 | "github.com/influxdata/influx-cli/v2/clients" 11 | ) 12 | 13 | type UpdateParams struct { 14 | clients.OrgBucketParams 15 | Name string 16 | ID string 17 | Stdin io.Reader 18 | ColumnsFile string 19 | ColumnsFormat ColumnsFormat 20 | ExtendedOutput bool 21 | } 22 | 23 | func (c Client) Update(ctx context.Context, params UpdateParams) error { 24 | cols, err := c.readColumns(params.Stdin, params.ColumnsFormat, params.ColumnsFile) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | var id string 35 | if params.ID == "" && params.Name == "" { 36 | return errors.New("measurement id or name required") 37 | } else if params.ID != "" { 38 | id = params.ID 39 | } else { 40 | id, err = c.resolveMeasurement(ctx, *ids, params.Name) 41 | if err != nil { 42 | return err 43 | } 44 | } 45 | 46 | res, err := c.UpdateMeasurementSchema(ctx, ids.BucketID, id). 47 | OrgID(ids.OrgID). 48 | MeasurementSchemaUpdateRequest(api.MeasurementSchemaUpdateRequest{ 49 | Columns: cols, 50 | }). 51 | Execute() 52 | 53 | if err != nil { 54 | return fmt.Errorf("failed to update measurement schema: %w", err) 55 | } 56 | 57 | return c.printMeasurements(ids.BucketID, []api.MeasurementSchema{res}, params.ExtendedOutput) 58 | } 59 | -------------------------------------------------------------------------------- /clients/cli.go: -------------------------------------------------------------------------------- 1 | package clients 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/influxdata/influx-cli/v2/config" 7 | "github.com/influxdata/influx-cli/v2/pkg/stdio" 8 | "github.com/influxdata/influx-cli/v2/pkg/tabwriter" 9 | ) 10 | 11 | // CLI is a container for common functionality used to execute commands. 12 | type CLI struct { 13 | StdIO stdio.StdIO 14 | 15 | HideTableHeaders bool 16 | PrintAsJSON bool 17 | 18 | ActiveConfig config.Config 19 | ConfigService config.Service 20 | } 21 | 22 | func (c *CLI) PrintJSON(v interface{}) error { 23 | enc := json.NewEncoder(c.StdIO) 24 | enc.SetIndent("", "\t") 25 | return enc.Encode(v) 26 | } 27 | 28 | func (c *CLI) PrintTable(headers []string, rows ...map[string]interface{}) error { 29 | w := tabwriter.NewTabWriter(c.StdIO, c.HideTableHeaders) 30 | defer w.Flush() 31 | if err := w.WriteHeaders(headers...); err != nil { 32 | return err 33 | } 34 | for _, r := range rows { 35 | if err := w.Write(r); err != nil { 36 | return err 37 | } 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /clients/dashboards/dashboards.go: -------------------------------------------------------------------------------- 1 | package dashboards 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | "github.com/influxdata/influx-cli/v2/clients" 9 | ) 10 | 11 | type Client struct { 12 | clients.CLI 13 | api.DashboardsApi 14 | } 15 | 16 | type Params struct { 17 | clients.OrgParams 18 | Ids []string 19 | } 20 | 21 | func (c Client) List(ctx context.Context, params *Params) error { 22 | if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" && len(params.Ids) == 0 { 23 | return fmt.Errorf("at least one of org, org-id, or id must be provided") 24 | } 25 | 26 | const limit = 100 27 | req := c.GetDashboards(ctx) 28 | req = req.Limit(limit) 29 | if params.OrgID != "" { 30 | req = req.OrgID(params.OrgID) 31 | } 32 | if params.OrgName != "" { 33 | req = req.Org(params.OrgName) 34 | } 35 | if params.OrgID == "" && params.OrgName == "" { 36 | req = req.Org(c.ActiveConfig.Org) 37 | } 38 | dashboards, err := req.Id(params.Ids).Execute() 39 | if err != nil { 40 | return fmt.Errorf("failed to find dashboards: %w", err) 41 | } 42 | 43 | return c.printDashboards(dashboards) 44 | } 45 | 46 | func (c Client) printDashboards(dashboards api.Dashboards) error { 47 | if c.PrintAsJSON { 48 | return c.PrintJSON(dashboards) 49 | } 50 | 51 | headers := []string{"ID", "OrgID", "Name", "Description", "Num Cells"} 52 | var rows []map[string]interface{} 53 | for _, u := range dashboards.GetDashboards() { 54 | row := map[string]interface{}{ 55 | "ID": u.GetId(), 56 | "OrgID": u.GetOrgID(), 57 | "Name": u.GetName(), 58 | "Description": u.GetDescription(), 59 | "Num Cells": len(u.GetCells()), 60 | } 61 | rows = append(rows, row) 62 | } 63 | 64 | return c.PrintTable(headers, rows...) 65 | } 66 | -------------------------------------------------------------------------------- /clients/delete/delete.go: -------------------------------------------------------------------------------- 1 | package delete 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/influxdata/influx-cli/v2/clients" 10 | ) 11 | 12 | type Client struct { 13 | clients.CLI 14 | api.DeleteApi 15 | } 16 | 17 | type Params struct { 18 | clients.OrgBucketParams 19 | Predicate string 20 | Start string 21 | Stop string 22 | } 23 | 24 | func (c Client) Delete(ctx context.Context, params *Params) error { 25 | if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" { 26 | return clients.ErrMustSpecifyOrg 27 | } 28 | if params.BucketID == "" && params.BucketName == "" { 29 | return clients.ErrMustSpecifyBucket 30 | } 31 | start, err := time.Parse(time.RFC3339Nano, params.Start) 32 | if err != nil { 33 | return fmt.Errorf("start time %q cannot be parsed as RFC3339Nano: %w", params.Start, err) 34 | } 35 | stop, err := time.Parse(time.RFC3339Nano, params.Stop) 36 | if err != nil { 37 | return fmt.Errorf("stop time %q cannot be parsed as RFC3339Nano: %w", params.Stop, err) 38 | } 39 | 40 | reqBody := api.NewDeletePredicateRequest(start, stop) 41 | if params.Predicate != "" { 42 | reqBody.SetPredicate(params.Predicate) 43 | } 44 | 45 | req := c.PostDelete(ctx).DeletePredicateRequest(*reqBody) 46 | if params.OrgID != "" { 47 | req = req.OrgID(params.OrgID) 48 | } else if params.OrgName != "" { 49 | req = req.Org(params.OrgName) 50 | } else { 51 | req = req.Org(c.ActiveConfig.Org) 52 | } 53 | if params.BucketID != "" { 54 | req = req.BucketID(params.BucketID) 55 | } else { 56 | req = req.Bucket(params.BucketName) 57 | } 58 | 59 | if err := req.Execute(); err != nil { 60 | return fmt.Errorf("failed to delete data: %w", err) 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /clients/errors.go: -------------------------------------------------------------------------------- 1 | package clients 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrPasswordIsTooShort = errors.New("password is too short") 7 | ErrMustSpecifyOrg = errors.New("must specify org ID or org name") 8 | ErrMustSpecifyBucket = errors.New("must specify bucket ID or bucket name") 9 | ) 10 | -------------------------------------------------------------------------------- /clients/helpers.go: -------------------------------------------------------------------------------- 1 | package clients 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // ReadQuery reads a Flux query into memory from a --file argument, args, or stdin 11 | func ReadQuery(filepath string, args []string) (string, error) { 12 | nargs := len(args) 13 | 14 | if nargs > 1 { 15 | return "", fmt.Errorf("at most 1 query string can be specified as an argument, got %d", nargs) 16 | } 17 | if nargs == 1 && filepath != "" { 18 | return "", fmt.Errorf("query can be specified as a CLI arg or passed in a file via flag, not both") 19 | } 20 | 21 | readFile := func(path string) (string, error) { 22 | queryBytes, err := os.ReadFile(path) 23 | if err != nil { 24 | return "", fmt.Errorf("failed to read query from %q: %w", path, err) 25 | } 26 | return string(queryBytes), nil 27 | } 28 | 29 | readStdin := func() (string, error) { 30 | queryBytes, err := io.ReadAll(os.Stdin) 31 | if err != nil { 32 | return "", fmt.Errorf("failed to read query from stdin: %w", err) 33 | } 34 | return string(queryBytes), err 35 | } 36 | 37 | if filepath != "" { 38 | return readFile(filepath) 39 | } 40 | if nargs == 0 { 41 | return readStdin() 42 | } 43 | 44 | arg := args[0] 45 | // Backwards compatibility. 46 | if strings.HasPrefix(arg, "@") { 47 | return readFile(arg[1:]) 48 | } else if arg == "-" { 49 | return readStdin() 50 | } else { 51 | return arg, nil 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /clients/org/client.go: -------------------------------------------------------------------------------- 1 | package org 2 | 3 | import ( 4 | "github.com/influxdata/influx-cli/v2/api" 5 | "github.com/influxdata/influx-cli/v2/clients" 6 | ) 7 | 8 | type Client struct { 9 | clients.CLI 10 | api.OrganizationsApi 11 | api.UsersApi 12 | } 13 | 14 | type printOrgOpts struct { 15 | org *api.Organization 16 | orgs []api.Organization 17 | deleted bool 18 | } 19 | 20 | func (c Client) printOrgs(opts printOrgOpts) error { 21 | if c.PrintAsJSON { 22 | var v interface{} 23 | if opts.org != nil { 24 | v = opts.org 25 | } else { 26 | v = opts.orgs 27 | } 28 | return c.PrintJSON(v) 29 | } 30 | 31 | headers := []string{"ID", "Name"} 32 | if opts.deleted { 33 | headers = append(headers, "Deleted") 34 | } 35 | 36 | if opts.org != nil { 37 | opts.orgs = append(opts.orgs, *opts.org) 38 | } 39 | 40 | var rows []map[string]interface{} 41 | for _, o := range opts.orgs { 42 | row := map[string]interface{}{ 43 | "ID": o.GetId(), 44 | "Name": o.GetName(), 45 | } 46 | if opts.deleted { 47 | row["Deleted"] = true 48 | } 49 | rows = append(rows, row) 50 | } 51 | 52 | return c.PrintTable(headers, rows...) 53 | } 54 | -------------------------------------------------------------------------------- /clients/org/org.go: -------------------------------------------------------------------------------- 1 | package org 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | "github.com/influxdata/influx-cli/v2/clients" 9 | ) 10 | 11 | type CreateParams struct { 12 | Name string 13 | Description string 14 | } 15 | 16 | func (c Client) Create(ctx context.Context, params *CreateParams) error { 17 | body := api.PostOrganizationRequest{Name: params.Name} 18 | if params.Description != "" { 19 | body.Description = ¶ms.Description 20 | } 21 | res, err := c.PostOrgs(ctx).PostOrganizationRequest(body).Execute() 22 | if err != nil { 23 | return fmt.Errorf("failed to create org %q: %w", params.Name, err) 24 | } 25 | return c.printOrgs(printOrgOpts{org: &res}) 26 | } 27 | 28 | func (c Client) Delete(ctx context.Context, id string) error { 29 | org, err := c.GetOrgsID(ctx, id).Execute() 30 | if err != nil { 31 | return fmt.Errorf("org %q not found: %w", id, err) 32 | 33 | } 34 | if err := c.DeleteOrgsID(ctx, id).Execute(); err != nil { 35 | return fmt.Errorf("failed to delete org %q: %w", id, err) 36 | } 37 | return c.printOrgs(printOrgOpts{org: &org, deleted: true}) 38 | } 39 | 40 | type ListParams struct { 41 | clients.OrgParams 42 | } 43 | 44 | func (c Client) List(ctx context.Context, params *ListParams) error { 45 | req := c.GetOrgs(ctx) 46 | if params.OrgName != "" { 47 | req = req.Org(params.OrgName) 48 | } 49 | if params.OrgID != "" { 50 | req = req.OrgID(params.OrgID) 51 | } 52 | orgs, err := req.Execute() 53 | if err != nil { 54 | return fmt.Errorf("failed to list orgs: %w", err) 55 | } 56 | printOpts := printOrgOpts{} 57 | if orgs.Orgs != nil { 58 | printOpts.orgs = *orgs.Orgs 59 | } 60 | return c.printOrgs(printOpts) 61 | } 62 | 63 | type UpdateParams struct { 64 | clients.OrgParams 65 | Description string 66 | } 67 | 68 | func (c Client) Update(ctx context.Context, params *UpdateParams) error { 69 | body := api.PatchOrganizationRequest{} 70 | if params.OrgName != "" { 71 | body.Name = ¶ms.OrgName 72 | } 73 | if params.Description != "" { 74 | body.Description = ¶ms.Description 75 | } 76 | 77 | res, err := c.PatchOrgsID(ctx, params.OrgID).PatchOrganizationRequest(body).Execute() 78 | if err != nil { 79 | return fmt.Errorf("failed to update org %q: %w", params.OrgID, err) 80 | } 81 | return c.printOrgs(printOrgOpts{org: &res}) 82 | } 83 | -------------------------------------------------------------------------------- /clients/params.go: -------------------------------------------------------------------------------- 1 | package clients 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | "github.com/influxdata/influx-cli/v2/config" 9 | ) 10 | 11 | type OrgParams struct { 12 | OrgID string 13 | OrgName string 14 | } 15 | 16 | func (p OrgParams) GetOrgID(ctx context.Context, activeConfig config.Config, orgApi api.OrganizationsApi) (string, error) { 17 | if p.OrgID != "" { 18 | return p.OrgID, nil 19 | } 20 | orgName := p.OrgName 21 | if orgName == "" { 22 | orgName = activeConfig.Org 23 | } 24 | if orgName == "" { 25 | return "", ErrMustSpecifyOrg 26 | } 27 | res, err := orgApi.GetOrgs(ctx).Org(orgName).Execute() 28 | if err != nil { 29 | return "", fmt.Errorf("failed to lookup org with name %q: %w", orgName, err) 30 | } 31 | if len(res.GetOrgs()) == 0 { 32 | return "", fmt.Errorf("no organization with name %q", orgName) 33 | } 34 | return res.GetOrgs()[0].GetId(), nil 35 | } 36 | 37 | type BucketParams struct { 38 | BucketID string 39 | BucketName string 40 | } 41 | 42 | type OrgBucketParams struct { 43 | OrgParams 44 | BucketParams 45 | } 46 | -------------------------------------------------------------------------------- /clients/ping/ping.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/influxdata/influx-cli/v2/api" 7 | "github.com/influxdata/influx-cli/v2/clients" 8 | ) 9 | 10 | type Client struct { 11 | clients.CLI 12 | api.HealthApi 13 | } 14 | 15 | // Ping checks the health of a remote InfluxDB instance. 16 | func (c Client) Ping(ctx context.Context) error { 17 | if _, err := c.GetHealth(ctx).Execute(); err != nil { 18 | return err 19 | } 20 | _, err := c.StdIO.Write([]byte("OK\n")) 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /clients/replication/replication_test.go: -------------------------------------------------------------------------------- 1 | package replication 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestDropNonRetryableDataBoolPtrFromFlags(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | dropNonRetryableData bool 15 | noDropNonRetryableData bool 16 | want *bool 17 | wantErr error 18 | }{ 19 | { 20 | name: "both true is an error", 21 | dropNonRetryableData: true, 22 | noDropNonRetryableData: true, 23 | want: nil, 24 | wantErr: errors.New("cannot specify both --drop-non-retryable-data and --no-drop-non-retryable-data at the same time"), 25 | }, 26 | { 27 | name: "drop is true", 28 | dropNonRetryableData: true, 29 | want: api.PtrBool(true), 30 | }, 31 | { 32 | name: "noDrop is true", 33 | noDropNonRetryableData: true, 34 | want: api.PtrBool(false), 35 | }, 36 | { 37 | name: "both nil is nil", 38 | want: nil, 39 | }, 40 | } 41 | 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | got, err := dropNonRetryableDataBoolPtrFromFlags(tt.dropNonRetryableData, tt.noDropNonRetryableData) 45 | require.Equal(t, tt.want, got) 46 | require.Equal(t, tt.wantErr, err) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /clients/server_config/server_config.go: -------------------------------------------------------------------------------- 1 | package server_config 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/BurntSushi/toml" 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/influxdata/influx-cli/v2/clients" 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | type Client struct { 14 | clients.CLI 15 | api.ConfigApi 16 | } 17 | 18 | type ListParams struct { 19 | TOML bool 20 | YAML bool 21 | } 22 | 23 | func (c Client) List(ctx context.Context, params *ListParams) error { 24 | response, err := c.GetConfig(ctx).Execute() 25 | if err != nil { 26 | return fmt.Errorf("failed to retrieve config: %w", err) 27 | } 28 | 29 | config := response.GetConfig() 30 | 31 | // Any numeric values that were decoded as floats need to be converted to 32 | // integers. InfluxDB currently does not have any float config values, and 33 | // this prevents formatting issues with the output. 34 | for k, v := range config { 35 | if f, ok := v.(float64); ok { 36 | config[k] = int(f) 37 | } 38 | } 39 | 40 | if params.TOML { 41 | return toml.NewEncoder(c.StdIO).Encode(config) 42 | } else if params.YAML { 43 | return yaml.NewEncoder(c.StdIO).Encode(config) 44 | } 45 | 46 | return c.PrintJSON(config) 47 | } 48 | -------------------------------------------------------------------------------- /clients/signin/signin.go: -------------------------------------------------------------------------------- 1 | package signin 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | "strings" 8 | "syscall" 9 | 10 | "github.com/influxdata/influx-cli/v2/api" 11 | "golang.org/x/term" 12 | ) 13 | 14 | func GetCookie(ctx context.Context, params api.ConfigParams, userPass string) (string, error) { 15 | bufUserPass, err := base64.StdEncoding.DecodeString(userPass) 16 | if err != nil { 17 | return "", err 18 | } 19 | 20 | splitUserPass := strings.Split(string(bufUserPass), ":") 21 | if len(splitUserPass) < 1 { 22 | return "", fmt.Errorf("bad config") 23 | } 24 | username := splitUserPass[0] 25 | var password string 26 | if len(splitUserPass) != 2 { 27 | fmt.Print("Please provide your password: ") 28 | bytePassword, err := term.ReadPassword(int(syscall.Stdin)) 29 | if err != nil { 30 | return "", err 31 | } 32 | password = string(bytePassword) 33 | fmt.Println() 34 | } else { 35 | password = splitUserPass[1] 36 | } 37 | 38 | cfg := api.NewAPIConfig(params) 39 | client := api.NewAPIClient(cfg) 40 | ctx = context.WithValue(ctx, api.ContextBasicAuth, api.BasicAuth{ 41 | UserName: username, 42 | Password: password, 43 | }) 44 | res, err := client.SigninApi.PostSignin(ctx).ExecuteWithHttpInfo() 45 | if err != nil { 46 | emsg := fmt.Errorf("error signing in, verify signin was not called against cloud influxdb: %w", err) 47 | return "", emsg 48 | } 49 | 50 | cookies := res.Cookies() 51 | 52 | for _, cookie := range cookies { 53 | if strings.Contains(cookie.Name, "influxdb") { 54 | return cookie.Value, nil 55 | } 56 | } 57 | 58 | return "", fmt.Errorf("failure getting session cookie, invalid cookies") 59 | } 60 | -------------------------------------------------------------------------------- /clients/template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | "github.com/influxdata/influx-cli/v2/clients" 9 | "github.com/influxdata/influx-cli/v2/pkg/template" 10 | ) 11 | 12 | type Client struct { 13 | clients.CLI 14 | api.TemplatesApi 15 | api.OrganizationsApi 16 | } 17 | 18 | type SummarizeParams struct { 19 | clients.OrgParams 20 | 21 | Sources []template.Source 22 | 23 | RenderTableColors bool 24 | RenderTableBorders bool 25 | } 26 | 27 | func (c Client) Summarize(ctx context.Context, params *SummarizeParams) error { 28 | orgID, err := params.GetOrgID(ctx, c.ActiveConfig, c.OrganizationsApi) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | templates, err := template.ReadSources(ctx, params.Sources) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | // Execute a dry-run to make the server summarize the template(s). 39 | req := api.TemplateApply{ 40 | DryRun: true, 41 | OrgID: orgID, 42 | Templates: templates, 43 | } 44 | res, err := c.ApplyTemplate(ctx).TemplateApply(req).Execute() 45 | if err != nil { 46 | return fmt.Errorf("failed to summarize template: %w", err) 47 | } 48 | 49 | if c.PrintAsJSON { 50 | return c.PrintJSON(res.Summary) 51 | } 52 | return template.PrintSummary(res.Summary, c.StdIO, params.RenderTableColors, params.RenderTableBorders) 53 | } 54 | 55 | type ValidateParams struct { 56 | clients.OrgParams 57 | 58 | Sources []template.Source 59 | } 60 | 61 | func (c Client) Validate(ctx context.Context, params *ValidateParams) error { 62 | orgID, err := params.GetOrgID(ctx, c.ActiveConfig, c.OrganizationsApi) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | templates, err := template.ReadSources(ctx, params.Sources) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | // Execute a dry-run to make the server summarize the template(s). 73 | req := api.TemplateApply{ 74 | DryRun: true, 75 | OrgID: orgID, 76 | Templates: templates, 77 | } 78 | _, err = c.ApplyTemplate(ctx).TemplateApply(req).Execute() 79 | if err == nil { 80 | return nil 81 | } 82 | 83 | if apiErr, ok := err.(api.GenericOpenAPIError); ok { 84 | return fmt.Errorf("template failed validation:\n\t%s", apiErr) 85 | } 86 | return fmt.Errorf("failed to validate template: %w", err) 87 | } 88 | -------------------------------------------------------------------------------- /clients/v1_shell/table_model_test.go: -------------------------------------------------------------------------------- 1 | package v1shell 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "os" 8 | "strings" 9 | "sync" 10 | "testing" 11 | "time" 12 | 13 | "github.com/influxdata/influx-cli/v2/api" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | type holder struct { 18 | buffer *bytes.Buffer 19 | mu sync.Mutex 20 | } 21 | 22 | func Test_checkEmptyTagValueRender(t *testing.T) { 23 | 24 | r, w, e := os.Pipe() 25 | if e != nil { 26 | assert.FailNow(t, e.Error()) 27 | } 28 | old := os.Stdout 29 | os.Stdout = w 30 | defer func() { 31 | os.Stdout = old 32 | }() 33 | 34 | print() 35 | 36 | h := &holder{buffer: &bytes.Buffer{}} 37 | seriesName := "tagless" 38 | seriesTags := map[string]string{"state": ""} 39 | seriesPartial := false 40 | seriesColumns := []string{"time", "state", "value"} 41 | seriesValues := [][]interface{}{{1731069761000000000, "on", 2}, 42 | {1731069771000000000, "off", 1}, {1731069781000000000, "on", 0}} 43 | 44 | model := NewModel( 45 | api.InfluxqlJsonResponseSeries{ 46 | Name: &seriesName, 47 | Tags: &seriesTags, 48 | Partial: &seriesPartial, 49 | Columns: &seriesColumns, 50 | Values: &seriesValues, 51 | }, 52 | true, 53 | "test", 54 | map[string]string{"foo": ""}, 55 | 0, 56 | 10, 57 | 0, 58 | 10, 59 | false, 60 | ) 61 | 62 | ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*1000) 63 | defer cancel() 64 | go func(ctx context.Context) { 65 | h.mu.Lock() 66 | _, err := io.CopyN(h.buffer, r, 27) 67 | h.mu.Unlock() 68 | if err != nil { 69 | assert.FailNow(t, err.Error()) 70 | } 71 | }(ctx) 72 | 73 | model.Init() 74 | 75 | assert.NotNil(t, <-ctx.Done()) 76 | os.Stdout = old 77 | h.mu.Lock() 78 | check := h.buffer.String() 79 | h.mu.Unlock() 80 | checkLines := strings.Split(check, "\n") 81 | assert.Equal(t, "Name: test", checkLines[0]) 82 | assert.Equal(t, "Tags: foo=", checkLines[1]) 83 | 84 | } 85 | -------------------------------------------------------------------------------- /clients/write/dryrun.go: -------------------------------------------------------------------------------- 1 | package write 2 | 3 | import ( 4 | "context" 5 | "io" 6 | 7 | "github.com/influxdata/influx-cli/v2/clients" 8 | ) 9 | 10 | type DryRunClient struct { 11 | clients.CLI 12 | LineReader 13 | } 14 | 15 | func (c DryRunClient) WriteDryRun(ctx context.Context) error { 16 | r, closer, err := c.LineReader.Open(ctx) 17 | if closer != nil { 18 | defer closer.Close() 19 | } 20 | if err != nil { 21 | return err 22 | } 23 | 24 | if _, err := io.Copy(c.StdIO, r); err != nil { 25 | return err 26 | } 27 | 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /clients/write/dryrun_test.go: -------------------------------------------------------------------------------- 1 | package write_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/golang/mock/gomock" 11 | "github.com/influxdata/influx-cli/v2/clients" 12 | "github.com/influxdata/influx-cli/v2/clients/write" 13 | "github.com/influxdata/influx-cli/v2/config" 14 | "github.com/influxdata/influx-cli/v2/internal/mock" 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | func TestWriteDryRun(t *testing.T) { 19 | t.Parallel() 20 | 21 | inLines := ` 22 | fake line protocol 1 23 | fake line protocol 2 24 | fake line protocol 3 25 | ` 26 | mockReader := bufferReader{} 27 | _, err := io.Copy(&mockReader.buf, strings.NewReader(inLines)) 28 | require.NoError(t, err) 29 | 30 | ctrl := gomock.NewController(t) 31 | stdio := mock.NewMockStdIO(ctrl) 32 | bytesWritten := bytes.Buffer{} 33 | stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes() 34 | 35 | cli := write.DryRunClient{ 36 | CLI: clients.CLI{ActiveConfig: config.Config{Org: "my-default-org"}, StdIO: stdio}, 37 | LineReader: &mockReader, 38 | } 39 | 40 | require.NoError(t, cli.WriteDryRun(context.Background())) 41 | require.Equal(t, inLines, bytesWritten.String()) 42 | } 43 | -------------------------------------------------------------------------------- /clients/write/throttler_test.go: -------------------------------------------------------------------------------- 1 | package write_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/influxdata/influx-cli/v2/clients/write" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestThrottlerPassthrough(t *testing.T) { 14 | // Hard to test that rate-limiting actually works, so we just check 15 | // that no data is lost. 16 | in := "Hello world!" 17 | bps, err := write.ToBytesPerSecond("1B/s") 18 | require.NoError(t, err) 19 | throttler := write.NewThrottler(bps) 20 | r := throttler.Throttle(context.Background(), strings.NewReader(in)) 21 | 22 | out := bytes.Buffer{} 23 | _, err = out.ReadFrom(r) 24 | require.NoError(t, err) 25 | 26 | require.Equal(t, in, out.String()) 27 | } 28 | 29 | func TestToBytesPerSecond(t *testing.T) { 30 | var tests = []struct { 31 | in string 32 | out float64 33 | error string 34 | }{ 35 | { 36 | in: "5 MB / 5 min", 37 | out: float64(5*1024*1024) / float64(5*60), 38 | }, 39 | { 40 | in: "17kBs", 41 | out: float64(17 * 1024), 42 | }, 43 | { 44 | in: "1B/m", 45 | out: float64(1) / float64(60), 46 | }, 47 | { 48 | in: "1B/2sec", 49 | out: float64(1) / float64(2), 50 | }, 51 | { 52 | in: "", 53 | out: 0, 54 | }, 55 | { 56 | in: "1B/munite", 57 | error: `invalid rate limit "1B/munite": it does not match format COUNT(B|kB|MB)/TIME(s|sec|m|min) with / and TIME being optional`, 58 | }, 59 | { 60 | in: ".B/s", 61 | error: `invalid rate limit ".B/s": '.' is not count of bytes:`, 62 | }, 63 | { 64 | in: "1B0s", 65 | error: `invalid rate limit "1B0s": positive time expected but 0 supplied`, 66 | }, 67 | { 68 | in: "1MB/42949672950s", 69 | error: `invalid rate limit "1MB/42949672950s": time is out of range`, 70 | }, 71 | } 72 | for _, test := range tests { 73 | t.Run(test.in, func(t *testing.T) { 74 | bytesPerSec, err := write.ToBytesPerSecond(test.in) 75 | if len(test.error) == 0 { 76 | require.Equal(t, test.out, float64(bytesPerSec)) 77 | require.Nil(t, err) 78 | } else { 79 | require.NotNil(t, err) 80 | // contains is used, since the error messages contains root cause that may evolve with go versions 81 | require.Contains(t, err.Error(), test.error) 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /clients/write/write.go: -------------------------------------------------------------------------------- 1 | package write 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "context" 7 | "errors" 8 | "fmt" 9 | "io" 10 | 11 | "github.com/influxdata/influx-cli/v2/api" 12 | "github.com/influxdata/influx-cli/v2/clients" 13 | ) 14 | 15 | type LineReader interface { 16 | Open(ctx context.Context) (io.Reader, io.Closer, error) 17 | } 18 | 19 | type RateLimiter interface { 20 | Throttle(ctx context.Context, in io.Reader) io.Reader 21 | } 22 | 23 | type BatchWriter interface { 24 | WriteBatches(ctx context.Context, r io.Reader, writeFn func(batch []byte) error) error 25 | } 26 | 27 | type Client struct { 28 | clients.CLI 29 | api.WriteApi 30 | LineReader 31 | RateLimiter 32 | BatchWriter 33 | } 34 | 35 | type Params struct { 36 | clients.OrgBucketParams 37 | Precision api.WritePrecision 38 | } 39 | 40 | var ErrWriteCanceled = errors.New("write canceled") 41 | 42 | func (c Client) Write(ctx context.Context, params *Params) error { 43 | if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" { 44 | return errors.New("must specify org ID or org name") 45 | } 46 | if params.BucketID == "" && params.BucketName == "" { 47 | return errors.New("must specify bucket ID or bucket name") 48 | } 49 | 50 | r, closer, err := c.LineReader.Open(ctx) 51 | if closer != nil { 52 | defer closer.Close() 53 | } 54 | if err != nil { 55 | return err 56 | } 57 | 58 | writeBatch := func(batch []byte) error { 59 | buf := bytes.Buffer{} 60 | gzw := gzip.NewWriter(&buf) 61 | _, err := gzw.Write(batch) 62 | gzw.Close() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | req := c.PostWrite(ctx).Body(buf.Bytes()).ContentEncoding("gzip").Precision(params.Precision) 68 | if params.BucketID != "" { 69 | req = req.Bucket(params.BucketID) 70 | } else { 71 | req = req.Bucket(params.BucketName) 72 | } 73 | if params.OrgID != "" { 74 | req = req.Org(params.OrgID) 75 | } else if params.OrgName != "" { 76 | req = req.Org(params.OrgName) 77 | } else { 78 | req = req.Org(c.ActiveConfig.Org) 79 | } 80 | 81 | if err := req.Execute(); err != nil { 82 | return err 83 | } 84 | 85 | return nil 86 | } 87 | 88 | if err := c.BatchWriter.WriteBatches(ctx, c.RateLimiter.Throttle(ctx, r), writeBatch); err == context.Canceled { 89 | return ErrWriteCanceled 90 | } else if err != nil { 91 | return fmt.Errorf("failed to write data: %w", err) 92 | } 93 | 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /cmd/influx/backup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/influxdata/influx-cli/v2/clients/backup" 7 | br "github.com/influxdata/influx-cli/v2/internal/backup_restore" 8 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | func newBackupCmd() cli.Command { 13 | var params backup.Params 14 | // Default to gzipping local files. 15 | params.Compression = br.GzipCompression 16 | 17 | return cli.Command{ 18 | Name: "backup", 19 | Usage: "Backup database", 20 | Description: `Backs up InfluxDB to a directory 21 | 22 | Examples: 23 | # backup all data 24 | influx backup /path/to/backup 25 | `, 26 | ArgsUsage: "path", 27 | Before: middleware.WithBeforeFns(withCli(), withApi(true)), 28 | Flags: append( 29 | append(commonFlagsNoPrint(), getOrgFlags(¶ms.OrgParams)...), 30 | &cli.StringFlag{ 31 | Name: "bucket-id", 32 | Usage: "The ID of the bucket to backup", 33 | Destination: ¶ms.BucketID, 34 | }, 35 | &cli.StringFlag{ 36 | Name: "bucket, b", 37 | Usage: "The name of the bucket to backup", 38 | Destination: ¶ms.BucketName, 39 | }, 40 | &cli.GenericFlag{ 41 | Name: "compression", 42 | Usage: "Compression to use for local backup files, either 'none' or 'gzip'", 43 | Value: ¶ms.Compression, 44 | }, 45 | ), 46 | Action: func(ctx *cli.Context) error { 47 | if err := checkOrgFlags(¶ms.OrgParams); err != nil { 48 | return err 49 | } 50 | if ctx.NArg() != 1 { 51 | return errors.New("backup path must be specified as a single positional argument") 52 | } 53 | params.Path = ctx.Args().Get(0) 54 | 55 | api := getAPI(ctx) 56 | client := backup.Client{ 57 | CLI: getCLI(ctx), 58 | BackupApi: api.BackupApi, 59 | HealthApi: api.HealthApi, 60 | } 61 | return client.Backup(getContext(ctx), ¶ms) 62 | }, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cmd/influx/dashboards.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/influxdata/influx-cli/v2/clients/dashboards" 5 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 6 | "github.com/urfave/cli" 7 | ) 8 | 9 | func newDashboardsCommand() cli.Command { 10 | var params dashboards.Params 11 | flags := append(commonFlags(), getOrgFlags(¶ms.OrgParams)...) 12 | flags = append(flags, &cli.StringSliceFlag{ 13 | Name: "id, i", 14 | Usage: "Dashboard ID to retrieve", 15 | }) 16 | return cli.Command{ 17 | Name: "dashboards", 18 | Usage: "List Dashboard(s).", 19 | Description: `List Dashboard(s). 20 | 21 | Examples: 22 | # list all known Dashboards 23 | influx dashboards 24 | 25 | # list all known Dashboards matching ids 26 | influx dashboards --id $ID1 --id $ID2 27 | 28 | # list all known Dashboards matching ids shorts 29 | influx dashboards -i $ID1 -i $ID2 30 | `, 31 | Flags: flags, 32 | Before: middleware.WithBeforeFns(withCli(), withApi(true), middleware.NoArgs), 33 | Action: func(ctx *cli.Context) error { 34 | if err := checkOrgFlags(¶ms.OrgParams); err != nil { 35 | return err 36 | } 37 | rawIds := ctx.StringSlice("id") 38 | params.Ids = rawIds 39 | 40 | api := getAPI(ctx) 41 | client := dashboards.Client{ 42 | CLI: getCLI(ctx), 43 | DashboardsApi: api.DashboardsApi, 44 | } 45 | return client.List(getContext(ctx), ¶ms) 46 | }, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cmd/influx/delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/influxdata/influx-cli/v2/clients/delete" 5 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 6 | "github.com/urfave/cli" 7 | ) 8 | 9 | func newDeleteCmd() cli.Command { 10 | var params delete.Params 11 | return cli.Command{ 12 | Name: "delete", 13 | Usage: "Delete points from InfluxDB", 14 | Description: "Delete points from InfluxDB, by specify start, end time and a sql like predicate string", 15 | Flags: append( 16 | commonFlagsNoPrint(), 17 | &cli.StringFlag{ 18 | Name: "org-id", 19 | Usage: "The ID of the organization that owns the bucket", 20 | EnvVar: "INFLUX_ORG_ID", 21 | Destination: ¶ms.OrgID, 22 | }, 23 | &cli.StringFlag{ 24 | Name: "org, o", 25 | Usage: "The name of the organization that owns the bucket", 26 | EnvVar: "INFLUX_ORG", 27 | Destination: ¶ms.OrgName, 28 | }, 29 | &cli.StringFlag{ 30 | Name: "bucket-id", 31 | Usage: "The ID of the bucket to delete from", 32 | EnvVar: "INFLUX_BUCKET_ID", 33 | Destination: ¶ms.BucketID, 34 | }, 35 | &cli.StringFlag{ 36 | Name: "bucket, b", 37 | Usage: "The name of the bucket to delete from", 38 | EnvVar: "INFLUX_BUCKET_NAME", 39 | Destination: ¶ms.BucketName, 40 | }, 41 | // NOTE: cli has a Timestamp flag we could use to parse the strings immediately on input, 42 | // but the help-text generation is broken for it. 43 | &cli.StringFlag{ 44 | Name: "start", 45 | Usage: "The start time in RFC3339Nano format (ex: '2009-01-02T23:00:00Z')", 46 | Required: true, 47 | Destination: ¶ms.Start, 48 | }, 49 | &cli.StringFlag{ 50 | Name: "stop", 51 | Usage: "The stop time in RFC3339Nano format (ex: '2009-01-02T23:00:00Z')", 52 | Required: true, 53 | Destination: ¶ms.Stop, 54 | }, 55 | &cli.StringFlag{ 56 | Name: "predicate, p", 57 | Usage: "sql like predicate string (ex: 'tag1=\"v1\" and (tag2=123)')", 58 | Destination: ¶ms.Predicate, 59 | }, 60 | ), 61 | Before: middleware.WithBeforeFns(withCli(), withApi(true), middleware.NoArgs), 62 | Action: func(ctx *cli.Context) error { 63 | client := delete.Client{CLI: getCLI(ctx), DeleteApi: getAPI(ctx).DeleteApi} 64 | return client.Delete(getContext(ctx), ¶ms) 65 | }, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cmd/influx/ping.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/influxdata/influx-cli/v2/clients/ping" 5 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 6 | "github.com/urfave/cli" 7 | ) 8 | 9 | func newPingCmd() cli.Command { 10 | return cli.Command{ 11 | Name: "ping", 12 | Usage: "Check the InfluxDB /health endpoint", 13 | Before: middleware.WithBeforeFns(withCli(), withApi(false), middleware.NoArgs), 14 | Flags: coreFlags(), 15 | Action: func(ctx *cli.Context) error { 16 | client := ping.Client{ 17 | CLI: getCLI(ctx), 18 | HealthApi: getAPINoToken(ctx).HealthApi, 19 | } 20 | return client.Ping(getContext(ctx)) 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cmd/influx/query.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/influxdata/influx-cli/v2/clients" 8 | "github.com/influxdata/influx-cli/v2/clients/query" 9 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 10 | "github.com/urfave/cli" 11 | ) 12 | 13 | func newQueryCmd() cli.Command { 14 | var orgParams clients.OrgParams 15 | return cli.Command{ 16 | Name: "query", 17 | Usage: "Execute a Flux query", 18 | Description: "Execute a Flux query provided via the first argument, a file, or stdin", 19 | ArgsUsage: "[query literal or '-' for stdin]", 20 | Before: middleware.WithBeforeFns(withCli(), withApi(true)), 21 | Flags: append( 22 | append(commonFlagsNoPrint(), getOrgFlags(&orgParams)...), 23 | &cli.StringFlag{ 24 | Name: "file, f", 25 | Usage: "Path to Flux query file", 26 | TakesFile: true, 27 | }, 28 | &cli.BoolFlag{ 29 | Name: "raw, r", 30 | Usage: "Display raw query results", 31 | }, 32 | &cli.StringSliceFlag{ 33 | Name: "profilers, p", 34 | Usage: "Names of Flux profilers to enable", 35 | }, 36 | ), 37 | Action: func(ctx *cli.Context) error { 38 | if err := checkOrgFlags(&orgParams); err != nil { 39 | return err 40 | } 41 | queryString, err := clients.ReadQuery(ctx.String("file"), ctx.Args()) 42 | if err != nil { 43 | return err 44 | } 45 | queryString = strings.TrimSpace(queryString) 46 | if queryString == "" { 47 | return errors.New("no query provided") 48 | } 49 | 50 | // The old CLI allowed specifying this either via repeated flags or 51 | // via a single flag w/ a comma-separated value. 52 | rawProfilers := ctx.StringSlice("profilers") 53 | var profilers []string 54 | for _, p := range rawProfilers { 55 | profilers = append(profilers, strings.Split(p, ",")...) 56 | } 57 | 58 | params := query.Params{ 59 | OrgParams: orgParams, 60 | Query: queryString, 61 | Profilers: profilers, 62 | } 63 | 64 | var printer query.ResultPrinter 65 | if ctx.Bool("raw") { 66 | printer = query.RawResultPrinter 67 | } else { 68 | printer = query.NewFormattingPrinter() 69 | } 70 | 71 | client := query.Client{ 72 | CLI: getCLI(ctx), 73 | QueryApi: getAPI(ctx).QueryApi, 74 | ResultPrinter: printer, 75 | } 76 | return client.Query(getContext(ctx), ¶ms) 77 | }, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /cmd/influx/server_config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/influxdata/influx-cli/v2/clients/server_config" 7 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 8 | "github.com/urfave/cli" 9 | ) 10 | 11 | func newServerConfigCommand() cli.Command { 12 | var params server_config.ListParams 13 | return cli.Command{ 14 | Name: "server-config", 15 | Usage: "Display server config", 16 | Flags: append( 17 | commonFlags(), 18 | &cli.BoolFlag{ 19 | Name: "toml", 20 | Usage: "Output configuration as TOML instead of JSON", 21 | Destination: ¶ms.TOML, 22 | }, 23 | &cli.BoolFlag{ 24 | Name: "yaml", 25 | Usage: "Output configuration as YAML instead of JSON", 26 | Destination: ¶ms.YAML, 27 | }, 28 | ), 29 | Before: middleware.WithBeforeFns(withCli(), withApi(true), middleware.NoArgs), 30 | Action: func(ctx *cli.Context) error { 31 | if params.TOML && params.YAML { 32 | return errors.New("cannot specify both TOML and YAML simultaneously") 33 | } 34 | 35 | api := getAPI(ctx) 36 | client := server_config.Client{ 37 | CLI: getCLI(ctx), 38 | ConfigApi: api.ConfigApi, 39 | } 40 | return client.List(getContext(ctx), ¶ms) 41 | }, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cmd/influx/setup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/influxdata/influx-cli/v2/clients/setup" 5 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 6 | "github.com/urfave/cli" 7 | ) 8 | 9 | func newSetupCmd() cli.Command { 10 | var params setup.Params 11 | return cli.Command{ 12 | Name: "setup", 13 | Usage: "Setup instance with initial user, org, bucket", 14 | Before: middleware.WithBeforeFns(withCli(), withApi(false), middleware.NoArgs), 15 | Flags: append( 16 | commonFlagsNoToken(), 17 | &cli.StringFlag{ 18 | Name: "username, u", 19 | Usage: "Name of initial user to create", 20 | Destination: ¶ms.Username, 21 | }, 22 | &cli.StringFlag{ 23 | Name: "password, p", 24 | Usage: "Password to set on initial user", 25 | Destination: ¶ms.Password, 26 | }, 27 | &cli.StringFlag{ 28 | Name: tokenFlagName + ", t", 29 | Usage: "Auth token to set on the initial user", 30 | EnvVar: "INFLUX_TOKEN", 31 | Destination: ¶ms.AuthToken, 32 | }, 33 | &cli.StringFlag{ 34 | Name: "org, o", 35 | Usage: "Name of initial organization to create", 36 | Destination: ¶ms.Org, 37 | }, 38 | &cli.StringFlag{ 39 | Name: "bucket, b", 40 | Usage: "Name of initial bucket to create", 41 | Destination: ¶ms.Bucket, 42 | }, 43 | &cli.StringFlag{ 44 | Name: "retention, r", 45 | Usage: "Duration initial bucket will retain data, or 0 for infinite", 46 | Destination: ¶ms.Retention, 47 | }, 48 | &cli.BoolFlag{ 49 | Name: "force, f", 50 | Usage: "Skip confirmation prompt", 51 | Destination: ¶ms.Force, 52 | }, 53 | &cli.StringFlag{ 54 | Name: "name, n", 55 | Usage: "Name to set on CLI config generated for the InfluxDB instance, required if other configs exist", 56 | Destination: ¶ms.ConfigName, 57 | }, 58 | ), 59 | Action: func(ctx *cli.Context) error { 60 | if ctx.IsSet(hostFlagName) { 61 | params.Host = ctx.String(hostFlagName) 62 | } 63 | client := setup.Client{ 64 | CLI: getCLI(ctx), 65 | SetupApi: getAPINoToken(ctx).SetupApi, 66 | } 67 | return client.Setup(getContext(ctx), ¶ms) 68 | }, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cmd/influx/v1_commands.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 5 | "github.com/urfave/cli" 6 | ) 7 | 8 | func newV1SubCommand() cli.Command { 9 | return cli.Command{ 10 | Name: "v1", 11 | Usage: "InfluxDB v1 management commands", 12 | Before: middleware.NoArgs, 13 | Subcommands: []cli.Command{ 14 | newV1DBRPCmd(), 15 | newV1AuthCommand(), 16 | newV1ShellCmd(), 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cmd/influx/v1_shell.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "github.com/influxdata/influx-cli/v2/api" 6 | "github.com/influxdata/influx-cli/v2/clients" 7 | shell "github.com/influxdata/influx-cli/v2/clients/v1_shell" 8 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | type Client struct { 13 | clients.CLI 14 | api.LegacyQueryApi 15 | } 16 | 17 | func newV1ShellCmd() cli.Command { 18 | var orgParams clients.OrgParams 19 | persistentQueryParams := shell.DefaultPersistentQueryParams() 20 | return cli.Command{ 21 | Name: "shell", 22 | Usage: "Start an InfluxQL shell", 23 | Description: "Start an InfluxQL shell", 24 | Before: middleware.WithBeforeFns(withCli(), withApi(true)), 25 | Flags: append(commonFlagsNoPrint(), getOrgFlags(&orgParams)...), 26 | Action: func(ctx *cli.Context) error { 27 | if err := checkOrgFlags(&orgParams); err != nil { 28 | return err 29 | } 30 | api := getAPI(ctx) 31 | c := shell.Client{ 32 | CLI: getCLI(ctx), 33 | PersistentQueryParams: persistentQueryParams, 34 | PingApi: api.PingApi, 35 | LegacyQueryApi: api.LegacyQueryApi, 36 | OrganizationsApi: api.OrganizationsApi, 37 | LegacyWriteApi: api.LegacyWriteApi, 38 | DBRPsApi: api.DBRPsApi, 39 | } 40 | color.Cyan("InfluxQL Shell %s", version) 41 | return c.Create(getContext(ctx)) 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cmd/influx/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/influxdata/influx-cli/v2/pkg/cli/middleware" 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | func newVersionCmd() cli.Command { 11 | return cli.Command{ 12 | Name: "version", 13 | Usage: "Print the influx CLI version", 14 | Before: middleware.NoArgs, 15 | Action: func(*cli.Context) error { 16 | fmt.Printf("Influx CLI %s (git: %s) build_date: %s\n", version, commit, date) 17 | return nil 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/user" 7 | "path/filepath" 8 | 9 | "github.com/influxdata/influx-cli/v2/api" 10 | ) 11 | 12 | // Config store the crendentials of influxdb host and token. 13 | type Config struct { 14 | Name string `toml:"-" json:"-"` 15 | Host string `toml:"url" json:"url"` 16 | // Token is base64 encoded sequence. 17 | Token string `toml:"token" json:"token"` 18 | Org string `toml:"org" json:"org"` 19 | Active bool `toml:"active,omitempty" json:"active,omitempty"` 20 | PreviousActive bool `toml:"previous,omitempty" json:"previous,omitempty"` 21 | Cookie string `toml:"cookie,omitempty" json:"cookie,omitempty"` 22 | } 23 | 24 | // DefaultConfig is default config without token 25 | var DefaultConfig = Config{ 26 | Name: "default", 27 | Host: "http://localhost:8086", 28 | Active: true, 29 | } 30 | 31 | // DefaultPath computes the path where CLI configs will be stored if not overridden. 32 | func DefaultPath() (string, error) { 33 | var dir string 34 | // By default, store meta and data files in current users home directory 35 | u, err := user.Current() 36 | if err == nil { 37 | dir = u.HomeDir 38 | } else if home := os.Getenv("HOME"); home != "" { 39 | dir = home 40 | } else { 41 | wd, err := os.Getwd() 42 | if err != nil { 43 | return "", err 44 | } 45 | dir = wd 46 | } 47 | dir = filepath.Join(dir, ".influxdbv2", "configs") 48 | 49 | return dir, nil 50 | } 51 | 52 | // Service is the service to list and write configs. 53 | type Service interface { 54 | CreateConfig(Config) (Config, error) 55 | DeleteConfig(name string) (Config, error) 56 | UpdateConfig(Config) (Config, error) 57 | SwitchActive(name string) (Config, error) 58 | Active() (Config, error) 59 | ListConfigs() (Configs, error) 60 | } 61 | 62 | // Configs is map of configs indexed by name. 63 | type Configs map[string]Config 64 | 65 | // Switch to another config. 66 | func (cfgs Configs) switchActive(name string) error { 67 | if _, ok := cfgs[name]; !ok { 68 | return &api.Error{ 69 | Code: api.ERRORCODE_NOT_FOUND, 70 | Message: api.PtrString(fmt.Sprintf("config %q is not found", name)), 71 | } 72 | } 73 | for k, v := range cfgs { 74 | v.PreviousActive = v.Active && (k != name) 75 | v.Active = k == name 76 | cfgs[k] = v 77 | } 78 | return nil 79 | } 80 | 81 | func (cfgs Configs) active() Config { 82 | for _, cfg := range cfgs { 83 | if cfg.Active { 84 | return cfg 85 | } 86 | } 87 | if len(cfgs) > 0 { 88 | for _, cfg := range cfgs { 89 | return cfg 90 | } 91 | } 92 | return DefaultConfig 93 | } 94 | -------------------------------------------------------------------------------- /etc/checkfmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HAS_FMT_ERR=0 4 | # For every Go file in the project, excluding vendor... 5 | for file in $(go list -f '{{$dir := .Dir}}{{range .GoFiles}}{{printf "%s/%s\n" $dir .}}{{end}}' ./...); do 6 | # ... if file does not contain standard generated code comment (https://golang.org/s/generatedcode)... 7 | if ! grep -Exq '^// Code generated .* DO NOT EDIT\.$' $file; then 8 | FMT_OUT="$(gofmt -l -d -e $file)" # gofmt exits 0 regardless of whether it's formatted. 9 | GCI_OUT="$(go run github.com/daixiang0/gci -d $file)" 10 | 11 | # Work around annoying output of gci 12 | if [[ "$GCI_OUT" = "skip file $file since no import" ]]; then 13 | GCI_OUT="" 14 | fi 15 | 16 | if [[ -n "$FMT_OUT" || -n "$GCI_OUT" ]]; then 17 | HAS_FMT_ERR=1 18 | echo "Not formatted: $file" 19 | fi 20 | fi 21 | done 22 | 23 | if [ "$HAS_FMT_ERR" -eq "1" ]; then 24 | echo 'Commit includes files that are not formatted' && \ 25 | echo 'run "make fmt"' && \ 26 | echo '' 27 | fi 28 | exit "$HAS_FMT_ERR" 29 | -------------------------------------------------------------------------------- /etc/checkgenerate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | function check_changes () { 6 | changes="$(git status --porcelain=v1 2>/dev/null)" 7 | if [ -n "$changes" ] ; then 8 | echo $1 9 | echo "$changes" 10 | exit 1 11 | fi 12 | } 13 | 14 | check_changes "git is dirty before running 'make generate-sources!'" 15 | make mock 16 | check_changes "git is dirty after running 'make generate-sources'!" 17 | -------------------------------------------------------------------------------- /etc/checkopenapi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | declare -r ETC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" 5 | ${ETC_DIR}/generate-openapi.sh 6 | 7 | if ! git --no-pager diff --exit-code -- api; then 8 | >&2 echo "openapi generated client doesn't match spec, please run 'make openapi'" 9 | exit 1 10 | fi 11 | -------------------------------------------------------------------------------- /etc/checktidy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export GO111MODULE=on 6 | go mod tidy 7 | 8 | if ! git --no-pager diff --exit-code -- go.mod go.sum; then 9 | >&2 echo "modules are not tidy, please run 'go mod tidy'" 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /etc/stripGroupTags.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | 4 | blacklist = [ 5 | "Data I/O endpoints", 6 | "Security and access endpoints", 7 | "System information endpoints" 8 | ] 9 | 10 | if __name__ == "__main__": 11 | with open(sys.argv[1]) as f: 12 | data = f.read() 13 | blItems = "|".join(blacklist) 14 | data = re.sub(f"- +({blItems})", "", data) 15 | print(data) -------------------------------------------------------------------------------- /internal/backup_restore/README.md: -------------------------------------------------------------------------------- 1 | # V1 Meta Protobufs 2 | 3 | For compatibility with backups made via the v2.0.x `influx` CLI, we include logic 4 | for opening & reading backed-up KV stores to derive bucket manifests. Part of that 5 | process requires reading & unmarshalling V1 database info, serialized as protobuf. 6 | To support that requirement, we've copied the `meta.proto` definition out of `influxdb` 7 | and into this repository. This file isn't intended to be modified. 8 | 9 | If `meta.pb.go` ever needs to be re-generated, follow these steps: 10 | 1. Install `protoc` (i.e. via `brew install protobuf`) 11 | 2. Run `go install google.golang.org/protobuf/cmd/protoc-gen-go` from within this repository 12 | 3. Run `go generate ` 13 | -------------------------------------------------------------------------------- /internal/backup_restore/api_check.go: -------------------------------------------------------------------------------- 1 | package backup_restore 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "regexp" 8 | "strconv" 9 | 10 | "github.com/influxdata/influx-cli/v2/api" 11 | ) 12 | 13 | var semverRegex = regexp.MustCompile(`(\d+)\.(\d+)\.(\d+).*`) 14 | 15 | // ServerIsLegacy checks if the InfluxDB server targeted by the backup is running v2.0.x, 16 | // which used different APIs for backups. 17 | func ServerIsLegacy(ctx context.Context, client api.HealthApi) (bool, error) { 18 | res, err := client.GetHealth(ctx).Execute() 19 | if err != nil { 20 | return false, fmt.Errorf("API compatibility check failed: %w", err) 21 | } 22 | var version string 23 | if res.Version != nil { 24 | version = *res.Version 25 | } 26 | 27 | matches := semverRegex.FindSubmatch([]byte(version)) 28 | if matches == nil { 29 | // Assume non-semver versions are only reported by nightlies & dev builds, which 30 | // should now support the new APIs. 31 | log.Printf("WARN: Couldn't parse version %q reported by server, assuming latest backup/restore APIs are supported", version) 32 | return false, nil 33 | } 34 | // matches[0] is the entire matched string, capture groups start at 1. 35 | majorStr, minorStr := matches[1], matches[2] 36 | // Ignore the err values here because the regex-match ensures we can parse the captured 37 | // groups as integers. 38 | major, _ := strconv.Atoi(string(majorStr)) 39 | minor, _ := strconv.Atoi(string(minorStr)) 40 | 41 | if major < 2 { 42 | return false, fmt.Errorf("InfluxDB v%d does not support the APIs required for backup/restore", major) 43 | } 44 | return minor == 0, nil 45 | } 46 | -------------------------------------------------------------------------------- /internal/backup_restore/api_check_test.go: -------------------------------------------------------------------------------- 1 | package backup_restore_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/influxdata/influx-cli/v2/api" 9 | "github.com/influxdata/influx-cli/v2/internal/backup_restore" 10 | "github.com/influxdata/influx-cli/v2/internal/mock" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestServerIsLegacy(t *testing.T) { 15 | t.Parallel() 16 | 17 | testCases := []struct { 18 | name string 19 | versionStr *string 20 | legacy bool 21 | wantErr string 22 | }{ 23 | { 24 | name: "2.0.x", 25 | versionStr: api.PtrString("2.0.7"), 26 | legacy: true, 27 | }, 28 | { 29 | name: "2.1.x", 30 | versionStr: api.PtrString("2.1.0-RC1"), 31 | }, 32 | { 33 | name: "nightly", 34 | versionStr: api.PtrString("nightly-2020-01-01"), 35 | }, 36 | { 37 | name: "dev", 38 | versionStr: api.PtrString("some.custom-version.2"), 39 | }, 40 | { 41 | name: "1.x", 42 | versionStr: api.PtrString("1.9.3"), 43 | wantErr: "InfluxDB v1 does not support the APIs", 44 | }, 45 | } 46 | 47 | for _, tc := range testCases { 48 | tc := tc 49 | t.Run(tc.name, func(t *testing.T) { 50 | t.Parallel() 51 | 52 | ctrl := gomock.NewController(t) 53 | healthApi := mock.NewMockHealthApi(ctrl) 54 | healthApi.EXPECT().GetHealth(gomock.Any()).Return(api.ApiGetHealthRequest{ApiService: healthApi}) 55 | healthApi.EXPECT().GetHealthExecute(gomock.Any()).Return(api.HealthCheck{Version: tc.versionStr}, nil) 56 | 57 | isLegacy, err := backup_restore.ServerIsLegacy(context.Background(), healthApi) 58 | 59 | if tc.wantErr != "" { 60 | require.Error(t, err) 61 | require.Contains(t, err.Error(), tc.wantErr) 62 | return 63 | } 64 | 65 | require.NoError(t, err) 66 | require.Equal(t, tc.legacy, isLegacy) 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /internal/backup_restore/testdata/test.bolt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/influxdata/influx-cli/cf80a7321f9be27b27fb667f34951d7e97080050/internal/backup_restore/testdata/test.bolt.gz -------------------------------------------------------------------------------- /internal/mock/README.md: -------------------------------------------------------------------------------- 1 | # Mocks 2 | 3 | The files in this module are generated via `mockgen` for use with [`gomock`](https://github.com/golang/mock). 4 | 5 | To add a new mock: 6 | 1. Add a line to `gen.go` in this module 7 | 1. Run `make mock` from the project root 8 | -------------------------------------------------------------------------------- /internal/mock/gen.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | // HTTP API mocks 4 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_bucket_schemas.gen.go github.com/influxdata/influx-cli/v2/api BucketSchemasApi 5 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_buckets.gen.go github.com/influxdata/influx-cli/v2/api BucketsApi 6 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_health.gen.go github.com/influxdata/influx-cli/v2/api HealthApi 7 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_organizations.gen.go github.com/influxdata/influx-cli/v2/api OrganizationsApi 8 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_setup.gen.go github.com/influxdata/influx-cli/v2/api SetupApi 9 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_write.gen.go github.com/influxdata/influx-cli/v2/api WriteApi 10 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_query.gen.go github.com/influxdata/influx-cli/v2/api QueryApi 11 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_users.gen.go github.com/influxdata/influx-cli/v2/api UsersApi 12 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_delete.gen.go github.com/influxdata/influx-cli/v2/api DeleteApi 13 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_backup.gen.go github.com/influxdata/influx-cli/v2/api BackupApi 14 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_secret.gen.go github.com/influxdata/influx-cli/v2/api SecretsApi 15 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_v1dbrps.gen.go github.com/influxdata/influx-cli/v2/api DBRPsApi 16 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination api_invokable_scripts.gen.go github.com/influxdata/influx-cli/v2/api InvokableScriptsApi 17 | 18 | // Other mocks 19 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination config.gen.go -mock_names Service=MockConfigService github.com/influxdata/influx-cli/v2/config Service 20 | //go:generate go run github.com/golang/mock/mockgen -package mock -destination stdio.gen.go github.com/influxdata/influx-cli/v2/pkg/stdio StdIO 21 | -------------------------------------------------------------------------------- /internal/testutils/utils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func MatchLines(t *testing.T, expectedLines []string, lines []string) { 10 | var nonEmptyLines []string 11 | for _, l := range lines { 12 | if l != "" { 13 | nonEmptyLines = append(nonEmptyLines, l) 14 | } 15 | } 16 | require.Equal(t, len(expectedLines), len(nonEmptyLines)) 17 | for i, expected := range expectedLines { 18 | require.Regexp(t, expected, nonEmptyLines[i]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/cli/context/context.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import "github.com/urfave/cli" 4 | 5 | const ( 6 | contextKeyCloudOnly = "cloudOnly" 7 | contextKeyOssOnly = "ossOnly" 8 | ) 9 | 10 | func SetCloudOnly(ctx *cli.Context) { 11 | ctx.App.Metadata[contextKeyCloudOnly] = true 12 | } 13 | 14 | func SetOssOnly(ctx *cli.Context) { 15 | ctx.App.Metadata[contextKeyOssOnly] = true 16 | } 17 | 18 | func GetCloudOnly(ctx *cli.Context) bool { 19 | _, ok := ctx.App.Metadata[contextKeyCloudOnly].(bool) 20 | return ok 21 | } 22 | 23 | func GetOssOnly(ctx *cli.Context) bool { 24 | _, ok := ctx.App.Metadata[contextKeyOssOnly].(bool) 25 | return ok 26 | } 27 | -------------------------------------------------------------------------------- /pkg/cli/middleware/handleexit.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/influxdata/influx-cli/v2/api" 8 | icontext "github.com/influxdata/influx-cli/v2/pkg/cli/context" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | const ( 13 | OSSBuildHeader = "OSS" 14 | CloudBuildHeader = "Cloud" 15 | ) 16 | 17 | func WrongHostErrString(commandHost, actualHost string) string { 18 | return fmt.Sprintf("Error: InfluxDB %s-only command used with InfluxDB %s host", commandHost, actualHost) 19 | } 20 | 21 | var HandleExit cli.ExitErrHandlerFunc = func(ctx *cli.Context, err error) { 22 | if err == nil { 23 | return 24 | } 25 | 26 | var header string 27 | var genericErr api.GenericOpenAPIError 28 | if errors.As(err, &genericErr) { 29 | header = genericErr.BuildHeader() 30 | } 31 | 32 | // Replace the error message with the relevant information if a platform-specific command was used on the wrong host. 33 | // Otherwise, pass the error message along as-is to the CLI exit handler. 34 | var setErr bool 35 | if header == OSSBuildHeader { 36 | if icontext.GetCloudOnly(ctx) { 37 | err = cli.NewExitError(WrongHostErrString(CloudBuildHeader, OSSBuildHeader), 1) 38 | setErr = true 39 | } 40 | } else if header == CloudBuildHeader { 41 | if icontext.GetOssOnly(ctx) { 42 | err = cli.NewExitError(WrongHostErrString(OSSBuildHeader, CloudBuildHeader), 1) 43 | setErr = true 44 | } 45 | } 46 | if !setErr { 47 | err = cli.NewExitError(fmt.Sprintf("Error: %v", err.Error()), 1) 48 | } 49 | 50 | cli.HandleExitCoder(err) 51 | } 52 | -------------------------------------------------------------------------------- /pkg/cli/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | icontext "github.com/influxdata/influx-cli/v2/pkg/cli/context" 5 | "github.com/urfave/cli" 6 | ) 7 | 8 | // WithBeforeFns returns a cli.BeforeFunc that calls each of the provided 9 | // functions in order. 10 | // NOTE: The first function to return an error will end execution and 11 | // be returned as the error value of the composed function. 12 | func WithBeforeFns(fns ...cli.BeforeFunc) cli.BeforeFunc { 13 | return func(ctx *cli.Context) error { 14 | for _, fn := range fns { 15 | if err := fn(ctx); err != nil { 16 | return err 17 | } 18 | } 19 | return nil 20 | } 21 | } 22 | 23 | // AddMWToCmds is used to append a middleware to a list of existing commands. 24 | func AddMWToCmds(cmds []cli.Command, mw cli.BeforeFunc) []cli.Command { 25 | newCmds := make([]cli.Command, 0, len(cmds)) 26 | 27 | for _, cmd := range cmds { 28 | cmd.Before = WithBeforeFns(cmd.Before, mw) 29 | newCmds = append(newCmds, cmd) 30 | } 31 | 32 | return newCmds 33 | } 34 | 35 | var CloudOnly cli.BeforeFunc = func(ctx *cli.Context) error { 36 | icontext.SetCloudOnly(ctx) 37 | return nil 38 | } 39 | 40 | var OSSOnly cli.BeforeFunc = func(ctx *cli.Context) error { 41 | icontext.SetOssOnly(ctx) 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/cli/middleware/noargs.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/urfave/cli" 7 | ) 8 | 9 | var NoArgs cli.BeforeFunc = func(ctx *cli.Context) error { 10 | // `Before` funcs get run prior to resolving subcommands from args 11 | if ctx.NArg() > 0 && ctx.App.Command(ctx.Args()[0]) == nil { 12 | cmdName := ctx.Command.Name 13 | if cmdName == "" && ctx.App.Name != "" { 14 | cmdName = ctx.App.Name 15 | } 16 | // Use the same error format as `cobra.NoArgs` for consistency with the old CLI. 17 | return fmt.Errorf("unknown command %q for %q", ctx.Args()[0], cmdName) 18 | } 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /pkg/csv2lp/multi_closer.go: -------------------------------------------------------------------------------- 1 | package csv2lp 2 | 3 | import ( 4 | "io" 5 | "log" 6 | ) 7 | 8 | // multicloser 9 | type multiCloser struct { 10 | closers []io.Closer 11 | } 12 | 13 | // Close implements io.Closer to closes all nested closers and logs a warning on error 14 | func (mc *multiCloser) Close() error { 15 | var err error 16 | for i := 0; i < len(mc.closers); i++ { 17 | e := mc.closers[i].Close() 18 | if e != nil { 19 | if err == nil { 20 | err = e 21 | } 22 | log.Println(err) 23 | } 24 | } 25 | return err 26 | } 27 | 28 | // MultiCloser creates an io.Closer that silently closes supplied io.Closer instances 29 | func MultiCloser(closers ...io.Closer) io.Closer { 30 | c := make([]io.Closer, len(closers)) 31 | copy(c, closers) 32 | return &multiCloser{c} 33 | } 34 | -------------------------------------------------------------------------------- /pkg/csv2lp/multi_closer_test.go: -------------------------------------------------------------------------------- 1 | package csv2lp 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | type testCloser struct { 17 | errorMsg string 18 | } 19 | 20 | func (c testCloser) Close() error { 21 | if c.errorMsg == "" { 22 | return nil 23 | } 24 | return errors.New(c.errorMsg) 25 | } 26 | 27 | // Test_escapeMeasurement 28 | func TestMultiCloser(t *testing.T) { 29 | var buf bytes.Buffer 30 | log.SetOutput(&buf) 31 | oldFlags := log.Flags() 32 | log.SetFlags(0) 33 | oldPrefix := log.Prefix() 34 | prefix := "::PREFIX::" 35 | log.SetPrefix(prefix) 36 | defer func() { 37 | log.SetOutput(os.Stderr) 38 | log.SetFlags(oldFlags) 39 | log.SetPrefix(oldPrefix) 40 | }() 41 | 42 | var tests = []struct { 43 | subject io.Closer 44 | errors int 45 | }{ 46 | {MultiCloser(), 0}, 47 | {MultiCloser(testCloser{}, testCloser{}), 0}, 48 | {MultiCloser(testCloser{"a"}, testCloser{}, testCloser{"c"}), 2}, 49 | } 50 | 51 | for i, test := range tests { 52 | t.Run(fmt.Sprint(i), func(t *testing.T) { 53 | buf.Reset() 54 | err := test.subject.Close() 55 | messages := strings.Count(buf.String(), prefix) 56 | require.Equal(t, test.errors, messages) 57 | if test.errors > 0 { 58 | require.NotNil(t, err) 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/csv2lp/skip_header_lines.go: -------------------------------------------------------------------------------- 1 | package csv2lp 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // skipFirstLines is an io.Reader that skips first lines 8 | type skipFirstLines struct { 9 | // reader provides data 10 | reader io.Reader 11 | // skipLines contains the lines to skip 12 | skipLines int 13 | // line is a mutable variable that increases until skipLines is reached 14 | line int 15 | // quotedString indicates whether a quoted CSV string is being read, 16 | // a new line inside a quoted string does not start a new CSV line 17 | quotedString bool 18 | } 19 | 20 | // Read implements io.Reader 21 | func (state *skipFirstLines) Read(p []byte) (n int, err error) { 22 | skipHeaderLines: 23 | for state.line < state.skipLines { 24 | n, err := state.reader.Read(p) 25 | if n == 0 { 26 | return n, err 27 | } 28 | for i := 0; i < n; i++ { 29 | switch p[i] { 30 | case '"': 31 | // a quoted string starts or stops 32 | state.quotedString = !state.quotedString 33 | case '\n': 34 | if !state.quotedString { 35 | state.line++ 36 | if state.line == state.skipLines { 37 | // modify the buffer and return 38 | if i == n-1 { 39 | if err != nil { 40 | return 0, err 41 | } 42 | // continue with the next chunk 43 | break skipHeaderLines 44 | } else { 45 | // copy all bytes after the newline 46 | for j := i + 1; j < n; j++ { 47 | p[j-i-1] = p[j] 48 | } 49 | return n - i - 1, err 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | return state.reader.Read(p) 57 | } 58 | 59 | // SkipHeaderLinesReader wraps a reader to skip the first skipLines lines in CSV data input 60 | func SkipHeaderLinesReader(skipLines int, reader io.Reader) io.Reader { 61 | return &skipFirstLines{ 62 | skipLines: skipLines, 63 | reader: reader, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/csv2lp/skip_header_lines_test.go: -------------------------------------------------------------------------------- 1 | package csv2lp 2 | 3 | import ( 4 | "io" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | // simulates the reader that returns all data together with EOF 13 | type readOnceWithEOF struct { 14 | reader io.Reader 15 | } 16 | 17 | func (r *readOnceWithEOF) Read(p []byte) (n int, err error) { 18 | n, _ = r.reader.Read(p) 19 | return n, io.EOF 20 | } 21 | 22 | // Test_SkipHeaderLines checks that first lines are skipped 23 | func Test_SkipHeaderLines(t *testing.T) { 24 | 25 | var tests = []struct { 26 | skipCount int 27 | input string 28 | result string 29 | }{ 30 | { 31 | 10, 32 | "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", 33 | "", 34 | }, 35 | { 36 | 0, 37 | "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", 38 | "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", 39 | }, 40 | { 41 | 1, 42 | "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", 43 | "2\n3\n4\n5\n6\n7\n8\n9\n0\n", 44 | }, 45 | { 46 | 5, 47 | "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", 48 | "6\n7\n8\n9\n0\n", 49 | }, 50 | { 51 | 20, 52 | "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", 53 | "", 54 | }, 55 | { 56 | 1, 57 | "\"\n\"\"\n\"\n2", 58 | "2", 59 | }, 60 | } 61 | 62 | for i, test := range tests { 63 | input := test.input 64 | bufferSizes := []int{1, 2, 7, 0, len(input), len(input) + 1} 65 | for _, bufferSize := range bufferSizes { 66 | t.Run(strconv.Itoa(i)+"_"+strconv.Itoa(bufferSize), func(t *testing.T) { 67 | var reader io.Reader 68 | if bufferSize == 0 { 69 | // emulate a reader that returns EOF together with data 70 | bufferSize = len(input) 71 | reader = SkipHeaderLinesReader(test.skipCount, &readOnceWithEOF{strings.NewReader(input)}) 72 | } else { 73 | reader = SkipHeaderLinesReader(test.skipCount, strings.NewReader(input)) 74 | } 75 | buffer := make([]byte, bufferSize) 76 | result := make([]byte, 0, 100) 77 | for { 78 | n, err := reader.Read(buffer) 79 | if n > 0 { 80 | result = append(result, buffer[:n]...) 81 | } 82 | if err != nil { 83 | if err != io.EOF { 84 | require.Nil(t, err.Error()) 85 | } 86 | break 87 | } 88 | } 89 | require.Equal(t, test.result, string(result)) 90 | }) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pkg/duration/parser_test.go: -------------------------------------------------------------------------------- 1 | package duration_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/influxdata/influx-cli/v2/pkg/duration" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_RawDurationToTimeDuration(t *testing.T) { 12 | testCases := []struct { 13 | name string 14 | input string 15 | expected time.Duration 16 | expectErr bool 17 | }{ 18 | { 19 | name: "nanos", 20 | input: "10ns", 21 | expected: 10, 22 | }, 23 | { 24 | name: "micros", 25 | input: "12345us", 26 | expected: 12345 * time.Microsecond, 27 | }, 28 | { 29 | name: "millis", 30 | input: "9876ms", 31 | expected: 9876 * time.Millisecond, 32 | }, 33 | { 34 | name: "seconds", 35 | input: "300s", 36 | expected: 300 * time.Second, 37 | }, 38 | { 39 | name: "minutes", 40 | input: "654m", 41 | expected: 654 * time.Minute, 42 | }, 43 | { 44 | name: "hours", 45 | input: "127h", 46 | expected: 127 * time.Hour, 47 | }, 48 | { 49 | name: "days", 50 | input: "29d", 51 | expected: 29 * duration.Day, 52 | }, 53 | { 54 | name: "weeks", 55 | input: "396w", 56 | expected: 396 * duration.Week, 57 | }, 58 | { 59 | name: "weeks+hours+seconds+micros", 60 | input: "1w2h3s4us", 61 | expected: duration.Week + 2*time.Hour + 3*time.Second + 4*time.Microsecond, 62 | }, 63 | { 64 | name: "days+minutes+millis+nanos", 65 | input: "9d8m7ms6ns", 66 | expected: 9*duration.Day + 8*time.Minute + 7*time.Millisecond + 6, 67 | }, 68 | { 69 | name: "negative", 70 | input: "-1d", 71 | expectErr: true, 72 | }, 73 | { 74 | name: "missing unit", 75 | input: "123", 76 | expectErr: true, 77 | }, 78 | } 79 | 80 | for _, tc := range testCases { 81 | t.Run(tc.name, func(t *testing.T) { 82 | parsed, err := duration.RawDurationToTimeDuration(tc.input) 83 | if tc.expectErr { 84 | require.Error(t, err) 85 | return 86 | } 87 | require.NoError(t, err) 88 | require.Equal(t, tc.expected, parsed) 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pkg/github/normalize.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "net/url" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | githubRawContentHost = "raw.githubusercontent.com" 11 | githubHost = "github.com" 12 | ) 13 | 14 | func NormalizeURLToContent(u *url.URL, extensions ...string) *url.URL { 15 | if u.Host != githubHost { 16 | return u 17 | } 18 | if len(extensions) > 0 && !extensionMatches(u, extensions) { 19 | return u 20 | } 21 | 22 | p := u.Path 23 | if !strings.HasPrefix(p, "/") { 24 | p = "/" + p 25 | } 26 | parts := strings.Split(p, "/") 27 | if len(parts) < 4 { 28 | return u 29 | } 30 | 31 | normalized := *u 32 | normalized.Host = githubRawContentHost 33 | normalized.Path = "/" + path.Join(append(parts[:3], parts[4:]...)...) 34 | return &normalized 35 | } 36 | 37 | func extensionMatches(u *url.URL, extensions []string) bool { 38 | ext := path.Ext(u.Path) 39 | for _, e := range extensions { 40 | if strings.EqualFold(ext, e) { 41 | return true 42 | } 43 | } 44 | return false 45 | } 46 | -------------------------------------------------------------------------------- /pkg/github/normalize_test.go: -------------------------------------------------------------------------------- 1 | package github_test 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | 7 | "github.com/influxdata/influx-cli/v2/pkg/github" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestNormalize(t *testing.T) { 12 | t.Parallel() 13 | 14 | testCases := []struct { 15 | name string 16 | in url.URL 17 | exts []string 18 | out url.URL 19 | }{ 20 | { 21 | name: "github URL", 22 | in: url.URL{Host: "github.com", Path: "/influxdata/influxdb/blob/master/flags.yml"}, 23 | out: url.URL{Host: "raw.githubusercontent.com", Path: "/influxdata/influxdb/master/flags.yml"}, 24 | }, 25 | { 26 | name: "github URL with extensions", 27 | in: url.URL{Host: "github.com", Path: "/influxdata/community-templates/blob/master/github/github.yml"}, 28 | exts: []string{".yaml", ".yml", ".jsonnet", ".json"}, 29 | out: url.URL{Host: "raw.githubusercontent.com", Path: "/influxdata/community-templates/master/github/github.yml"}, 30 | }, 31 | { 32 | name: "other URL", 33 | in: url.URL{Host: "google.com", Path: "/fake.yml"}, 34 | out: url.URL{Host: "google.com", Path: "/fake.yml"}, 35 | }, 36 | { 37 | name: "github URL - wrong extension", 38 | in: url.URL{Host: "github.com", Path: "/influxdata/influxdb/blob/master/flags.yml"}, 39 | exts: []string{".json"}, 40 | out: url.URL{Host: "github.com", Path: "/influxdata/influxdb/blob/master/flags.yml"}, 41 | }, 42 | } 43 | 44 | for _, tc := range testCases { 45 | tc := tc 46 | t.Run(tc.name, func(t *testing.T) { 47 | t.Parallel() 48 | 49 | normalized := github.NormalizeURLToContent(&tc.in, tc.exts...) 50 | require.Equal(t, tc.out, *normalized) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/gzip/gunzip.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | ) 7 | 8 | func NewGunzipReadCloser(in io.ReadCloser) (*gunzipReadCloser, error) { 9 | gzr, err := gzip.NewReader(in) 10 | if err != nil { 11 | return nil, err 12 | } 13 | return &gunzipReadCloser{underlying: in, gunzip: gzr}, nil 14 | } 15 | 16 | type gunzipReadCloser struct { 17 | underlying io.ReadCloser 18 | gunzip io.ReadCloser 19 | } 20 | 21 | func (gzrc *gunzipReadCloser) Read(p []byte) (int, error) { 22 | return gzrc.gunzip.Read(p) 23 | } 24 | 25 | func (gzrc *gunzipReadCloser) Close() error { 26 | if err := gzrc.gunzip.Close(); err != nil { 27 | return err 28 | } 29 | return gzrc.underlying.Close() 30 | } 31 | -------------------------------------------------------------------------------- /pkg/gzip/pipe.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | ) 7 | 8 | var _ io.ReadCloser = (*gzipPipe)(nil) 9 | 10 | type gzipPipe struct { 11 | underlying io.ReadCloser 12 | pipeOut io.ReadCloser 13 | } 14 | 15 | // NewGzipPipe returns an io.ReadCloser that wraps an input data stream, 16 | // applying gzip compression to the underlying data on Read and closing the 17 | // underlying data on Close. 18 | func NewGzipPipe(in io.ReadCloser) *gzipPipe { 19 | pr, pw := io.Pipe() 20 | gw := gzip.NewWriter(pw) 21 | 22 | go func() { 23 | _, err := io.Copy(gw, in) 24 | gw.Close() 25 | if err != nil { 26 | pw.CloseWithError(err) 27 | } else { 28 | pw.Close() 29 | } 30 | }() 31 | 32 | return &gzipPipe{underlying: in, pipeOut: pr} 33 | } 34 | 35 | func (gzp gzipPipe) Read(p []byte) (int, error) { 36 | return gzp.pipeOut.Read(p) 37 | } 38 | 39 | func (gzp gzipPipe) Close() error { 40 | if err := gzp.pipeOut.Close(); err != nil { 41 | return err 42 | } 43 | if err := gzp.underlying.Close(); err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/gzip/pipe_test.go: -------------------------------------------------------------------------------- 1 | package gzip_test 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "errors" 7 | "io" 8 | "strings" 9 | "testing" 10 | 11 | pgzip "github.com/influxdata/influx-cli/v2/pkg/gzip" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestGzipPipe(t *testing.T) { 16 | t.Parallel() 17 | 18 | t.Run("success", func(t *testing.T) { 19 | t.Parallel() 20 | 21 | data := strings.Repeat("Data data I'm some data\n", 1024) 22 | reader := strings.NewReader(data) 23 | pipe := pgzip.NewGzipPipe(io.NopCloser(reader)) 24 | defer pipe.Close() 25 | gunzip, err := gzip.NewReader(pipe) 26 | require.NoError(t, err) 27 | defer gunzip.Close() 28 | 29 | out := bytes.Buffer{} 30 | _, err = io.Copy(&out, gunzip) 31 | require.NoError(t, err) 32 | 33 | require.Equal(t, data, out.String()) 34 | }) 35 | 36 | t.Run("error", func(t *testing.T) { 37 | t.Parallel() 38 | 39 | reader := &failingReader{n: 3, err: errors.New("I BROKE")} 40 | pipe := pgzip.NewGzipPipe(io.NopCloser(reader)) 41 | defer pipe.Close() 42 | gunzip, err := gzip.NewReader(pipe) 43 | require.NoError(t, err) 44 | defer gunzip.Close() 45 | 46 | out := bytes.Buffer{} 47 | _, err = io.Copy(&out, gunzip) 48 | require.Error(t, err) 49 | require.Equal(t, reader.err, err) 50 | }) 51 | } 52 | 53 | type failingReader struct { 54 | n int 55 | err error 56 | } 57 | 58 | func (frc *failingReader) Read(p []byte) (int, error) { 59 | if frc.n <= 0 { 60 | return 0, frc.err 61 | } 62 | frc.n-- 63 | p[0] = 'a' 64 | return 1, nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/influxid/id.go: -------------------------------------------------------------------------------- 1 | package influxid 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "errors" 7 | "strconv" 8 | ) 9 | 10 | // IDLength is the exact length a string (or a byte slice representing it) must have in order to be decoded into a valid ID. 11 | const IDLength = 16 12 | 13 | var ( 14 | // ErrInvalidID signifies invalid IDs. 15 | ErrInvalidID = errors.New("invalid ID") 16 | 17 | // ErrInvalidIDLength is returned when an ID has the incorrect number of bytes. 18 | ErrInvalidIDLength = errors.New("id must have a length of 16 bytes") 19 | ) 20 | 21 | // Validate ensures that a passed string has a valid ID syntax. 22 | // Checks that the string is of length 16, and is a valid hex-encoded uint. 23 | func Validate(id string) error { 24 | _, err := Decode(id) 25 | return err 26 | } 27 | 28 | // Encode converts a uint64 to a hex-encoded byte-slice-string. 29 | func Encode(id uint64) string { 30 | b := make([]byte, hex.DecodedLen(IDLength)) 31 | binary.BigEndian.PutUint64(b, id) 32 | 33 | dst := make([]byte, hex.EncodedLen(len(b))) 34 | hex.Encode(dst, b) 35 | return string(dst) 36 | } 37 | 38 | // Decode parses id as a hex-encoded byte-slice-string. 39 | // 40 | // It errors if the input byte slice does not have the correct length 41 | // or if it contains all zeros. 42 | func Decode(id string) (uint64, error) { 43 | if len([]byte(id)) != 16 { 44 | return 0, ErrInvalidIDLength 45 | } 46 | res, err := strconv.ParseUint(id, 16, 64) 47 | if err != nil || res == 0 { 48 | return 0, ErrInvalidID 49 | } 50 | return res, nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/influxid/id_test.go: -------------------------------------------------------------------------------- 1 | package influxid_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/influxdata/influx-cli/v2/pkg/influxid" 8 | ) 9 | 10 | func TestDecode(t *testing.T) { 11 | if _, err := influxid.Decode("020f755c3c082000"); err != nil { 12 | t.Errorf(err.Error()) 13 | } 14 | } 15 | 16 | func TestEncode(t *testing.T) { 17 | res, _ := influxid.Decode("5ca1ab1eba5eba11") 18 | want := []byte{53, 99, 97, 49, 97, 98, 49, 101, 98, 97, 53, 101, 98, 97, 49, 49} 19 | got := []byte(influxid.Encode(res)) 20 | if !bytes.Equal(want, got) { 21 | t.Errorf("encoding error") 22 | } 23 | } 24 | 25 | func TestDecodeFromAllZeros(t *testing.T) { 26 | if _, err := influxid.Decode(string(make([]byte, influxid.IDLength))); err == nil { 27 | t.Errorf("expecting all zeros ID to not be a valid ID") 28 | } 29 | } 30 | 31 | func TestDecodeFromShorterString(t *testing.T) { 32 | if _, err := influxid.Decode("020f75"); err == nil { 33 | t.Errorf("expecting shorter inputs to error") 34 | } 35 | } 36 | 37 | func TestDecodeFromLongerString(t *testing.T) { 38 | if _, err := influxid.Decode("020f755c3c082000aaa"); err == nil { 39 | t.Errorf("expecting shorter inputs to error") 40 | } 41 | } 42 | 43 | func TestDecodeFromEmptyString(t *testing.T) { 44 | if _, err := influxid.Decode(""); err == nil { 45 | t.Errorf("expecting empty inputs to error") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/jsonnet/decode.go: -------------------------------------------------------------------------------- 1 | package jsonnet 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | 7 | "github.com/google/go-jsonnet" 8 | ) 9 | 10 | // Decoder type can decode a jsonnet stream into the given output. 11 | type Decoder struct { 12 | r io.Reader 13 | } 14 | 15 | // NewDecoder creates a new decoder. 16 | func NewDecoder(r io.Reader) *Decoder { 17 | return &Decoder{r: r} 18 | } 19 | 20 | // Decode decodes the stream into the provide value. 21 | func (d *Decoder) Decode(v interface{}) error { 22 | b, err := io.ReadAll(d.r) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | vm := jsonnet.MakeVM() 28 | jsonStr, err := vm.EvaluateAnonymousSnippet("memory", string(b)) 29 | if err != nil { 30 | return err 31 | } 32 | return json.Unmarshal([]byte(jsonStr), &v) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/jsonnet/decode_test.go: -------------------------------------------------------------------------------- 1 | package jsonnet_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/influxdata/influx-cli/v2/pkg/jsonnet" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestDecoder(t *testing.T) { 12 | type ( 13 | person struct { 14 | Name string `json:"name"` 15 | Welcome string `json:"welcome"` 16 | } 17 | 18 | persons struct { 19 | Person1 person `json:"person1"` 20 | Person2 person `json:"person2"` 21 | } 22 | ) 23 | 24 | const entry = `{ 25 | person1: { 26 | name: "Alice", 27 | welcome: "Hello " + self.name + "!", 28 | }, 29 | person2: self.person1 { name: "Bob" }, 30 | }` 31 | 32 | var out persons 33 | require.NoError(t, jsonnet.NewDecoder(strings.NewReader(entry)).Decode(&out)) 34 | 35 | expected := persons{ 36 | Person1: person{ 37 | Name: "Alice", 38 | Welcome: "Hello Alice!", 39 | }, 40 | Person2: person{ 41 | Name: "Bob", 42 | Welcome: "Hello Bob!", 43 | }, 44 | } 45 | require.Equal(t, expected, out) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/signals/context.go: -------------------------------------------------------------------------------- 1 | package signals 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | ) 9 | 10 | // WithSignals returns a context that is canceled with any signal in sigs. 11 | func WithSignals(ctx context.Context, sigs ...os.Signal) context.Context { 12 | sigCh := make(chan os.Signal, 1) 13 | signal.Notify(sigCh, sigs...) 14 | 15 | ctx, cancel := context.WithCancel(ctx) 16 | go func() { 17 | defer cancel() 18 | select { 19 | case <-ctx.Done(): 20 | return 21 | case <-sigCh: 22 | return 23 | } 24 | }() 25 | return ctx 26 | } 27 | 28 | // WithStandardSignals cancels the context on os.Interrupt, syscall.SIGTERM. 29 | func WithStandardSignals(ctx context.Context) context.Context { 30 | return WithSignals(ctx, os.Interrupt, syscall.SIGTERM) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/signals/context_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package signals 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "os" 9 | "syscall" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func ExampleWithSignals() { 15 | ctx := WithSignals(context.Background(), syscall.SIGUSR1) 16 | go func() { 17 | time.Sleep(500 * time.Millisecond) // after some time SIGUSR1 is sent 18 | // mimicking a signal from the outside 19 | syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) 20 | }() 21 | 22 | <-ctx.Done() 23 | fmt.Println("finished") 24 | // Output: 25 | // finished 26 | } 27 | 28 | func Example_withUnregisteredSignals() { 29 | dctx, cancel := context.WithTimeout(context.TODO(), time.Millisecond*100) 30 | defer cancel() 31 | 32 | ctx := WithSignals(dctx, syscall.SIGUSR1) 33 | go func() { 34 | time.Sleep(10 * time.Millisecond) // after some time SIGUSR2 is sent 35 | // mimicking a signal from the outside, WithSignals will not handle it 36 | syscall.Kill(syscall.Getpid(), syscall.SIGUSR2) 37 | }() 38 | 39 | <-ctx.Done() 40 | fmt.Println("finished") 41 | // Output: 42 | // finished 43 | } 44 | 45 | func TestWithSignals(t *testing.T) { 46 | tests := []struct { 47 | name string 48 | ctx context.Context 49 | sigs []os.Signal 50 | wantSignal bool 51 | }{ 52 | { 53 | name: "sending signal SIGUSR2 should exit context.", 54 | ctx: context.Background(), 55 | sigs: []os.Signal{syscall.SIGUSR2}, 56 | wantSignal: true, 57 | }, 58 | { 59 | name: "sending signal SIGUSR2 should NOT exit context.", 60 | ctx: context.Background(), 61 | sigs: []os.Signal{syscall.SIGUSR1}, 62 | }, 63 | } 64 | for _, tt := range tests { 65 | t.Run(tt.name, func(t *testing.T) { 66 | ctx := WithSignals(tt.ctx, tt.sigs...) 67 | syscall.Kill(syscall.Getpid(), syscall.SIGUSR2) 68 | timer := time.NewTimer(500 * time.Millisecond) 69 | select { 70 | case <-ctx.Done(): 71 | if !tt.wantSignal { 72 | t.Errorf("unexpected exit with signal") 73 | } 74 | case <-timer.C: 75 | if tt.wantSignal { 76 | t.Errorf("expected to exit with signal but did not") 77 | } 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pkg/stdio/noninteractive.go: -------------------------------------------------------------------------------- 1 | package stdio 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | // noninteractiveStdio interacts with stdin/stdout/stderr as files, with no user interaction. 11 | type noninteractiveStdio struct { 12 | in *bufio.Scanner 13 | out io.Writer 14 | err io.Writer 15 | } 16 | 17 | func (t *noninteractiveStdio) Write(p []byte) (int, error) { 18 | return t.out.Write(p) 19 | } 20 | 21 | func (t *noninteractiveStdio) WriteErr(p []byte) (int, error) { 22 | return t.err.Write(p) 23 | } 24 | 25 | func (t *noninteractiveStdio) Banner(message string) error { 26 | _, err := fmt.Fprintf(t.out, "> %s\n", message) 27 | return err 28 | } 29 | 30 | func (t *noninteractiveStdio) Error(message string) error { 31 | _, err := fmt.Fprintf(t.err, "X %s\n", message) 32 | return err 33 | } 34 | 35 | func (t *noninteractiveStdio) IsInteractive() bool { 36 | return false 37 | } 38 | 39 | func (t *noninteractiveStdio) GetStringInput(prompt, defaultValue string) (string, error) { 40 | if inLine := t.readStdinLine(); inLine != "" { 41 | return inLine, nil 42 | } 43 | if defaultValue != "" { 44 | return defaultValue, nil 45 | } 46 | return "", fmt.Errorf("couldn't get input for prompt %q: no data on stdin", prompt) 47 | } 48 | 49 | func (t *noninteractiveStdio) GetSecret(prompt string, minLen int) (string, error) { 50 | inLine := t.readStdinLine() 51 | if len(inLine) >= minLen { 52 | return inLine, nil 53 | } else if minLen > 0 { 54 | return "", fmt.Errorf("value for prompt %q is too short: min length is %d", prompt, minLen) 55 | } 56 | return "", nil 57 | } 58 | 59 | func (t *noninteractiveStdio) GetPassword(prompt string) (string, error) { 60 | return t.GetSecret(prompt, MinPasswordLen) 61 | } 62 | 63 | func (t *noninteractiveStdio) GetConfirm(prompt string) (answer bool) { 64 | return strings.HasPrefix(t.readStdinLine(), "y") 65 | } 66 | 67 | // readStdinLine returns the first line of text on stdin, or empty string if stdin is at EOF. 68 | func (t *noninteractiveStdio) readStdinLine() string { 69 | if !t.in.Scan() { 70 | return "" 71 | } 72 | return t.in.Text() 73 | } 74 | -------------------------------------------------------------------------------- /pkg/stdio/stdio.go: -------------------------------------------------------------------------------- 1 | package stdio 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os" 7 | 8 | "github.com/AlecAivazis/survey/v2/terminal" 9 | "github.com/mattn/go-isatty" 10 | ) 11 | 12 | // Disable password length checking to let influxdb handle it 13 | const MinPasswordLen = 0 14 | 15 | type StdIO interface { 16 | // Write prints some bytes to stdout. 17 | Write(p []byte) (n int, err error) 18 | // WriteErr prints some bytes to stderr. 19 | WriteErr(p []byte) (n int, err error) 20 | // Banner displays informational text to the user. 21 | Banner(message string) error 22 | // Error displays an error message to the user. 23 | Error(message string) error 24 | // IsInteractive signals whether interactive I/O is supported. 25 | IsInteractive() bool 26 | // GetStringInput prompts the user for arbitrary input. 27 | GetStringInput(prompt, defaultValue string) (string, error) 28 | // GetSecret prompts the user for a secret. 29 | GetSecret(prompt string, minLen int) (string, error) 30 | // GetPassword prompts the user for a secret twice, and inputs must match. 31 | // Uses stdio.MinPasswordLen as the minimum input length 32 | GetPassword(prompt string) (string, error) 33 | // GetConfirm asks the user for a y/n answer to a prompt. 34 | GetConfirm(prompt string) bool 35 | } 36 | 37 | func newTerminalStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) StdIO { 38 | interactiveIn := isatty.IsTerminal(in.Fd()) || isatty.IsCygwinTerminal(in.Fd()) 39 | interactiveOut := isatty.IsTerminal(out.Fd()) || isatty.IsCygwinTerminal(out.Fd()) 40 | 41 | if interactiveIn && interactiveOut { 42 | return &interactiveStdio{in: in, out: out, err: err} 43 | } 44 | 45 | return &noninteractiveStdio{ 46 | in: bufio.NewScanner(in), 47 | out: out, 48 | err: err, 49 | } 50 | } 51 | 52 | // TerminalStdio interacts with users over stdin/stdout/stderr. 53 | var TerminalStdio = newTerminalStdio(os.Stdin, os.Stdout, os.Stderr) 54 | -------------------------------------------------------------------------------- /pkg/tabwriter/tabwriter.go: -------------------------------------------------------------------------------- 1 | package tabwriter 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "text/tabwriter" 8 | ) 9 | 10 | // TabWriter wraps tab writer headers logic. 11 | type TabWriter struct { 12 | writer *tabwriter.Writer 13 | headers []string 14 | hideHeaders bool 15 | } 16 | 17 | // NewTabWriter creates a new tab writer. 18 | func NewTabWriter(w io.Writer, hideHeaders bool) *TabWriter { 19 | return &TabWriter{ 20 | writer: tabwriter.NewWriter(w, 0, 8, 1, '\t', 0), 21 | hideHeaders: hideHeaders, 22 | } 23 | } 24 | 25 | // WriteHeaders will Write headers. 26 | func (w *TabWriter) WriteHeaders(h ...string) error { 27 | w.headers = h 28 | if !w.hideHeaders { 29 | if _, err := fmt.Fprintln(w.writer, strings.Join(h, "\t")); err != nil { 30 | return err 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | // Write will Write the map into embed tab writer. 37 | func (w *TabWriter) Write(m map[string]interface{}) error { 38 | body := make([]interface{}, len(w.headers)) 39 | types := make([]string, len(w.headers)) 40 | for i, h := range w.headers { 41 | v := m[h] 42 | body[i] = v 43 | types[i] = formatStringType(v) 44 | } 45 | formatString := strings.Join(types, "\t") 46 | if _, err := fmt.Fprintf(w.writer, formatString+"\n", body...); err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | // Flush should be called after the last call to Write to ensure 53 | // that any data buffered in the Writer is written to output. Any 54 | // incomplete escape sequence at the end is considered 55 | // complete for formatting purposes. 56 | func (w *TabWriter) Flush() error { 57 | return w.writer.Flush() 58 | } 59 | 60 | func formatStringType(v interface{}) string { 61 | switch v.(type) { 62 | case int: 63 | return "%d" 64 | case string: 65 | return "%s" 66 | } 67 | return "%v" 68 | } 69 | -------------------------------------------------------------------------------- /pkg/tabwriter/tabwriter_test.go: -------------------------------------------------------------------------------- 1 | package tabwriter_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/influxdata/influx-cli/v2/pkg/tabwriter" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_WriteHeaders(t *testing.T) { 12 | out := bytes.Buffer{} 13 | w := tabwriter.NewTabWriter(&out, false) 14 | require.NoError(t, w.WriteHeaders("foo", "bar", "baz")) 15 | require.NoError(t, w.Flush()) 16 | require.Equal(t, "foo\tbar\tbaz\n", out.String()) 17 | } 18 | 19 | func Test_WriteHeadersDisabled(t *testing.T) { 20 | out := bytes.Buffer{} 21 | w := tabwriter.NewTabWriter(&out, true) 22 | require.NoError(t, w.WriteHeaders("foo", "bar", "baz")) 23 | require.NoError(t, w.Flush()) 24 | require.Empty(t, out.String()) 25 | } 26 | 27 | func Test_Write(t *testing.T) { 28 | out := bytes.Buffer{} 29 | w := tabwriter.NewTabWriter(&out, true) 30 | require.NoError(t, w.WriteHeaders("foo", "bar", "baz")) 31 | require.NoError(t, w.Write(map[string]interface{}{ 32 | "bar": 123, 33 | "foo": "a string!", 34 | "baz": false, 35 | })) 36 | require.NoError(t, w.Flush()) 37 | require.Equal(t, "a string!\t123\tfalse\n", out.String()) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/template/color.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "github.com/olekukonko/tablewriter" 6 | ) 7 | 8 | var ( 9 | colorTitle = color.New(color.FgYellow, color.Bold) 10 | colorTotalAdd = color.New(color.FgHiGreen, color.Bold) 11 | colorTotalRemove = color.New(color.FgRed, color.Bold) 12 | 13 | noColor = tablewriter.Colors{} 14 | colorAdd = tablewriter.Colors{tablewriter.FgHiGreenColor, tablewriter.Bold} 15 | colorFooter = tablewriter.Color(tablewriter.FgHiBlueColor, tablewriter.Bold) 16 | colorHeader = tablewriter.Colors{tablewriter.FgHiCyanColor, tablewriter.Bold} 17 | colorRemove = tablewriter.Colors{tablewriter.FgRedColor, tablewriter.Bold} 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/template/out.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/influxdata/influx-cli/v2/api" 11 | "gopkg.in/yaml.v3" 12 | ) 13 | 14 | type OutEncoding int 15 | 16 | const ( 17 | YamlEncoding OutEncoding = iota 18 | JsonEncoding 19 | ) 20 | 21 | type OutParams struct { 22 | Out io.Writer 23 | Encoding OutEncoding 24 | } 25 | 26 | func ParseOutParams(path string, fallback io.Writer) (OutParams, func(), error) { 27 | if path == "" { 28 | return OutParams{Out: fallback, Encoding: YamlEncoding}, nil, nil 29 | } 30 | 31 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 32 | if err != nil { 33 | return OutParams{}, nil, fmt.Errorf("failed to open output path %q: %w", path, err) 34 | } 35 | params := OutParams{Out: f} 36 | switch filepath.Ext(path) { 37 | case ".json": 38 | params.Encoding = JsonEncoding 39 | default: 40 | params.Encoding = YamlEncoding 41 | } 42 | 43 | return params, func() { _ = f.Close() }, nil 44 | } 45 | 46 | func (o OutParams) WriteTemplate(template []api.TemplateEntry) error { 47 | switch o.Encoding { 48 | case JsonEncoding: 49 | enc := json.NewEncoder(o.Out) 50 | enc.SetIndent("", "\t") 51 | return enc.Encode(template) 52 | case YamlEncoding: 53 | enc := yaml.NewEncoder(o.Out) 54 | for _, entry := range template { 55 | if err := enc.Encode(entry); err != nil { 56 | return err 57 | } 58 | } 59 | default: 60 | return fmt.Errorf("encoding %q is not recognized", o.Encoding) 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/template/table_printer_test.go: -------------------------------------------------------------------------------- 1 | package template_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/influxdata/influx-cli/v2/pkg/template" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestTablePrinter_Empty(t *testing.T) { 12 | t.Parallel() 13 | 14 | out := bytes.Buffer{} 15 | printer := template.NewTablePrinter(&out, false, true). 16 | Title("Example"). 17 | SetHeaders("Wow", "Such", "A", "Fancy", "Printer") 18 | 19 | printer.Render() 20 | require.Empty(t, out.String()) 21 | } 22 | 23 | func TestTablePrinter(t *testing.T) { 24 | t.Parallel() 25 | 26 | out := bytes.Buffer{} 27 | printer := template.NewTablePrinter(&out, false, true). 28 | Title("Example"). 29 | SetHeaders("Wow", "Such", "A", "Fancy", "Printer") 30 | 31 | printer.Append([]string{"foo", "bar", "baz", "qux", "wat"}) 32 | printer.Append([]string{"veryveryverylongggg", "", "a", "b", "c"}) 33 | 34 | printer.Render() 35 | expected := `EXAMPLE 36 | +---------------------+------+-----+-------+---------+ 37 | | WOW | SUCH | A | FANCY | PRINTER | 38 | +---------------------+------+-----+-------+---------+ 39 | | foo | bar | baz | qux | wat | 40 | +---------------------+------+-----+-------+---------+ 41 | | veryveryverylongggg | | a | b | c | 42 | +---------------------+------+-----+-------+---------+ 43 | | TOTAL | 2 | 44 | +---------------------+------+-----+-------+---------+ 45 | ` 46 | require.Equal(t, expected, out.String()) 47 | } 48 | 49 | func TestTablePrinter_Description(t *testing.T) { 50 | t.Parallel() 51 | 52 | out := bytes.Buffer{} 53 | printer := template.NewTablePrinter(&out, false, true). 54 | Title("Example"). 55 | SetHeaders("Wow", "Such", "A", "Fancy", "Description") 56 | printer.Append([]string{"once", "upon", "a", "time", "short description"}) 57 | 58 | printer.Render() 59 | // Expect that the description is left-aligned with a min width. 60 | expected := `EXAMPLE 61 | +------+------+---+-------+--------------------------------+ 62 | | WOW | SUCH | A | FANCY | DESCRIPTION | 63 | +------+------+---+-------+--------------------------------+ 64 | | once | upon | a | time | short description | 65 | +------+------+---+-------+--------------------------------+ 66 | | TOTAL | 1 | 67 | +------+------+---+-------+--------------------------------+ 68 | ` 69 | require.Equal(t, expected, out.String()) 70 | } 71 | -------------------------------------------------------------------------------- /scripts/ci/build-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exo pipefail 3 | 4 | function build_test_binaries () { 5 | local tags="" 6 | if [ "$(go env GOOS)" = windows ]; then 7 | tags="-tags timetzdata" 8 | fi 9 | 10 | CGO_ENABLED=0 go-test-compile ${tags} -o "${1}/" ./... 11 | } 12 | 13 | function build_test_tools () { 14 | # Copy pre-built gotestsum out of the cross-builder. 15 | local ext="" 16 | if [ "$(go env GOOS)" = windows ]; then 17 | ext=".exe" 18 | fi 19 | cp "/usr/local/bin/gotestsum_$(go env GOOS)_$(go env GOARCH)${ext}" "$1/gotestsum${ext}" 20 | 21 | # Build test2json from the installed Go distribution. 22 | CGO_ENABLED=0 go build -o "${1}/" -ldflags="-s -w" cmd/test2json 23 | } 24 | 25 | function write_test_metadata () { 26 | # Write version that should be reported in test results. 27 | echo "$(go env GOVERSION) $(go env GOOS)/$(go env GOARCH)" > "${1}/go.version" 28 | 29 | # Write list of all packages. 30 | go list ./... > "${1}/tests.list" 31 | } 32 | 33 | function main () { 34 | if [[ $# != 1 ]]; then 35 | >&2 echo Usage: $0 '' 36 | >&2 echo '' will be created if it does not already exist 37 | exit 1 38 | fi 39 | local -r out_dir="$1" 40 | 41 | mkdir -p "$out_dir" 42 | 43 | # Build all test binaries. 44 | build_test_binaries "${out_dir}/" 45 | # Build gotestsum and test2json so downstream jobs can use it without needing `go`. 46 | build_test_tools "$out_dir" 47 | # Write other metadata needed for testing. 48 | write_test_metadata "$out_dir" 49 | } 50 | 51 | main ${@} 52 | -------------------------------------------------------------------------------- /scripts/ci/install-go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | declare -r GO_VERSION=1.19.6 5 | 6 | # Hashes are from the table at https://golang.org/dl/ 7 | function go_hash () { 8 | case $1 in 9 | linux_amd64) 10 | echo e3410c676ced327aec928303fef11385702a5562fd19d9a1750d5a2979763c3d 11 | ;; 12 | linux_arm64) 13 | echo e4d63c933a68e5fad07cab9d12c5c1610ce4810832d47c44314c3246f511ac4f 14 | ;; 15 | mac) 16 | echo 63386d51c69cef6c001ff0436832289635ba4a2649282451a18827e93507b444 17 | ;; 18 | windows) 19 | echo 8d84af29e46c38b1eec77f9310310517c9e394ac7489e1c7329a94b443b0388d 20 | ;; 21 | esac 22 | } 23 | 24 | function install_go_linux () { 25 | local -r arch=$(dpkg --print-architecture) 26 | ARCHIVE=go${GO_VERSION}.linux-${arch}.tar.gz 27 | wget https://golang.org/dl/${ARCHIVE} 28 | echo "$(go_hash linux_${arch}) ${ARCHIVE}" | sha256sum --check -- 29 | tar -C $1 -xzf ${ARCHIVE} 30 | rm ${ARCHIVE} 31 | } 32 | 33 | function install_go_mac () { 34 | ARCHIVE=go${GO_VERSION}.darwin-amd64.tar.gz 35 | wget https://golang.org/dl/${ARCHIVE} 36 | echo "$(go_hash mac) ${ARCHIVE}" | shasum -a 256 --check - 37 | tar -C $1 -xzf ${ARCHIVE} 38 | rm ${ARCHIVE} 39 | } 40 | 41 | function install_go_windows () { 42 | ARCHIVE=go${GO_VERSION}.windows-amd64.zip 43 | wget https://golang.org/dl/${ARCHIVE} 44 | echo "$(go_hash windows) ${ARCHIVE}" | sha256sum --check -- 45 | unzip -qq -d $1 ${ARCHIVE} 46 | rm ${ARCHIVE} 47 | } 48 | 49 | function main () { 50 | if [[ $# != 1 ]]; then 51 | >&2 echo Usage: $0 '' 52 | exit 1 53 | fi 54 | local -r install_dir=$1 55 | 56 | rm -rf "$install_dir" 57 | mkdir -p "$install_dir" 58 | case $(uname) in 59 | Linux) 60 | install_go_linux "$install_dir" 61 | ;; 62 | Darwin) 63 | install_go_mac "$install_dir" 64 | ;; 65 | MSYS_NT*) 66 | install_go_windows "$install_dir" 67 | ;; 68 | *) 69 | >&2 echo Error: unknown OS $(uname) 70 | exit 1 71 | ;; 72 | esac 73 | 74 | "${install_dir}/go/bin/go" version 75 | } 76 | 77 | main ${@} 78 | -------------------------------------------------------------------------------- /scripts/ci/install-rosetta.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | if [[ "${MACHTYPE}" == "arm64-apple-darwin"* ]] 5 | then 6 | /usr/sbin/softwareupdate --install-rosetta --agree-to-license 7 | fi 8 | -------------------------------------------------------------------------------- /scripts/ci/run-prebuilt-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exo pipefail 3 | 4 | function test_package () { 5 | local -r pkg="$1" bin_dir="$2" result_dir="$3" 6 | 7 | local -r test_file="${bin_dir}/${pkg}.test" 8 | if [ ! -f "$test_file" ]; then 9 | return 10 | fi 11 | 12 | out_dir="${result_dir}/${pkg}" 13 | mkdir -p "${out_dir}" 14 | 15 | # Run test files from within their original packages so any relative references 16 | # to data files resolve properly. 17 | local source_dir="${pkg##github.com/influxdata/influx-cli/v2}" 18 | source_dir="${source_dir##/}" 19 | if [ -z "$source_dir" ]; then 20 | source_dir="." 21 | fi 22 | ( 23 | set +e 24 | cd "$source_dir" 25 | GOVERSION="$(cat ${bin_dir}/go.version)" "${bin_dir}/gotestsum" --junitfile "${out_dir}/report.xml" --raw-command -- \ 26 | "${bin_dir}/test2json" -t -p "$pkg" "$test_file" -test.v 27 | if [ $? != 0 ]; then 28 | echo 1 > "${result_dir}/rc" 29 | fi 30 | ) 31 | } 32 | 33 | function main () { 34 | if [[ $# != 2 ]]; then 35 | >&2 echo Usage: $0 '' '' 36 | >&2 echo '' will be created if it does not already exist 37 | exit 1 38 | fi 39 | local -r bin_dir="$1" result_dir="$2" 40 | 41 | mkdir -p "$result_dir" 42 | 43 | local -r test_packages="$(cat "${bin_dir}/tests.list" | circleci tests split --split-by=timings --timings-type=classname)" 44 | 45 | # Pre-built test binaries will exit with a nonzero return code if the tests they encapsulate fail. 46 | # We don't want to fail-fast the first time a test fails, so (elsewhere in the script) we `set +e` before running 47 | # anything. By default that would cause the script to exit with success even when tests fail, which we don't want 48 | # either. To work around this, we track the final return code for this script in the `rc` file. The code is 49 | # initialized to 0, and overwritten to be 1 if any test binary fails. 50 | echo 0 > "${result_dir}/rc" 51 | for pkg in ${test_packages[@]}; do 52 | test_package "$pkg" "$bin_dir" "$result_dir" 53 | done 54 | 55 | exit $(cat "${result_dir}/rc") 56 | } 57 | 58 | main ${@} 59 | -------------------------------------------------------------------------------- /scripts/ci/run-race-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exo pipefail 3 | 4 | function main () { 5 | if [[ $# != 1 ]]; then 6 | >&2 echo Usage: $0 '' 7 | exit 1 8 | fi 9 | if [[ $(go env GOOS) != linux || $(go env GOARCH) != amd64 ]]; then 10 | >&2 echo Race tests only supported on linux/amd64 11 | exit 1 12 | fi 13 | 14 | local -r out_dir="$1" 15 | mkdir -p "$out_dir" 16 | 17 | gotestsum --junitfile "${out_dir}/report.xml" -- -race ./... 18 | } 19 | 20 | main ${@} 21 | -------------------------------------------------------------------------------- /scripts/ci/setup-system.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | function setup_linux () { 5 | sudo apt-get update 6 | sudo apt-get install -y --no-install-recommends make 7 | } 8 | 9 | function setup_mac () { 10 | # Python and TCL both come pre-installed on Circle's mac executors, and both depend on wget in some way. 11 | # Homebrew will auto-upgrade both of them when wget is installed/upgraded, triggering a chain of upgrades. 12 | # Uninstall them both before adding wget to avoid burning time in CI for things we don't need. 13 | brew remove --force python@3.9 tcl-tk 14 | HOMEBREW_NO_AUTO_UPDATE=1 brew install wget 15 | } 16 | 17 | function setup_windows () { 18 | choco install make mingw wget 19 | } 20 | 21 | function main () { 22 | case $(uname) in 23 | Linux) 24 | setup_linux 25 | ;; 26 | Darwin) 27 | setup_mac 28 | ;; 29 | MSYS_NT*) 30 | setup_windows 31 | ;; 32 | *) 33 | >&2 echo Error: unknown OS $(uname) 34 | exit 1 35 | ;; 36 | esac 37 | } 38 | 39 | main ${@} 40 | -------------------------------------------------------------------------------- /support.mk: -------------------------------------------------------------------------------- 1 | 2 | # Add quotes to a string and escape any internal quotes. 3 | # $(call with-quotes,alice bob) -> "alice bob" 4 | # $(call with-quotes,) -> "" 5 | # $(call with-quotes,Bobby "Drop Tables") -> "Bobby \"Drop Tables\"" 6 | define with-quotes 7 | $(if $(1),"$(subst ",\",$(1))") 8 | endef 9 | 10 | # Generate a command line option in the form $(1)"$(2)", but only if $(2) 11 | # is not empty. 12 | # $(call with-param,-t=,) -> 13 | # $(call with-param,-t=,Ralph Wiggum) -> -t="Ralph Wiggum" 14 | # $(call with-param,-t=,Bobby "Drop Tables") -> -t="Bobby \"Drop Tables\"" 15 | define with-param 16 | $(if $(2),$(1)$(call with-quotes,$(2))) 17 | endef 18 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package influxcli 5 | 6 | // This package is a workaround for adding additional paths to the go.mod file 7 | // and ensuring they stay there. The build tag ensures this file never gets 8 | // compiled, but the go module tool will still look at the dependencies and 9 | // add/keep them in go.mod so we can version these paths along with our other 10 | // dependencies. When we run build on any of these paths, we get the version 11 | // that has been specified in go.mod rather than the master copy. 12 | 13 | import ( 14 | _ "github.com/daixiang0/gci" 15 | _ "github.com/golang/mock/mockgen" 16 | _ "golang.org/x/tools/cmd/goimports" 17 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 18 | _ "honnef.co/go/tools/cmd/staticcheck" 19 | ) 20 | --------------------------------------------------------------------------------