From 1eaea347201e09d0ef1cc5d8901840ba87508140 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:03:29 +0000 Subject: [PATCH] Update github.com/sapcc/swift-http-import digest to f33ec7a --- go.mod | 18 +- go.sum | 22 + .../google/go-github/{v62 => v63}/AUTHORS | 0 .../google/go-github/{v62 => v63}/LICENSE | 0 .../go-github/{v62 => v63}/github/actions.go | 0 .../{v62 => v63}/github/actions_artifacts.go | 0 .../{v62 => v63}/github/actions_cache.go | 0 .../{v62 => v63}/github/actions_oidc.go | 0 .../github/actions_permissions_enterprise.go | 0 .../github/actions_permissions_orgs.go | 0 .../github/actions_required_workflows.go | 0 .../github/actions_runner_groups.go | 0 .../{v62 => v63}/github/actions_runners.go | 0 .../{v62 => v63}/github/actions_secrets.go | 0 .../{v62 => v63}/github/actions_variables.go | 0 .../github/actions_workflow_jobs.go | 0 .../github/actions_workflow_runs.go | 1 + .../{v62 => v63}/github/actions_workflows.go | 0 .../go-github/{v62 => v63}/github/activity.go | 0 .../{v62 => v63}/github/activity_events.go | 0 .../github/activity_notifications.go | 0 .../{v62 => v63}/github/activity_star.go | 0 .../{v62 => v63}/github/activity_watching.go | 0 .../go-github/{v62 => v63}/github/admin.go | 0 .../{v62 => v63}/github/admin_orgs.go | 0 .../{v62 => v63}/github/admin_stats.go | 0 .../{v62 => v63}/github/admin_users.go | 0 .../go-github/{v62 => v63}/github/apps.go | 2 + .../{v62 => v63}/github/apps_hooks.go | 0 .../github/apps_hooks_deliveries.go | 0 .../{v62 => v63}/github/apps_installation.go | 0 .../{v62 => v63}/github/apps_manifest.go | 0 .../{v62 => v63}/github/apps_marketplace.go | 0 .../{v62 => v63}/github/authorizations.go | 0 .../go-github/{v62 => v63}/github/billing.go | 0 .../go-github/{v62 => v63}/github/checks.go | 0 .../{v62 => v63}/github/code-scanning.go | 0 .../{v62 => v63}/github/codesofconduct.go | 0 .../{v62 => v63}/github/codespaces.go | 0 .../{v62 => v63}/github/codespaces_secrets.go | 0 .../go-github/{v62 => v63}/github/copilot.go | 0 .../{v62 => v63}/github/dependabot.go | 0 .../{v62 => v63}/github/dependabot_alerts.go | 0 .../{v62 => v63}/github/dependabot_secrets.go | 0 .../{v62 => v63}/github/dependency_graph.go | 0 .../github/dependency_graph_snapshots.go | 0 .../go-github/{v62 => v63}/github/doc.go | 2 +- .../go-github/{v62 => v63}/github/emojis.go | 0 .../{v62 => v63}/github/enterprise.go | 0 .../enterprise_actions_runner_groups.go | 0 .../github/enterprise_actions_runners.go | 23 +- .../github/enterprise_audit_log.go | 0 .../enterprise_code_security_and_analysis.go | 0 .../go-github/{v62 => v63}/github/event.go | 0 .../{v62 => v63}/github/event_types.go | 0 .../go-github/{v62 => v63}/github/gists.go | 0 .../{v62 => v63}/github/gists_comments.go | 0 .../go-github/{v62 => v63}/github/git.go | 0 .../{v62 => v63}/github/git_blobs.go | 0 .../{v62 => v63}/github/git_commits.go | 0 .../go-github/{v62 => v63}/github/git_refs.go | 0 .../go-github/{v62 => v63}/github/git_tags.go | 0 .../{v62 => v63}/github/git_trees.go | 0 .../{v62 => v63}/github/github-accessors.go | 126 ++- .../go-github/{v62 => v63}/github/github.go | 2 +- .../{v62 => v63}/github/gitignore.go | 0 .../{v62 => v63}/github/interactions.go | 0 .../{v62 => v63}/github/interactions_orgs.go | 0 .../{v62 => v63}/github/interactions_repos.go | 0 .../{v62 => v63}/github/issue_import.go | 0 .../go-github/{v62 => v63}/github/issues.go | 0 .../{v62 => v63}/github/issues_assignees.go | 0 .../{v62 => v63}/github/issues_comments.go | 0 .../{v62 => v63}/github/issues_events.go | 0 .../{v62 => v63}/github/issues_labels.go | 0 .../{v62 => v63}/github/issues_milestones.go | 0 .../{v62 => v63}/github/issues_timeline.go | 0 .../go-github/{v62 => v63}/github/licenses.go | 0 .../go-github/{v62 => v63}/github/markdown.go | 0 .../go-github/{v62 => v63}/github/messages.go | 0 .../go-github/{v62 => v63}/github/meta.go | 0 .../{v62 => v63}/github/migrations.go | 0 .../github/migrations_source_import.go | 0 .../{v62 => v63}/github/migrations_user.go | 0 .../go-github/{v62 => v63}/github/orgs.go | 0 .../github/orgs_actions_allowed.go | 0 .../github/orgs_actions_permissions.go | 0 .../{v62 => v63}/github/orgs_audit_log.go | 0 .../github/orgs_credential_authorizations.go | 0 .../{v62 => v63}/github/orgs_custom_roles.go | 135 ++- .../{v62 => v63}/github/orgs_hooks.go | 0 .../github/orgs_hooks_configuration.go | 0 .../github/orgs_hooks_deliveries.go | 0 .../{v62 => v63}/github/orgs_members.go | 0 .../github/orgs_outside_collaborators.go | 0 .../{v62 => v63}/github/orgs_packages.go | 0 .../github/orgs_personal_access_tokens.go | 0 .../{v62 => v63}/github/orgs_projects.go | 0 .../{v62 => v63}/github/orgs_properties.go | 17 +- .../{v62 => v63}/github/orgs_rules.go | 0 .../github/orgs_security_managers.go | 0 .../github/orgs_users_blocking.go | 0 .../go-github/{v62 => v63}/github/packages.go | 0 .../go-github/{v62 => v63}/github/projects.go | 0 .../go-github/{v62 => v63}/github/pulls.go | 0 .../{v62 => v63}/github/pulls_comments.go | 0 .../{v62 => v63}/github/pulls_reviewers.go | 0 .../{v62 => v63}/github/pulls_reviews.go | 9 +- .../{v62 => v63}/github/pulls_threads.go | 0 .../{v62 => v63}/github/rate_limit.go | 0 .../{v62 => v63}/github/reactions.go | 0 .../go-github/{v62 => v63}/github/repos.go | 0 .../github/repos_actions_access.go | 0 .../github/repos_actions_allowed.go | 0 .../github/repos_actions_permissions.go | 0 .../{v62 => v63}/github/repos_autolinks.go | 0 .../{v62 => v63}/github/repos_codeowners.go | 0 .../github/repos_collaborators.go | 2 + .../{v62 => v63}/github/repos_comments.go | 0 .../{v62 => v63}/github/repos_commits.go | 0 .../github/repos_community_health.go | 0 .../{v62 => v63}/github/repos_contents.go | 14 +- .../repos_deployment_branch_policies.go | 0 .../repos_deployment_protection_rules.go | 0 .../{v62 => v63}/github/repos_deployments.go | 0 .../{v62 => v63}/github/repos_environments.go | 0 .../{v62 => v63}/github/repos_forks.go | 0 .../{v62 => v63}/github/repos_hooks.go | 0 .../github/repos_hooks_configuration.go | 0 .../github/repos_hooks_deliveries.go | 0 .../{v62 => v63}/github/repos_invitations.go | 0 .../{v62 => v63}/github/repos_keys.go | 0 .../{v62 => v63}/github/repos_lfs.go | 0 .../{v62 => v63}/github/repos_merging.go | 0 .../{v62 => v63}/github/repos_pages.go | 0 .../github/repos_prereceive_hooks.go | 0 .../{v62 => v63}/github/repos_projects.go | 0 .../{v62 => v63}/github/repos_properties.go | 0 .../{v62 => v63}/github/repos_releases.go | 0 .../{v62 => v63}/github/repos_rules.go | 81 +- .../{v62 => v63}/github/repos_stats.go | 0 .../{v62 => v63}/github/repos_statuses.go | 0 .../{v62 => v63}/github/repos_tags.go | 0 .../{v62 => v63}/github/repos_traffic.go | 0 .../go-github/{v62 => v63}/github/scim.go | 0 .../go-github/{v62 => v63}/github/search.go | 0 .../{v62 => v63}/github/secret_scanning.go | 0 .../github/security_advisories.go | 0 .../go-github/{v62 => v63}/github/strings.go | 0 .../go-github/{v62 => v63}/github/teams.go | 0 .../github/teams_discussion_comments.go | 0 .../{v62 => v63}/github/teams_discussions.go | 0 .../{v62 => v63}/github/teams_members.go | 0 .../{v62 => v63}/github/timestamp.go | 0 .../go-github/{v62 => v63}/github/users.go | 0 .../github/users_administration.go | 0 .../{v62 => v63}/github/users_blocking.go | 0 .../{v62 => v63}/github/users_emails.go | 0 .../{v62 => v63}/github/users_followers.go | 0 .../{v62 => v63}/github/users_gpg_keys.go | 0 .../{v62 => v63}/github/users_keys.go | 0 .../{v62 => v63}/github/users_packages.go | 0 .../{v62 => v63}/github/users_projects.go | 0 .../github/users_ssh_signing_keys.go | 0 .../{v62 => v63}/github/with_appengine.go | 0 .../{v62 => v63}/github/without_appengine.go | 0 .../gophercloud/gophercloud/v2/.gitignore | 5 + .../gophercloud/gophercloud/v2/.golangci.yaml | 19 + .../gophercloud/gophercloud/v2/CHANGELOG.md | 983 ++++++++++++++++++ .../gophercloud/gophercloud/v2/LICENSE | 192 ++++ .../gophercloud/gophercloud/v2/Makefile | 109 ++ .../gophercloud/gophercloud/v2/README.md | 254 +++++ .../gophercloud/gophercloud/v2/RELEASE.md | 79 ++ .../gophercloud/v2/auth_options.go | 517 +++++++++ .../gophercloud/gophercloud/v2/auth_result.go | 52 + .../gophercloud/gophercloud/v2/doc.go | 148 +++ .../gophercloud/v2/endpoint_search.go | 76 ++ .../gophercloud/gophercloud/v2/errors.go | 351 +++++++ .../gophercloud/v2/openstack/auth_env.go | 137 +++ .../gophercloud/v2/openstack/client.go | 492 +++++++++ .../gophercloud/v2/openstack/doc.go | 14 + .../v2/openstack/endpoint_location.go | 111 ++ .../gophercloud/v2/openstack/errors.go | 47 + .../v2/openstack/identity/v2/tenants/doc.go | 65 ++ .../openstack/identity/v2/tenants/requests.go | 122 +++ .../openstack/identity/v2/tenants/results.go | 95 ++ .../v2/openstack/identity/v2/tenants/urls.go | 23 + .../v2/openstack/identity/v2/tokens/doc.go | 46 + .../openstack/identity/v2/tokens/requests.go | 114 ++ .../openstack/identity/v2/tokens/results.go | 174 ++++ .../v2/openstack/identity/v2/tokens/urls.go | 13 + .../v2/openstack/identity/v3/ec2tokens/doc.go | 40 + .../identity/v3/ec2tokens/requests.go | 378 +++++++ .../openstack/identity/v3/ec2tokens/urls.go | 11 + .../v2/openstack/identity/v3/oauth1/doc.go | 122 +++ .../openstack/identity/v3/oauth1/requests.go | 588 +++++++++++ .../openstack/identity/v3/oauth1/results.go | 317 ++++++ .../v2/openstack/identity/v3/oauth1/urls.go | 43 + .../v2/openstack/identity/v3/tokens/doc.go | 107 ++ .../openstack/identity/v3/tokens/requests.go | 179 ++++ .../openstack/identity/v3/tokens/results.go | 215 ++++ .../v2/openstack/identity/v3/tokens/urls.go | 7 + .../v2/openstack/utils/base_endpoint.go | 28 + .../v2/openstack/utils/choose_version.go | 236 +++++ .../gophercloud/v2/pagination/http.go | 63 ++ .../gophercloud/v2/pagination/linked.go | 92 ++ .../gophercloud/v2/pagination/marker.go | 58 ++ .../gophercloud/v2/pagination/pager.go | 256 +++++ .../gophercloud/v2/pagination/pkg.go | 4 + .../gophercloud/v2/pagination/single.go | 33 + .../gophercloud/gophercloud/v2/params.go | 506 +++++++++ .../gophercloud/v2/provider_client.go | 583 +++++++++++ .../gophercloud/gophercloud/v2/results.go | 465 +++++++++ .../gophercloud/v2/service_client.go | 164 +++ .../gophercloud/gophercloud/v2/util.go | 109 ++ .../github.com/gophercloud/utils/v2/LICENSE | 201 ++++ .../gophercloud/utils/v2/client/client.go | 404 +++++++ .../gophercloud/utils/v2/client/doc.go | 146 +++ .../github.com/majewsky/schwift/v2/.gitignore | 4 + .../majewsky/schwift/v2/.golangci.yaml | 159 +++ .../majewsky/schwift/v2/CHANGELOG.md | 44 + .../majewsky/schwift/v2/CONTRIBUTING.md | 39 + .../majewsky/schwift/v2/LICENSE.txt | 202 ++++ .../github.com/majewsky/schwift/v2/Makefile | 99 ++ .../majewsky/schwift/v2/Makefile.maker.yaml | 17 + .../github.com/majewsky/schwift/v2/README.md | 94 ++ .../github.com/majewsky/schwift/v2/account.go | 230 ++++ .../github.com/majewsky/schwift/v2/backend.go | 48 + vendor/github.com/majewsky/schwift/v2/bulk.go | 338 ++++++ .../majewsky/schwift/v2/capabilities.go | 91 ++ .../schwift/v2/capabilities/package.go | 29 + .../majewsky/schwift/v2/container.go | 225 ++++ .../majewsky/schwift/v2/container_iterator.go | 208 ++++ vendor/github.com/majewsky/schwift/v2/doc.go | 110 ++ .../majewsky/schwift/v2/download.go | 80 ++ .../github.com/majewsky/schwift/v2/errors.go | 169 +++ .../majewsky/schwift/v2/field_metadata.go | 56 + .../majewsky/schwift/v2/field_string.go | 64 ++ .../majewsky/schwift/v2/field_time.go | 170 +++ .../majewsky/schwift/v2/field_uint64.go | 105 ++ .../majewsky/schwift/v2/generated.go | 354 +++++++ .../majewsky/schwift/v2/generated.go.in | 101 ++ .../schwift/v2/gopherschwift/package.go | 140 +++ .../github.com/majewsky/schwift/v2/headers.go | 122 +++ .../schwift/v2/internal/errext/errext.go | 26 + .../majewsky/schwift/v2/iterator.go | 170 +++ .../majewsky/schwift/v2/largeobject.go | 825 +++++++++++++++ .../github.com/majewsky/schwift/v2/object.go | 689 ++++++++++++ .../majewsky/schwift/v2/object_iterator.go | 253 +++++ .../github.com/majewsky/schwift/v2/request.go | 193 ++++ .../github.com/majewsky/schwift/v2/version.go | 22 + .../swift-http-import/pkg/actors/cleaner.go | 8 +- .../swift-http-import/pkg/objects/config.go | 24 +- .../swift-http-import/pkg/objects/debian.go | 6 +- .../swift-http-import/pkg/objects/file.go | 38 +- .../swift-http-import/pkg/objects/github.go | 16 +- .../swift-http-import/pkg/objects/source.go | 10 +- .../swift-http-import/pkg/objects/swift.go | 30 +- .../swift-http-import/pkg/objects/yum.go | 6 +- .../sapcc/swift-http-import/pkg/util/gpg.go | 14 +- vendor/golang.org/x/crypto/cast5/cast5.go | 2 +- .../x/crypto/openpgp/armor/armor.go | 5 +- .../x/crypto/openpgp/clearsign/clearsign.go | 2 +- .../x/crypto/openpgp/elgamal/elgamal.go | 2 +- .../x/crypto/openpgp/errors/errors.go | 2 +- .../x/crypto/openpgp/packet/packet.go | 2 +- vendor/golang.org/x/crypto/openpgp/read.go | 2 +- vendor/golang.org/x/crypto/openpgp/s2k/s2k.go | 2 +- vendor/golang.org/x/sys/unix/mremap.go | 5 + .../golang.org/x/sys/unix/syscall_darwin.go | 12 + vendor/golang.org/x/sys/unix/syscall_unix.go | 9 + .../x/sys/unix/zsyscall_darwin_amd64.go | 33 + .../x/sys/unix/zsyscall_darwin_amd64.s | 10 + .../x/sys/unix/zsyscall_darwin_arm64.go | 33 + .../x/sys/unix/zsyscall_darwin_arm64.s | 10 + .../x/sys/windows/security_windows.go | 24 +- .../x/sys/windows/zsyscall_windows.go | 9 + vendor/modules.txt | 40 +- 278 files changed, 15744 insertions(+), 135 deletions(-) rename vendor/github.com/google/go-github/{v62 => v63}/AUTHORS (100%) rename vendor/github.com/google/go-github/{v62 => v63}/LICENSE (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_artifacts.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_cache.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_oidc.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_permissions_enterprise.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_permissions_orgs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_required_workflows.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_runner_groups.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_runners.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_secrets.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_variables.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_workflow_jobs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_workflow_runs.go (99%) rename vendor/github.com/google/go-github/{v62 => v63}/github/actions_workflows.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/activity.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/activity_events.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/activity_notifications.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/activity_star.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/activity_watching.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/admin.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/admin_orgs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/admin_stats.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/admin_users.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/apps.go (99%) rename vendor/github.com/google/go-github/{v62 => v63}/github/apps_hooks.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/apps_hooks_deliveries.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/apps_installation.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/apps_manifest.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/apps_marketplace.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/authorizations.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/billing.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/checks.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/code-scanning.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/codesofconduct.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/codespaces.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/codespaces_secrets.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/copilot.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/dependabot.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/dependabot_alerts.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/dependabot_secrets.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/dependency_graph.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/dependency_graph_snapshots.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/doc.go (99%) rename vendor/github.com/google/go-github/{v62 => v63}/github/emojis.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/enterprise.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/enterprise_actions_runner_groups.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/enterprise_actions_runners.go (83%) rename vendor/github.com/google/go-github/{v62 => v63}/github/enterprise_audit_log.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/enterprise_code_security_and_analysis.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/event.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/event_types.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/gists.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/gists_comments.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/git.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/git_blobs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/git_commits.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/git_refs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/git_tags.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/git_trees.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/github-accessors.go (99%) rename vendor/github.com/google/go-github/{v62 => v63}/github/github.go (99%) rename vendor/github.com/google/go-github/{v62 => v63}/github/gitignore.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/interactions.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/interactions_orgs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/interactions_repos.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issue_import.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issues.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issues_assignees.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issues_comments.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issues_events.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issues_labels.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issues_milestones.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/issues_timeline.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/licenses.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/markdown.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/messages.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/meta.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/migrations.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/migrations_source_import.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/migrations_user.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_actions_allowed.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_actions_permissions.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_audit_log.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_credential_authorizations.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_custom_roles.go (58%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_hooks.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_hooks_configuration.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_hooks_deliveries.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_members.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_outside_collaborators.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_packages.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_personal_access_tokens.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_projects.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_properties.go (91%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_rules.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_security_managers.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/orgs_users_blocking.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/packages.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/projects.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/pulls.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/pulls_comments.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/pulls_reviewers.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/pulls_reviews.go (97%) rename vendor/github.com/google/go-github/{v62 => v63}/github/pulls_threads.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/rate_limit.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/reactions.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_actions_access.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_actions_allowed.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_actions_permissions.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_autolinks.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_codeowners.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_collaborators.go (99%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_comments.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_commits.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_community_health.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_contents.go (96%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_deployment_branch_policies.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_deployment_protection_rules.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_deployments.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_environments.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_forks.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_hooks.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_hooks_configuration.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_hooks_deliveries.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_invitations.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_keys.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_lfs.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_merging.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_pages.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_prereceive_hooks.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_projects.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_properties.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_releases.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_rules.go (84%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_stats.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_statuses.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_tags.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/repos_traffic.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/scim.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/search.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/secret_scanning.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/security_advisories.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/strings.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/teams.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/teams_discussion_comments.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/teams_discussions.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/teams_members.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/timestamp.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_administration.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_blocking.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_emails.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_followers.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_gpg_keys.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_keys.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_packages.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_projects.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/users_ssh_signing_keys.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/with_appengine.go (100%) rename vendor/github.com/google/go-github/{v62 => v63}/github/without_appengine.go (100%) create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/.gitignore create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/.golangci.yaml create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/CHANGELOG.md create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/LICENSE create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/Makefile create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/README.md create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/RELEASE.md create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/auth_options.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/auth_result.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/endpoint_search.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/auth_env.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/client.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/endpoint_location.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/errors.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/requests.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/urls.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/base_endpoint.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/choose_version.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/pagination/http.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/pagination/linked.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/pagination/marker.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/pagination/pager.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/pagination/pkg.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/pagination/single.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/params.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/provider_client.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/results.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/service_client.go create mode 100644 vendor/github.com/gophercloud/gophercloud/v2/util.go create mode 100644 vendor/github.com/gophercloud/utils/v2/LICENSE create mode 100644 vendor/github.com/gophercloud/utils/v2/client/client.go create mode 100644 vendor/github.com/gophercloud/utils/v2/client/doc.go create mode 100644 vendor/github.com/majewsky/schwift/v2/.gitignore create mode 100644 vendor/github.com/majewsky/schwift/v2/.golangci.yaml create mode 100644 vendor/github.com/majewsky/schwift/v2/CHANGELOG.md create mode 100644 vendor/github.com/majewsky/schwift/v2/CONTRIBUTING.md create mode 100644 vendor/github.com/majewsky/schwift/v2/LICENSE.txt create mode 100644 vendor/github.com/majewsky/schwift/v2/Makefile create mode 100644 vendor/github.com/majewsky/schwift/v2/Makefile.maker.yaml create mode 100644 vendor/github.com/majewsky/schwift/v2/README.md create mode 100644 vendor/github.com/majewsky/schwift/v2/account.go create mode 100644 vendor/github.com/majewsky/schwift/v2/backend.go create mode 100644 vendor/github.com/majewsky/schwift/v2/bulk.go create mode 100644 vendor/github.com/majewsky/schwift/v2/capabilities.go create mode 100644 vendor/github.com/majewsky/schwift/v2/capabilities/package.go create mode 100644 vendor/github.com/majewsky/schwift/v2/container.go create mode 100644 vendor/github.com/majewsky/schwift/v2/container_iterator.go create mode 100644 vendor/github.com/majewsky/schwift/v2/doc.go create mode 100644 vendor/github.com/majewsky/schwift/v2/download.go create mode 100644 vendor/github.com/majewsky/schwift/v2/errors.go create mode 100644 vendor/github.com/majewsky/schwift/v2/field_metadata.go create mode 100644 vendor/github.com/majewsky/schwift/v2/field_string.go create mode 100644 vendor/github.com/majewsky/schwift/v2/field_time.go create mode 100644 vendor/github.com/majewsky/schwift/v2/field_uint64.go create mode 100644 vendor/github.com/majewsky/schwift/v2/generated.go create mode 100644 vendor/github.com/majewsky/schwift/v2/generated.go.in create mode 100644 vendor/github.com/majewsky/schwift/v2/gopherschwift/package.go create mode 100644 vendor/github.com/majewsky/schwift/v2/headers.go create mode 100644 vendor/github.com/majewsky/schwift/v2/internal/errext/errext.go create mode 100644 vendor/github.com/majewsky/schwift/v2/iterator.go create mode 100644 vendor/github.com/majewsky/schwift/v2/largeobject.go create mode 100644 vendor/github.com/majewsky/schwift/v2/object.go create mode 100644 vendor/github.com/majewsky/schwift/v2/object_iterator.go create mode 100644 vendor/github.com/majewsky/schwift/v2/request.go create mode 100644 vendor/github.com/majewsky/schwift/v2/version.go diff --git a/go.mod b/go.mod index 6e24b7d..ff6c98b 100644 --- a/go.mod +++ b/go.mod @@ -11,12 +11,12 @@ require ( github.com/klauspost/compress v1.17.8 github.com/machinebox/progress v0.2.0 github.com/majewsky/schwift v1.3.0 - github.com/sapcc/go-bits v0.0.0-20240530080859-f4579fb3a074 - github.com/sapcc/swift-http-import v0.0.0-20240605095849-0db239238e71 + github.com/sapcc/go-bits v0.0.0-20240709125621-b1e90ef040ad + github.com/sapcc/swift-http-import v0.0.0-20240712085221-f33ec7ac8e34 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.19.0 github.com/xhit/go-str2duration/v2 v2.1.0 - golang.org/x/term v0.21.0 + golang.org/x/term v0.22.0 ) require ( @@ -24,19 +24,23 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/go-github/v61 v61.0.0 // indirect github.com/google/go-github/v62 v62.0.0 // indirect + github.com/google/go-github/v63 v63.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/gophercloud/gophercloud/v2 v2.0.0 // indirect + github.com/gophercloud/utils/v2 v2.0.0-20240705071316-780b64d153e3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/majewsky/schwift/v2 v2.0.0 // indirect github.com/matryer/is v1.4.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sapcc/go-api-declarations v1.11.2 // indirect + github.com/sapcc/go-api-declarations v1.11.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -45,11 +49,11 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index dc12bf8..8066bca 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5p github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY= github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= +github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE= +github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -25,8 +27,12 @@ github.com/gophercloud/gophercloud v1.11.0 h1:ls0O747DIq1D8SUHc7r2vI8BFbMLeLFuEN github.com/gophercloud/gophercloud v1.11.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.12.0 h1:Jrz16vPAL93l80q16fp8NplrTCp93y7rZh2P3Q4Yq7g= github.com/gophercloud/gophercloud v1.12.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +github.com/gophercloud/gophercloud/v2 v2.0.0 h1:iH0x0Ji79a/ULzmq95fvOBAyie7+M+wUAEu+JrRMsCk= +github.com/gophercloud/gophercloud/v2 v2.0.0/go.mod h1:ZKbcGNjxFTSaP5wlvtLDdsppllD/UGGvXBPqcjeqA8Y= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto= +github.com/gophercloud/utils/v2 v2.0.0-20240705071316-780b64d153e3 h1:Jakaq9ALfVGOGC8w0eTS2FMa+yG8bMbM1lDhkFpEDXA= +github.com/gophercloud/utils/v2 v2.0.0-20240705071316-780b64d153e3/go.mod h1:SlglU6S3SCTU9qjapk7XjPufH/VhHXi+nKl8fuoLUoc= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -48,6 +54,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/majewsky/schwift v1.3.0 h1:00kcDohEoNv8znTpmA53pnFSPs3eb1Lsokk/beln3nA= github.com/majewsky/schwift v1.3.0/go.mod h1:MZiCKCKKEhUxK+1+6QNWVtmckuMFeFz3PnalcR6WUEw= +github.com/majewsky/schwift/v2 v2.0.0 h1:Rgzv/18yMAej3bBZWoxYmS2lZMiCKD6P451dU8TyQtE= +github.com/majewsky/schwift/v2 v2.0.0/go.mod h1:qqN4N7s2+jhwebBgxFZm/e2NOqDzNphwb7SnAIB5G+4= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -70,16 +78,22 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sapcc/go-api-declarations v1.11.2 h1:CFVxgWqrs4WBJhbA0nJNQ/0mBpAxmNFuUZtuEITltFU= github.com/sapcc/go-api-declarations v1.11.2/go.mod h1:83R3hTANhuRXt/pXDby37IJetw8l7DG41s33Tp9NXxI= +github.com/sapcc/go-api-declarations v1.11.3 h1:A8JgeSmOdziYXuiOes9Lp3LKZ0FsU2lc9FOxoM3kRR0= +github.com/sapcc/go-api-declarations v1.11.3/go.mod h1:83R3hTANhuRXt/pXDby37IJetw8l7DG41s33Tp9NXxI= github.com/sapcc/go-bits v0.0.0-20240510133533-f59e074e0a64 h1:WUFe1LWMAEeoFLaiaDE8OFuPp9guOhjGfOyUuFUTR+U= github.com/sapcc/go-bits v0.0.0-20240510133533-f59e074e0a64/go.mod h1:Y7GPFH7hi5btdchBtmw/T0kSNBq1ingJl7SM/aOc9S8= github.com/sapcc/go-bits v0.0.0-20240530080859-f4579fb3a074 h1:ldl0MVD/P5fhEygDiF+gc3FzomWqt/DQvL4iS98ho7o= github.com/sapcc/go-bits v0.0.0-20240530080859-f4579fb3a074/go.mod h1:NHZtb0ZLLXY3FCvmjuGmjG4n/PTys6zP604qS/tl5dQ= +github.com/sapcc/go-bits v0.0.0-20240709125621-b1e90ef040ad h1:e0kDKCEhohs+oKwYRRAibCJdqd2DvLwWrK17yELxUpY= +github.com/sapcc/go-bits v0.0.0-20240709125621-b1e90ef040ad/go.mod h1:d9JN0Gm8lF5jUMQeH/3MS5iNhs6/AlR/wVQ8vxRAGmo= github.com/sapcc/swift-http-import v0.0.0-20240510131904-46e49858d896 h1:0W096L0JYpC+QNLS2Gepi+l45uXdx4vFvODKdFmH8+E= github.com/sapcc/swift-http-import v0.0.0-20240510131904-46e49858d896/go.mod h1:A7DpoYex1ddwc6uEtPKW867SEz+BDX55z/NLYxcTAqQ= github.com/sapcc/swift-http-import v0.0.0-20240531043308-875f6bfa1088 h1:60oFdpkZuo/yz2vhbokI7SRYfKx44ZRlYboB9C3FjzU= github.com/sapcc/swift-http-import v0.0.0-20240531043308-875f6bfa1088/go.mod h1:0cLCmgdLaOdnh2TgW//hK+DD8/WotiDhFqBTPPW7afs= github.com/sapcc/swift-http-import v0.0.0-20240605095849-0db239238e71 h1:1fTx+yJG9ZPZDYtTrNw7AodxDb3CiBaTpoNHu8UizL4= github.com/sapcc/swift-http-import v0.0.0-20240605095849-0db239238e71/go.mod h1:rEoiaGlVvCPuImSBbsC919QTzmetKSKoCoCzNaNIDpY= +github.com/sapcc/swift-http-import v0.0.0-20240712085221-f33ec7ac8e34 h1:DR4hRouMC7PvRwHjxe5HfQVFAhBSlJj6l3V8d95PbcE= +github.com/sapcc/swift-http-import v0.0.0-20240712085221-f33ec7ac8e34/go.mod h1:7purlezg80hcLAM18jTNLc1RGBXno6MZ3DOmj9wJSww= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -123,6 +137,8 @@ golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -134,6 +150,8 @@ golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= @@ -151,12 +169,16 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/vendor/github.com/google/go-github/v62/AUTHORS b/vendor/github.com/google/go-github/v63/AUTHORS similarity index 100% rename from vendor/github.com/google/go-github/v62/AUTHORS rename to vendor/github.com/google/go-github/v63/AUTHORS diff --git a/vendor/github.com/google/go-github/v62/LICENSE b/vendor/github.com/google/go-github/v63/LICENSE similarity index 100% rename from vendor/github.com/google/go-github/v62/LICENSE rename to vendor/github.com/google/go-github/v63/LICENSE diff --git a/vendor/github.com/google/go-github/v62/github/actions.go b/vendor/github.com/google/go-github/v63/github/actions.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions.go rename to vendor/github.com/google/go-github/v63/github/actions.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_artifacts.go b/vendor/github.com/google/go-github/v63/github/actions_artifacts.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_artifacts.go rename to vendor/github.com/google/go-github/v63/github/actions_artifacts.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_cache.go b/vendor/github.com/google/go-github/v63/github/actions_cache.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_cache.go rename to vendor/github.com/google/go-github/v63/github/actions_cache.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_oidc.go b/vendor/github.com/google/go-github/v63/github/actions_oidc.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_oidc.go rename to vendor/github.com/google/go-github/v63/github/actions_oidc.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_permissions_enterprise.go b/vendor/github.com/google/go-github/v63/github/actions_permissions_enterprise.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_permissions_enterprise.go rename to vendor/github.com/google/go-github/v63/github/actions_permissions_enterprise.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_permissions_orgs.go b/vendor/github.com/google/go-github/v63/github/actions_permissions_orgs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_permissions_orgs.go rename to vendor/github.com/google/go-github/v63/github/actions_permissions_orgs.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_required_workflows.go b/vendor/github.com/google/go-github/v63/github/actions_required_workflows.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_required_workflows.go rename to vendor/github.com/google/go-github/v63/github/actions_required_workflows.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_runner_groups.go b/vendor/github.com/google/go-github/v63/github/actions_runner_groups.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_runner_groups.go rename to vendor/github.com/google/go-github/v63/github/actions_runner_groups.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_runners.go b/vendor/github.com/google/go-github/v63/github/actions_runners.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_runners.go rename to vendor/github.com/google/go-github/v63/github/actions_runners.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_secrets.go b/vendor/github.com/google/go-github/v63/github/actions_secrets.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_secrets.go rename to vendor/github.com/google/go-github/v63/github/actions_secrets.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_variables.go b/vendor/github.com/google/go-github/v63/github/actions_variables.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_variables.go rename to vendor/github.com/google/go-github/v63/github/actions_variables.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_workflow_jobs.go b/vendor/github.com/google/go-github/v63/github/actions_workflow_jobs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_workflow_jobs.go rename to vendor/github.com/google/go-github/v63/github/actions_workflow_jobs.go diff --git a/vendor/github.com/google/go-github/v62/github/actions_workflow_runs.go b/vendor/github.com/google/go-github/v63/github/actions_workflow_runs.go similarity index 99% rename from vendor/github.com/google/go-github/v62/github/actions_workflow_runs.go rename to vendor/github.com/google/go-github/v63/github/actions_workflow_runs.go index bc7afe9..d55c01d 100644 --- a/vendor/github.com/google/go-github/v62/github/actions_workflow_runs.go +++ b/vendor/github.com/google/go-github/v63/github/actions_workflow_runs.go @@ -19,6 +19,7 @@ type WorkflowRun struct { NodeID *string `json:"node_id,omitempty"` HeadBranch *string `json:"head_branch,omitempty"` HeadSHA *string `json:"head_sha,omitempty"` + Path *string `json:"path,omitempty"` RunNumber *int `json:"run_number,omitempty"` RunAttempt *int `json:"run_attempt,omitempty"` Event *string `json:"event,omitempty"` diff --git a/vendor/github.com/google/go-github/v62/github/actions_workflows.go b/vendor/github.com/google/go-github/v63/github/actions_workflows.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/actions_workflows.go rename to vendor/github.com/google/go-github/v63/github/actions_workflows.go diff --git a/vendor/github.com/google/go-github/v62/github/activity.go b/vendor/github.com/google/go-github/v63/github/activity.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/activity.go rename to vendor/github.com/google/go-github/v63/github/activity.go diff --git a/vendor/github.com/google/go-github/v62/github/activity_events.go b/vendor/github.com/google/go-github/v63/github/activity_events.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/activity_events.go rename to vendor/github.com/google/go-github/v63/github/activity_events.go diff --git a/vendor/github.com/google/go-github/v62/github/activity_notifications.go b/vendor/github.com/google/go-github/v63/github/activity_notifications.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/activity_notifications.go rename to vendor/github.com/google/go-github/v63/github/activity_notifications.go diff --git a/vendor/github.com/google/go-github/v62/github/activity_star.go b/vendor/github.com/google/go-github/v63/github/activity_star.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/activity_star.go rename to vendor/github.com/google/go-github/v63/github/activity_star.go diff --git a/vendor/github.com/google/go-github/v62/github/activity_watching.go b/vendor/github.com/google/go-github/v63/github/activity_watching.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/activity_watching.go rename to vendor/github.com/google/go-github/v63/github/activity_watching.go diff --git a/vendor/github.com/google/go-github/v62/github/admin.go b/vendor/github.com/google/go-github/v63/github/admin.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/admin.go rename to vendor/github.com/google/go-github/v63/github/admin.go diff --git a/vendor/github.com/google/go-github/v62/github/admin_orgs.go b/vendor/github.com/google/go-github/v63/github/admin_orgs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/admin_orgs.go rename to vendor/github.com/google/go-github/v63/github/admin_orgs.go diff --git a/vendor/github.com/google/go-github/v62/github/admin_stats.go b/vendor/github.com/google/go-github/v63/github/admin_stats.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/admin_stats.go rename to vendor/github.com/google/go-github/v63/github/admin_stats.go diff --git a/vendor/github.com/google/go-github/v62/github/admin_users.go b/vendor/github.com/google/go-github/v63/github/admin_users.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/admin_users.go rename to vendor/github.com/google/go-github/v63/github/admin_users.go diff --git a/vendor/github.com/google/go-github/v62/github/apps.go b/vendor/github.com/google/go-github/v63/github/apps.go similarity index 99% rename from vendor/github.com/google/go-github/v62/github/apps.go rename to vendor/github.com/google/go-github/v63/github/apps.go index 6969dab..16ef758 100644 --- a/vendor/github.com/google/go-github/v62/github/apps.go +++ b/vendor/github.com/google/go-github/v63/github/apps.go @@ -78,6 +78,7 @@ type InstallationTokenListRepoOptions struct { // https://docs.github.com/rest/apps#create-an-installation-access-token-for-an-app type InstallationPermissions struct { Actions *string `json:"actions,omitempty"` + ActionsVariables *string `json:"actions_variables,omitempty"` Administration *string `json:"administration,omitempty"` Blocking *string `json:"blocking,omitempty"` Checks *string `json:"checks,omitempty"` @@ -93,6 +94,7 @@ type InstallationPermissions struct { OrganizationAdministration *string `json:"organization_administration,omitempty"` OrganizationCustomProperties *string `json:"organization_custom_properties,omitempty"` OrganizationCustomRoles *string `json:"organization_custom_roles,omitempty"` + OrganizationCustomOrgRoles *string `json:"organization_custom_org_roles,omitempty"` OrganizationHooks *string `json:"organization_hooks,omitempty"` OrganizationPackages *string `json:"organization_packages,omitempty"` OrganizationPlan *string `json:"organization_plan,omitempty"` diff --git a/vendor/github.com/google/go-github/v62/github/apps_hooks.go b/vendor/github.com/google/go-github/v63/github/apps_hooks.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/apps_hooks.go rename to vendor/github.com/google/go-github/v63/github/apps_hooks.go diff --git a/vendor/github.com/google/go-github/v62/github/apps_hooks_deliveries.go b/vendor/github.com/google/go-github/v63/github/apps_hooks_deliveries.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/apps_hooks_deliveries.go rename to vendor/github.com/google/go-github/v63/github/apps_hooks_deliveries.go diff --git a/vendor/github.com/google/go-github/v62/github/apps_installation.go b/vendor/github.com/google/go-github/v63/github/apps_installation.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/apps_installation.go rename to vendor/github.com/google/go-github/v63/github/apps_installation.go diff --git a/vendor/github.com/google/go-github/v62/github/apps_manifest.go b/vendor/github.com/google/go-github/v63/github/apps_manifest.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/apps_manifest.go rename to vendor/github.com/google/go-github/v63/github/apps_manifest.go diff --git a/vendor/github.com/google/go-github/v62/github/apps_marketplace.go b/vendor/github.com/google/go-github/v63/github/apps_marketplace.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/apps_marketplace.go rename to vendor/github.com/google/go-github/v63/github/apps_marketplace.go diff --git a/vendor/github.com/google/go-github/v62/github/authorizations.go b/vendor/github.com/google/go-github/v63/github/authorizations.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/authorizations.go rename to vendor/github.com/google/go-github/v63/github/authorizations.go diff --git a/vendor/github.com/google/go-github/v62/github/billing.go b/vendor/github.com/google/go-github/v63/github/billing.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/billing.go rename to vendor/github.com/google/go-github/v63/github/billing.go diff --git a/vendor/github.com/google/go-github/v62/github/checks.go b/vendor/github.com/google/go-github/v63/github/checks.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/checks.go rename to vendor/github.com/google/go-github/v63/github/checks.go diff --git a/vendor/github.com/google/go-github/v62/github/code-scanning.go b/vendor/github.com/google/go-github/v63/github/code-scanning.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/code-scanning.go rename to vendor/github.com/google/go-github/v63/github/code-scanning.go diff --git a/vendor/github.com/google/go-github/v62/github/codesofconduct.go b/vendor/github.com/google/go-github/v63/github/codesofconduct.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/codesofconduct.go rename to vendor/github.com/google/go-github/v63/github/codesofconduct.go diff --git a/vendor/github.com/google/go-github/v62/github/codespaces.go b/vendor/github.com/google/go-github/v63/github/codespaces.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/codespaces.go rename to vendor/github.com/google/go-github/v63/github/codespaces.go diff --git a/vendor/github.com/google/go-github/v62/github/codespaces_secrets.go b/vendor/github.com/google/go-github/v63/github/codespaces_secrets.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/codespaces_secrets.go rename to vendor/github.com/google/go-github/v63/github/codespaces_secrets.go diff --git a/vendor/github.com/google/go-github/v62/github/copilot.go b/vendor/github.com/google/go-github/v63/github/copilot.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/copilot.go rename to vendor/github.com/google/go-github/v63/github/copilot.go diff --git a/vendor/github.com/google/go-github/v62/github/dependabot.go b/vendor/github.com/google/go-github/v63/github/dependabot.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/dependabot.go rename to vendor/github.com/google/go-github/v63/github/dependabot.go diff --git a/vendor/github.com/google/go-github/v62/github/dependabot_alerts.go b/vendor/github.com/google/go-github/v63/github/dependabot_alerts.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/dependabot_alerts.go rename to vendor/github.com/google/go-github/v63/github/dependabot_alerts.go diff --git a/vendor/github.com/google/go-github/v62/github/dependabot_secrets.go b/vendor/github.com/google/go-github/v63/github/dependabot_secrets.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/dependabot_secrets.go rename to vendor/github.com/google/go-github/v63/github/dependabot_secrets.go diff --git a/vendor/github.com/google/go-github/v62/github/dependency_graph.go b/vendor/github.com/google/go-github/v63/github/dependency_graph.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/dependency_graph.go rename to vendor/github.com/google/go-github/v63/github/dependency_graph.go diff --git a/vendor/github.com/google/go-github/v62/github/dependency_graph_snapshots.go b/vendor/github.com/google/go-github/v63/github/dependency_graph_snapshots.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/dependency_graph_snapshots.go rename to vendor/github.com/google/go-github/v63/github/dependency_graph_snapshots.go diff --git a/vendor/github.com/google/go-github/v62/github/doc.go b/vendor/github.com/google/go-github/v63/github/doc.go similarity index 99% rename from vendor/github.com/google/go-github/v62/github/doc.go rename to vendor/github.com/google/go-github/v63/github/doc.go index e0c8108..1781a6a 100644 --- a/vendor/github.com/google/go-github/v62/github/doc.go +++ b/vendor/github.com/google/go-github/v63/github/doc.go @@ -8,7 +8,7 @@ Package github provides a client for using the GitHub API. Usage: - import "github.com/google/go-github/v62/github" // with go modules enabled (GO111MODULE=on or outside GOPATH) + import "github.com/google/go-github/v63/github" // with go modules enabled (GO111MODULE=on or outside GOPATH) import "github.com/google/go-github/github" // with go modules disabled Construct a new GitHub client, then use the various services on the client to diff --git a/vendor/github.com/google/go-github/v62/github/emojis.go b/vendor/github.com/google/go-github/v63/github/emojis.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/emojis.go rename to vendor/github.com/google/go-github/v63/github/emojis.go diff --git a/vendor/github.com/google/go-github/v62/github/enterprise.go b/vendor/github.com/google/go-github/v63/github/enterprise.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/enterprise.go rename to vendor/github.com/google/go-github/v63/github/enterprise.go diff --git a/vendor/github.com/google/go-github/v62/github/enterprise_actions_runner_groups.go b/vendor/github.com/google/go-github/v63/github/enterprise_actions_runner_groups.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/enterprise_actions_runner_groups.go rename to vendor/github.com/google/go-github/v63/github/enterprise_actions_runner_groups.go diff --git a/vendor/github.com/google/go-github/v62/github/enterprise_actions_runners.go b/vendor/github.com/google/go-github/v63/github/enterprise_actions_runners.go similarity index 83% rename from vendor/github.com/google/go-github/v62/github/enterprise_actions_runners.go rename to vendor/github.com/google/go-github/v63/github/enterprise_actions_runners.go index 4a6e6b5..fa345ae 100644 --- a/vendor/github.com/google/go-github/v62/github/enterprise_actions_runners.go +++ b/vendor/github.com/google/go-github/v63/github/enterprise_actions_runners.go @@ -80,7 +80,7 @@ func (s *EnterpriseService) CreateRegistrationToken(ctx context.Context, enterpr // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/actions/self-hosted-runners#list-self-hosted-runners-for-an-enterprise // //meta:operation GET /enterprises/{enterprise}/actions/runners -func (s *EnterpriseService) ListRunners(ctx context.Context, enterprise string, opts *ListOptions) (*Runners, *Response, error) { +func (s *EnterpriseService) ListRunners(ctx context.Context, enterprise string, opts *ListRunnersOptions) (*Runners, *Response, error) { u := fmt.Sprintf("enterprises/%v/actions/runners", enterprise) u, err := addOptions(u, opts) if err != nil { @@ -101,6 +101,27 @@ func (s *EnterpriseService) ListRunners(ctx context.Context, enterprise string, return runners, resp, nil } +// GetRunner gets a specific self-hosted runner configured in an enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/actions/self-hosted-runners#get-a-self-hosted-runner-for-an-enterprise +// +//meta:operation GET /enterprises/{enterprise}/actions/runners/{runner_id} +func (s *EnterpriseService) GetRunner(ctx context.Context, enterprise string, runnerID int64) (*Runner, *Response, error) { + u := fmt.Sprintf("enterprises/%v/actions/runners/%v", enterprise, runnerID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + runner := new(Runner) + resp, err := s.client.Do(ctx, req, runner) + if err != nil { + return nil, resp, err + } + + return runner, resp, nil +} + // RemoveRunner forces the removal of a self-hosted runner from an enterprise using the runner id. // // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/actions/self-hosted-runners#delete-a-self-hosted-runner-from-an-enterprise diff --git a/vendor/github.com/google/go-github/v62/github/enterprise_audit_log.go b/vendor/github.com/google/go-github/v63/github/enterprise_audit_log.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/enterprise_audit_log.go rename to vendor/github.com/google/go-github/v63/github/enterprise_audit_log.go diff --git a/vendor/github.com/google/go-github/v62/github/enterprise_code_security_and_analysis.go b/vendor/github.com/google/go-github/v63/github/enterprise_code_security_and_analysis.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/enterprise_code_security_and_analysis.go rename to vendor/github.com/google/go-github/v63/github/enterprise_code_security_and_analysis.go diff --git a/vendor/github.com/google/go-github/v62/github/event.go b/vendor/github.com/google/go-github/v63/github/event.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/event.go rename to vendor/github.com/google/go-github/v63/github/event.go diff --git a/vendor/github.com/google/go-github/v62/github/event_types.go b/vendor/github.com/google/go-github/v63/github/event_types.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/event_types.go rename to vendor/github.com/google/go-github/v63/github/event_types.go diff --git a/vendor/github.com/google/go-github/v62/github/gists.go b/vendor/github.com/google/go-github/v63/github/gists.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/gists.go rename to vendor/github.com/google/go-github/v63/github/gists.go diff --git a/vendor/github.com/google/go-github/v62/github/gists_comments.go b/vendor/github.com/google/go-github/v63/github/gists_comments.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/gists_comments.go rename to vendor/github.com/google/go-github/v63/github/gists_comments.go diff --git a/vendor/github.com/google/go-github/v62/github/git.go b/vendor/github.com/google/go-github/v63/github/git.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/git.go rename to vendor/github.com/google/go-github/v63/github/git.go diff --git a/vendor/github.com/google/go-github/v62/github/git_blobs.go b/vendor/github.com/google/go-github/v63/github/git_blobs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/git_blobs.go rename to vendor/github.com/google/go-github/v63/github/git_blobs.go diff --git a/vendor/github.com/google/go-github/v62/github/git_commits.go b/vendor/github.com/google/go-github/v63/github/git_commits.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/git_commits.go rename to vendor/github.com/google/go-github/v63/github/git_commits.go diff --git a/vendor/github.com/google/go-github/v62/github/git_refs.go b/vendor/github.com/google/go-github/v63/github/git_refs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/git_refs.go rename to vendor/github.com/google/go-github/v63/github/git_refs.go diff --git a/vendor/github.com/google/go-github/v62/github/git_tags.go b/vendor/github.com/google/go-github/v63/github/git_tags.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/git_tags.go rename to vendor/github.com/google/go-github/v63/github/git_tags.go diff --git a/vendor/github.com/google/go-github/v62/github/git_trees.go b/vendor/github.com/google/go-github/v63/github/git_trees.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/git_trees.go rename to vendor/github.com/google/go-github/v63/github/git_trees.go diff --git a/vendor/github.com/google/go-github/v62/github/github-accessors.go b/vendor/github.com/google/go-github/v63/github/github-accessors.go similarity index 99% rename from vendor/github.com/google/go-github/v62/github/github-accessors.go rename to vendor/github.com/google/go-github/v63/github/github-accessors.go index fd2fe22..5118791 100644 --- a/vendor/github.com/google/go-github/v62/github/github-accessors.go +++ b/vendor/github.com/google/go-github/v63/github/github-accessors.go @@ -4351,7 +4351,7 @@ func (c *CreateOrgInvitationOptions) GetRole() string { } // GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise. -func (c *CreateOrUpdateCustomRoleOptions) GetBaseRole() string { +func (c *CreateOrUpdateCustomRepoRoleOptions) GetBaseRole() string { if c == nil || c.BaseRole == nil { return "" } @@ -4359,7 +4359,7 @@ func (c *CreateOrUpdateCustomRoleOptions) GetBaseRole() string { } // GetDescription returns the Description field if it's non-nil, zero value otherwise. -func (c *CreateOrUpdateCustomRoleOptions) GetDescription() string { +func (c *CreateOrUpdateCustomRepoRoleOptions) GetDescription() string { if c == nil || c.Description == nil { return "" } @@ -4367,7 +4367,23 @@ func (c *CreateOrUpdateCustomRoleOptions) GetDescription() string { } // GetName returns the Name field if it's non-nil, zero value otherwise. -func (c *CreateOrUpdateCustomRoleOptions) GetName() string { +func (c *CreateOrUpdateCustomRepoRoleOptions) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateOrgRoleOptions) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateOrgRoleOptions) GetName() string { if c == nil || c.Name == nil { return "" } @@ -4686,6 +4702,30 @@ func (c *CustomDeploymentProtectionRuleRequest) GetIntegrationID() int64 { return *c.IntegrationID } +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CustomOrgRoles) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (c *CustomOrgRoles) GetID() int64 { + if c == nil || c.ID == nil { + return 0 + } + return *c.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CustomOrgRoles) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + // GetDefaultValue returns the DefaultValue field if it's non-nil, zero value otherwise. func (c *CustomProperty) GetDefaultValue() string { if c == nil || c.DefaultValue == nil { @@ -4718,6 +4758,14 @@ func (c *CustomProperty) GetRequired() bool { return *c.Required } +// GetValuesEditableBy returns the ValuesEditableBy field if it's non-nil, zero value otherwise. +func (c *CustomProperty) GetValuesEditableBy() string { + if c == nil || c.ValuesEditableBy == nil { + return "" + } + return *c.ValuesEditableBy +} + // GetValue returns the Value field if it's non-nil, zero value otherwise. func (c *CustomPropertyValue) GetValue() string { if c == nil || c.Value == nil { @@ -6502,6 +6550,22 @@ func (d *DraftReviewComment) GetBody() string { return *d.Body } +// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise. +func (d *DraftReviewComment) GetCommitID() string { + if d == nil || d.CommitID == nil { + return "" + } + return *d.CommitID +} + +// GetInReplyTo returns the InReplyTo field if it's non-nil, zero value otherwise. +func (d *DraftReviewComment) GetInReplyTo() int64 { + if d == nil || d.InReplyTo == nil { + return 0 + } + return *d.InReplyTo +} + // GetLine returns the Line field if it's non-nil, zero value otherwise. func (d *DraftReviewComment) GetLine() int { if d == nil || d.Line == nil { @@ -6550,6 +6614,14 @@ func (d *DraftReviewComment) GetStartSide() string { return *d.StartSide } +// GetSubjectType returns the SubjectType field if it's non-nil, zero value otherwise. +func (d *DraftReviewComment) GetSubjectType() string { + if d == nil || d.SubjectType == nil { + return "" + } + return *d.SubjectType +} + // GetRef returns the Ref field. func (e *EditBase) GetRef() *EditRef { if e == nil { @@ -8750,6 +8822,14 @@ func (i *InstallationPermissions) GetActions() string { return *i.Actions } +// GetActionsVariables returns the ActionsVariables field if it's non-nil, zero value otherwise. +func (i *InstallationPermissions) GetActionsVariables() string { + if i == nil || i.ActionsVariables == nil { + return "" + } + return *i.ActionsVariables +} + // GetAdministration returns the Administration field if it's non-nil, zero value otherwise. func (i *InstallationPermissions) GetAdministration() string { if i == nil || i.Administration == nil { @@ -8854,6 +8934,14 @@ func (i *InstallationPermissions) GetOrganizationAdministration() string { return *i.OrganizationAdministration } +// GetOrganizationCustomOrgRoles returns the OrganizationCustomOrgRoles field if it's non-nil, zero value otherwise. +func (i *InstallationPermissions) GetOrganizationCustomOrgRoles() string { + if i == nil || i.OrganizationCustomOrgRoles == nil { + return "" + } + return *i.OrganizationCustomOrgRoles +} + // GetOrganizationCustomProperties returns the OrganizationCustomProperties field if it's non-nil, zero value otherwise. func (i *InstallationPermissions) GetOrganizationCustomProperties() string { if i == nil || i.OrganizationCustomProperties == nil { @@ -12838,6 +12926,14 @@ func (o *OrganizationCustomRepoRoles) GetTotalCount() int { return *o.TotalCount } +// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. +func (o *OrganizationCustomRoles) GetTotalCount() int { + if o == nil || o.TotalCount == nil { + return 0 + } + return *o.TotalCount +} + // GetAction returns the Action field if it's non-nil, zero value otherwise. func (o *OrganizationEvent) GetAction() string { if o == nil || o.Action == nil { @@ -19998,6 +20094,14 @@ func (r *RepositoryPermissionLevel) GetPermission() string { return *r.Permission } +// GetRoleName returns the RoleName field if it's non-nil, zero value otherwise. +func (r *RepositoryPermissionLevel) GetRoleName() string { + if r == nil || r.RoleName == nil { + return "" + } + return *r.RoleName +} + // GetUser returns the User field. func (r *RepositoryPermissionLevel) GetUser() *User { if r == nil { @@ -20774,6 +20878,14 @@ func (r *RulesetConditions) GetRepositoryName() *RulesetRepositoryNamesCondition return r.RepositoryName } +// GetRepositoryProperty returns the RepositoryProperty field. +func (r *RulesetConditions) GetRepositoryProperty() *RulesetRepositoryPropertyConditionParameters { + if r == nil { + return nil + } + return r.RepositoryProperty +} + // GetHRef returns the HRef field if it's non-nil, zero value otherwise. func (r *RulesetLink) GetHRef() string { if r == nil || r.HRef == nil { @@ -25246,6 +25358,14 @@ func (w *WorkflowRun) GetNodeID() string { return *w.NodeID } +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (w *WorkflowRun) GetPath() string { + if w == nil || w.Path == nil { + return "" + } + return *w.Path +} + // GetPreviousAttemptURL returns the PreviousAttemptURL field if it's non-nil, zero value otherwise. func (w *WorkflowRun) GetPreviousAttemptURL() string { if w == nil || w.PreviousAttemptURL == nil { diff --git a/vendor/github.com/google/go-github/v62/github/github.go b/vendor/github.com/google/go-github/v63/github/github.go similarity index 99% rename from vendor/github.com/google/go-github/v62/github/github.go rename to vendor/github.com/google/go-github/v63/github/github.go index 28a3c10..cd0c013 100644 --- a/vendor/github.com/google/go-github/v62/github/github.go +++ b/vendor/github.com/google/go-github/v63/github/github.go @@ -28,7 +28,7 @@ import ( ) const ( - Version = "v62.0.0" + Version = "v63.0.0" defaultAPIVersion = "2022-11-28" defaultBaseURL = "https://api.github.com/" diff --git a/vendor/github.com/google/go-github/v62/github/gitignore.go b/vendor/github.com/google/go-github/v63/github/gitignore.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/gitignore.go rename to vendor/github.com/google/go-github/v63/github/gitignore.go diff --git a/vendor/github.com/google/go-github/v62/github/interactions.go b/vendor/github.com/google/go-github/v63/github/interactions.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/interactions.go rename to vendor/github.com/google/go-github/v63/github/interactions.go diff --git a/vendor/github.com/google/go-github/v62/github/interactions_orgs.go b/vendor/github.com/google/go-github/v63/github/interactions_orgs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/interactions_orgs.go rename to vendor/github.com/google/go-github/v63/github/interactions_orgs.go diff --git a/vendor/github.com/google/go-github/v62/github/interactions_repos.go b/vendor/github.com/google/go-github/v63/github/interactions_repos.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/interactions_repos.go rename to vendor/github.com/google/go-github/v63/github/interactions_repos.go diff --git a/vendor/github.com/google/go-github/v62/github/issue_import.go b/vendor/github.com/google/go-github/v63/github/issue_import.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issue_import.go rename to vendor/github.com/google/go-github/v63/github/issue_import.go diff --git a/vendor/github.com/google/go-github/v62/github/issues.go b/vendor/github.com/google/go-github/v63/github/issues.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issues.go rename to vendor/github.com/google/go-github/v63/github/issues.go diff --git a/vendor/github.com/google/go-github/v62/github/issues_assignees.go b/vendor/github.com/google/go-github/v63/github/issues_assignees.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issues_assignees.go rename to vendor/github.com/google/go-github/v63/github/issues_assignees.go diff --git a/vendor/github.com/google/go-github/v62/github/issues_comments.go b/vendor/github.com/google/go-github/v63/github/issues_comments.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issues_comments.go rename to vendor/github.com/google/go-github/v63/github/issues_comments.go diff --git a/vendor/github.com/google/go-github/v62/github/issues_events.go b/vendor/github.com/google/go-github/v63/github/issues_events.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issues_events.go rename to vendor/github.com/google/go-github/v63/github/issues_events.go diff --git a/vendor/github.com/google/go-github/v62/github/issues_labels.go b/vendor/github.com/google/go-github/v63/github/issues_labels.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issues_labels.go rename to vendor/github.com/google/go-github/v63/github/issues_labels.go diff --git a/vendor/github.com/google/go-github/v62/github/issues_milestones.go b/vendor/github.com/google/go-github/v63/github/issues_milestones.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issues_milestones.go rename to vendor/github.com/google/go-github/v63/github/issues_milestones.go diff --git a/vendor/github.com/google/go-github/v62/github/issues_timeline.go b/vendor/github.com/google/go-github/v63/github/issues_timeline.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/issues_timeline.go rename to vendor/github.com/google/go-github/v63/github/issues_timeline.go diff --git a/vendor/github.com/google/go-github/v62/github/licenses.go b/vendor/github.com/google/go-github/v63/github/licenses.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/licenses.go rename to vendor/github.com/google/go-github/v63/github/licenses.go diff --git a/vendor/github.com/google/go-github/v62/github/markdown.go b/vendor/github.com/google/go-github/v63/github/markdown.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/markdown.go rename to vendor/github.com/google/go-github/v63/github/markdown.go diff --git a/vendor/github.com/google/go-github/v62/github/messages.go b/vendor/github.com/google/go-github/v63/github/messages.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/messages.go rename to vendor/github.com/google/go-github/v63/github/messages.go diff --git a/vendor/github.com/google/go-github/v62/github/meta.go b/vendor/github.com/google/go-github/v63/github/meta.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/meta.go rename to vendor/github.com/google/go-github/v63/github/meta.go diff --git a/vendor/github.com/google/go-github/v62/github/migrations.go b/vendor/github.com/google/go-github/v63/github/migrations.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/migrations.go rename to vendor/github.com/google/go-github/v63/github/migrations.go diff --git a/vendor/github.com/google/go-github/v62/github/migrations_source_import.go b/vendor/github.com/google/go-github/v63/github/migrations_source_import.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/migrations_source_import.go rename to vendor/github.com/google/go-github/v63/github/migrations_source_import.go diff --git a/vendor/github.com/google/go-github/v62/github/migrations_user.go b/vendor/github.com/google/go-github/v63/github/migrations_user.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/migrations_user.go rename to vendor/github.com/google/go-github/v63/github/migrations_user.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs.go b/vendor/github.com/google/go-github/v63/github/orgs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs.go rename to vendor/github.com/google/go-github/v63/github/orgs.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_actions_allowed.go b/vendor/github.com/google/go-github/v63/github/orgs_actions_allowed.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_actions_allowed.go rename to vendor/github.com/google/go-github/v63/github/orgs_actions_allowed.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_actions_permissions.go b/vendor/github.com/google/go-github/v63/github/orgs_actions_permissions.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_actions_permissions.go rename to vendor/github.com/google/go-github/v63/github/orgs_actions_permissions.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_audit_log.go b/vendor/github.com/google/go-github/v63/github/orgs_audit_log.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_audit_log.go rename to vendor/github.com/google/go-github/v63/github/orgs_audit_log.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_credential_authorizations.go b/vendor/github.com/google/go-github/v63/github/orgs_credential_authorizations.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_credential_authorizations.go rename to vendor/github.com/google/go-github/v63/github/orgs_credential_authorizations.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_custom_roles.go b/vendor/github.com/google/go-github/v63/github/orgs_custom_roles.go similarity index 58% rename from vendor/github.com/google/go-github/v62/github/orgs_custom_roles.go rename to vendor/github.com/google/go-github/v63/github/orgs_custom_roles.go index e1deeb7..f71cb7a 100644 --- a/vendor/github.com/google/go-github/v62/github/orgs_custom_roles.go +++ b/vendor/github.com/google/go-github/v63/github/orgs_custom_roles.go @@ -10,6 +10,20 @@ import ( "fmt" ) +// OrganizationCustomRoles represents custom organization roles available in specified organization. +type OrganizationCustomRoles struct { + TotalCount *int `json:"total_count,omitempty"` + CustomRepoRoles []*CustomOrgRoles `json:"roles,omitempty"` +} + +// CustomOrgRoles represents custom organization role available in specified organization. +type CustomOrgRoles struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Permissions []string `json:"permissions,omitempty"` +} + // OrganizationCustomRepoRoles represents custom repository roles available in specified organization. type OrganizationCustomRepoRoles struct { TotalCount *int `json:"total_count,omitempty"` @@ -27,6 +41,113 @@ type CustomRepoRoles struct { Permissions []string `json:"permissions,omitempty"` } +// CreateOrUpdateOrgRoleOptions represents options required to create or update a custom organization role. +type CreateOrUpdateOrgRoleOptions struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Permissions []string `json:"permissions,omitempty"` +} + +// CreateOrUpdateCustomRepoRoleOptions represents options required to create or update a custom repository role. +type CreateOrUpdateCustomRepoRoleOptions struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + BaseRole *string `json:"base_role,omitempty"` + Permissions []string `json:"permissions,omitempty"` +} + +// ListRoles lists the custom roles available in this organization. +// In order to see custom roles in an organization, the authenticated user must be an organization owner. +// +// GitHub API docs: https://docs.github.com/rest/orgs/organization-roles#get-all-organization-roles-for-an-organization +// +//meta:operation GET /orgs/{org}/organization-roles +func (s *OrganizationsService) ListRoles(ctx context.Context, org string) (*OrganizationCustomRoles, *Response, error) { + u := fmt.Sprintf("orgs/%v/organization-roles", org) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + customRepoRoles := new(OrganizationCustomRoles) + resp, err := s.client.Do(ctx, req, customRepoRoles) + if err != nil { + return nil, resp, err + } + + return customRepoRoles, resp, nil +} + +// CreateCustomOrgRole creates a custom role in this organization. +// In order to create custom roles in an organization, the authenticated user must be an organization owner. +// +// GitHub API docs: https://docs.github.com/rest/orgs/organization-roles#create-a-custom-organization-role +// +//meta:operation POST /orgs/{org}/organization-roles +func (s *OrganizationsService) CreateCustomOrgRole(ctx context.Context, org string, opts *CreateOrUpdateOrgRoleOptions) (*CustomOrgRoles, *Response, error) { + u := fmt.Sprintf("orgs/%v/organization-roles", org) + + req, err := s.client.NewRequest("POST", u, opts) + if err != nil { + return nil, nil, err + } + + resultingRole := new(CustomOrgRoles) + resp, err := s.client.Do(ctx, req, resultingRole) + if err != nil { + return nil, resp, err + } + + return resultingRole, resp, err +} + +// UpdateCustomOrgRole updates a custom role in this organization. +// In order to update custom roles in an organization, the authenticated user must be an organization owner. +// +// GitHub API docs: https://docs.github.com/rest/orgs/organization-roles#update-a-custom-organization-role +// +//meta:operation PATCH /orgs/{org}/organization-roles/{role_id} +func (s *OrganizationsService) UpdateCustomOrgRole(ctx context.Context, org string, roleID int64, opts *CreateOrUpdateOrgRoleOptions) (*CustomOrgRoles, *Response, error) { + u := fmt.Sprintf("orgs/%v/organization-roles/%v", org, roleID) + + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + + resultingRole := new(CustomOrgRoles) + resp, err := s.client.Do(ctx, req, resultingRole) + if err != nil { + return nil, resp, err + } + + return resultingRole, resp, err +} + +// DeleteCustomOrgRole deletes an existing custom role in this organization. +// In order to delete custom roles in an organization, the authenticated user must be an organization owner. +// +// GitHub API docs: https://docs.github.com/rest/orgs/organization-roles#delete-a-custom-organization-role +// +//meta:operation DELETE /orgs/{org}/organization-roles/{role_id} +func (s *OrganizationsService) DeleteCustomOrgRole(ctx context.Context, org string, roleID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/organization-roles/%v", org, roleID) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + resultingRole := new(CustomOrgRoles) + resp, err := s.client.Do(ctx, req, resultingRole) + if err != nil { + return resp, err + } + + return resp, nil +} + // ListCustomRepoRoles lists the custom repository roles available in this organization. // In order to see custom repository roles in an organization, the authenticated user must be an organization owner. // @@ -50,21 +171,13 @@ func (s *OrganizationsService) ListCustomRepoRoles(ctx context.Context, org stri return customRepoRoles, resp, nil } -// CreateOrUpdateCustomRoleOptions represents options required to create or update a custom repository role. -type CreateOrUpdateCustomRoleOptions struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - BaseRole *string `json:"base_role,omitempty"` - Permissions []string `json:"permissions,omitempty"` -} - // CreateCustomRepoRole creates a custom repository role in this organization. // In order to create custom repository roles in an organization, the authenticated user must be an organization owner. // // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#create-a-custom-repository-role // //meta:operation POST /orgs/{org}/custom-repository-roles -func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org string, opts *CreateOrUpdateCustomRoleOptions) (*CustomRepoRoles, *Response, error) { +func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org string, opts *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) { u := fmt.Sprintf("orgs/%v/custom-repository-roles", org) req, err := s.client.NewRequest("POST", u, opts) @@ -87,7 +200,7 @@ func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org str // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#update-a-custom-repository-role // //meta:operation PATCH /orgs/{org}/custom-repository-roles/{role_id} -func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org, roleID string, opts *CreateOrUpdateCustomRoleOptions) (*CustomRepoRoles, *Response, error) { +func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org string, roleID int64, opts *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) { u := fmt.Sprintf("orgs/%v/custom-repository-roles/%v", org, roleID) req, err := s.client.NewRequest("PATCH", u, opts) @@ -110,7 +223,7 @@ func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org, ro // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#delete-a-custom-repository-role // //meta:operation DELETE /orgs/{org}/custom-repository-roles/{role_id} -func (s *OrganizationsService) DeleteCustomRepoRole(ctx context.Context, org, roleID string) (*Response, error) { +func (s *OrganizationsService) DeleteCustomRepoRole(ctx context.Context, org string, roleID int64) (*Response, error) { u := fmt.Sprintf("orgs/%v/custom-repository-roles/%v", org, roleID) req, err := s.client.NewRequest("DELETE", u, nil) diff --git a/vendor/github.com/google/go-github/v62/github/orgs_hooks.go b/vendor/github.com/google/go-github/v63/github/orgs_hooks.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_hooks.go rename to vendor/github.com/google/go-github/v63/github/orgs_hooks.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_hooks_configuration.go b/vendor/github.com/google/go-github/v63/github/orgs_hooks_configuration.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_hooks_configuration.go rename to vendor/github.com/google/go-github/v63/github/orgs_hooks_configuration.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_hooks_deliveries.go b/vendor/github.com/google/go-github/v63/github/orgs_hooks_deliveries.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_hooks_deliveries.go rename to vendor/github.com/google/go-github/v63/github/orgs_hooks_deliveries.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_members.go b/vendor/github.com/google/go-github/v63/github/orgs_members.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_members.go rename to vendor/github.com/google/go-github/v63/github/orgs_members.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_outside_collaborators.go b/vendor/github.com/google/go-github/v63/github/orgs_outside_collaborators.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_outside_collaborators.go rename to vendor/github.com/google/go-github/v63/github/orgs_outside_collaborators.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_packages.go b/vendor/github.com/google/go-github/v63/github/orgs_packages.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_packages.go rename to vendor/github.com/google/go-github/v63/github/orgs_packages.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_personal_access_tokens.go b/vendor/github.com/google/go-github/v63/github/orgs_personal_access_tokens.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_personal_access_tokens.go rename to vendor/github.com/google/go-github/v63/github/orgs_personal_access_tokens.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_projects.go b/vendor/github.com/google/go-github/v63/github/orgs_projects.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_projects.go rename to vendor/github.com/google/go-github/v63/github/orgs_projects.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_properties.go b/vendor/github.com/google/go-github/v63/github/orgs_properties.go similarity index 91% rename from vendor/github.com/google/go-github/v62/github/orgs_properties.go rename to vendor/github.com/google/go-github/v63/github/orgs_properties.go index 2e88b7f..124b89c 100644 --- a/vendor/github.com/google/go-github/v62/github/orgs_properties.go +++ b/vendor/github.com/google/go-github/v63/github/orgs_properties.go @@ -15,12 +15,19 @@ type CustomProperty struct { // PropertyName is required for most endpoints except when calling CreateOrUpdateCustomProperty; // where this is sent in the path and thus can be omitted. PropertyName *string `json:"property_name,omitempty"` - // Possible values for ValueType are: string, single_select - ValueType string `json:"value_type"` - Required *bool `json:"required,omitempty"` - DefaultValue *string `json:"default_value,omitempty"` - Description *string `json:"description,omitempty"` + // The type of the value for the property. Can be one of: string, single_select. + ValueType string `json:"value_type"` + // Whether the property is required. + Required *bool `json:"required,omitempty"` + // Default value of the property. + DefaultValue *string `json:"default_value,omitempty"` + // Short description of the property. + Description *string `json:"description,omitempty"` + // An ordered list of the allowed values of the property. The property can have up to 200 + // allowed values. AllowedValues []string `json:"allowed_values,omitempty"` + // Who can edit the values of the property. Can be one of: org_actors, org_and_repo_actors, nil (null). + ValuesEditableBy *string `json:"values_editable_by,omitempty"` } // RepoCustomPropertyValue represents a repository custom property value. diff --git a/vendor/github.com/google/go-github/v62/github/orgs_rules.go b/vendor/github.com/google/go-github/v63/github/orgs_rules.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_rules.go rename to vendor/github.com/google/go-github/v63/github/orgs_rules.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_security_managers.go b/vendor/github.com/google/go-github/v63/github/orgs_security_managers.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_security_managers.go rename to vendor/github.com/google/go-github/v63/github/orgs_security_managers.go diff --git a/vendor/github.com/google/go-github/v62/github/orgs_users_blocking.go b/vendor/github.com/google/go-github/v63/github/orgs_users_blocking.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/orgs_users_blocking.go rename to vendor/github.com/google/go-github/v63/github/orgs_users_blocking.go diff --git a/vendor/github.com/google/go-github/v62/github/packages.go b/vendor/github.com/google/go-github/v63/github/packages.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/packages.go rename to vendor/github.com/google/go-github/v63/github/packages.go diff --git a/vendor/github.com/google/go-github/v62/github/projects.go b/vendor/github.com/google/go-github/v63/github/projects.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/projects.go rename to vendor/github.com/google/go-github/v63/github/projects.go diff --git a/vendor/github.com/google/go-github/v62/github/pulls.go b/vendor/github.com/google/go-github/v63/github/pulls.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/pulls.go rename to vendor/github.com/google/go-github/v63/github/pulls.go diff --git a/vendor/github.com/google/go-github/v62/github/pulls_comments.go b/vendor/github.com/google/go-github/v63/github/pulls_comments.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/pulls_comments.go rename to vendor/github.com/google/go-github/v63/github/pulls_comments.go diff --git a/vendor/github.com/google/go-github/v62/github/pulls_reviewers.go b/vendor/github.com/google/go-github/v63/github/pulls_reviewers.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/pulls_reviewers.go rename to vendor/github.com/google/go-github/v63/github/pulls_reviewers.go diff --git a/vendor/github.com/google/go-github/v62/github/pulls_reviews.go b/vendor/github.com/google/go-github/v63/github/pulls_reviews.go similarity index 97% rename from vendor/github.com/google/go-github/v62/github/pulls_reviews.go rename to vendor/github.com/google/go-github/v63/github/pulls_reviews.go index 27b8dc3..28450c3 100644 --- a/vendor/github.com/google/go-github/v62/github/pulls_reviews.go +++ b/vendor/github.com/google/go-github/v63/github/pulls_reviews.go @@ -35,9 +35,12 @@ func (p PullRequestReview) String() string { // DraftReviewComment represents a comment part of the review. type DraftReviewComment struct { - Path *string `json:"path,omitempty"` - Position *int `json:"position,omitempty"` - Body *string `json:"body,omitempty"` + Path *string `json:"path,omitempty"` + Position *int `json:"position,omitempty"` + Body *string `json:"body,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + InReplyTo *int64 `json:"in_reply_to,omitempty"` + SubjectType *string `json:"subject_type,omitempty"` // The new comfort-fade-preview fields StartSide *string `json:"start_side,omitempty"` diff --git a/vendor/github.com/google/go-github/v62/github/pulls_threads.go b/vendor/github.com/google/go-github/v63/github/pulls_threads.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/pulls_threads.go rename to vendor/github.com/google/go-github/v63/github/pulls_threads.go diff --git a/vendor/github.com/google/go-github/v62/github/rate_limit.go b/vendor/github.com/google/go-github/v63/github/rate_limit.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/rate_limit.go rename to vendor/github.com/google/go-github/v63/github/rate_limit.go diff --git a/vendor/github.com/google/go-github/v62/github/reactions.go b/vendor/github.com/google/go-github/v63/github/reactions.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/reactions.go rename to vendor/github.com/google/go-github/v63/github/reactions.go diff --git a/vendor/github.com/google/go-github/v62/github/repos.go b/vendor/github.com/google/go-github/v63/github/repos.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos.go rename to vendor/github.com/google/go-github/v63/github/repos.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_actions_access.go b/vendor/github.com/google/go-github/v63/github/repos_actions_access.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_actions_access.go rename to vendor/github.com/google/go-github/v63/github/repos_actions_access.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_actions_allowed.go b/vendor/github.com/google/go-github/v63/github/repos_actions_allowed.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_actions_allowed.go rename to vendor/github.com/google/go-github/v63/github/repos_actions_allowed.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_actions_permissions.go b/vendor/github.com/google/go-github/v63/github/repos_actions_permissions.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_actions_permissions.go rename to vendor/github.com/google/go-github/v63/github/repos_actions_permissions.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_autolinks.go b/vendor/github.com/google/go-github/v63/github/repos_autolinks.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_autolinks.go rename to vendor/github.com/google/go-github/v63/github/repos_autolinks.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_codeowners.go b/vendor/github.com/google/go-github/v63/github/repos_codeowners.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_codeowners.go rename to vendor/github.com/google/go-github/v63/github/repos_codeowners.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_collaborators.go b/vendor/github.com/google/go-github/v63/github/repos_collaborators.go similarity index 99% rename from vendor/github.com/google/go-github/v62/github/repos_collaborators.go rename to vendor/github.com/google/go-github/v63/github/repos_collaborators.go index 15a4e77..d6c9853 100644 --- a/vendor/github.com/google/go-github/v62/github/repos_collaborators.go +++ b/vendor/github.com/google/go-github/v63/github/repos_collaborators.go @@ -99,6 +99,8 @@ type RepositoryPermissionLevel struct { Permission *string `json:"permission,omitempty"` User *User `json:"user,omitempty"` + + RoleName *string `json:"role_name,omitempty"` } // GetPermissionLevel retrieves the specific permission level a collaborator has for a given repository. diff --git a/vendor/github.com/google/go-github/v62/github/repos_comments.go b/vendor/github.com/google/go-github/v63/github/repos_comments.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_comments.go rename to vendor/github.com/google/go-github/v63/github/repos_comments.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_commits.go b/vendor/github.com/google/go-github/v63/github/repos_commits.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_commits.go rename to vendor/github.com/google/go-github/v63/github/repos_commits.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_community_health.go b/vendor/github.com/google/go-github/v63/github/repos_community_health.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_community_health.go rename to vendor/github.com/google/go-github/v63/github/repos_community_health.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_contents.go b/vendor/github.com/google/go-github/v63/github/repos_contents.go similarity index 96% rename from vendor/github.com/google/go-github/v62/github/repos_contents.go rename to vendor/github.com/google/go-github/v63/github/repos_contents.go index 9539a5c..97539ae 100644 --- a/vendor/github.com/google/go-github/v62/github/repos_contents.go +++ b/vendor/github.com/google/go-github/v63/github/repos_contents.go @@ -150,7 +150,11 @@ func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo, return nil, resp, fmt.Errorf("no download link found for %s", filepath) } - dlResp, err := s.client.client.Get(*contents.DownloadURL) + dlReq, err := http.NewRequestWithContext(ctx, http.MethodGet, *contents.DownloadURL, nil) + if err != nil { + return nil, resp, err + } + dlResp, err := s.client.client.Do(dlReq) if err != nil { return nil, &Response{Response: dlResp}, err } @@ -188,7 +192,11 @@ func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owne return nil, contents, resp, fmt.Errorf("no download link found for %s", filepath) } - dlResp, err := s.client.client.Get(*contents.DownloadURL) + dlReq, err := http.NewRequestWithContext(ctx, http.MethodGet, *contents.DownloadURL, nil) + if err != nil { + return nil, contents, resp, err + } + dlResp, err := s.client.client.Do(dlReq) if err != nil { return nil, contents, &Response{Response: dlResp}, err } @@ -346,7 +354,7 @@ func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo st } defer resp.Body.Close() - if resp.StatusCode != http.StatusFound { + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound { return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status) } diff --git a/vendor/github.com/google/go-github/v62/github/repos_deployment_branch_policies.go b/vendor/github.com/google/go-github/v63/github/repos_deployment_branch_policies.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_deployment_branch_policies.go rename to vendor/github.com/google/go-github/v63/github/repos_deployment_branch_policies.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_deployment_protection_rules.go b/vendor/github.com/google/go-github/v63/github/repos_deployment_protection_rules.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_deployment_protection_rules.go rename to vendor/github.com/google/go-github/v63/github/repos_deployment_protection_rules.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_deployments.go b/vendor/github.com/google/go-github/v63/github/repos_deployments.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_deployments.go rename to vendor/github.com/google/go-github/v63/github/repos_deployments.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_environments.go b/vendor/github.com/google/go-github/v63/github/repos_environments.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_environments.go rename to vendor/github.com/google/go-github/v63/github/repos_environments.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_forks.go b/vendor/github.com/google/go-github/v63/github/repos_forks.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_forks.go rename to vendor/github.com/google/go-github/v63/github/repos_forks.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_hooks.go b/vendor/github.com/google/go-github/v63/github/repos_hooks.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_hooks.go rename to vendor/github.com/google/go-github/v63/github/repos_hooks.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_hooks_configuration.go b/vendor/github.com/google/go-github/v63/github/repos_hooks_configuration.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_hooks_configuration.go rename to vendor/github.com/google/go-github/v63/github/repos_hooks_configuration.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_hooks_deliveries.go b/vendor/github.com/google/go-github/v63/github/repos_hooks_deliveries.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_hooks_deliveries.go rename to vendor/github.com/google/go-github/v63/github/repos_hooks_deliveries.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_invitations.go b/vendor/github.com/google/go-github/v63/github/repos_invitations.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_invitations.go rename to vendor/github.com/google/go-github/v63/github/repos_invitations.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_keys.go b/vendor/github.com/google/go-github/v63/github/repos_keys.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_keys.go rename to vendor/github.com/google/go-github/v63/github/repos_keys.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_lfs.go b/vendor/github.com/google/go-github/v63/github/repos_lfs.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_lfs.go rename to vendor/github.com/google/go-github/v63/github/repos_lfs.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_merging.go b/vendor/github.com/google/go-github/v63/github/repos_merging.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_merging.go rename to vendor/github.com/google/go-github/v63/github/repos_merging.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_pages.go b/vendor/github.com/google/go-github/v63/github/repos_pages.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_pages.go rename to vendor/github.com/google/go-github/v63/github/repos_pages.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_prereceive_hooks.go b/vendor/github.com/google/go-github/v63/github/repos_prereceive_hooks.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_prereceive_hooks.go rename to vendor/github.com/google/go-github/v63/github/repos_prereceive_hooks.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_projects.go b/vendor/github.com/google/go-github/v63/github/repos_projects.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_projects.go rename to vendor/github.com/google/go-github/v63/github/repos_projects.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_properties.go b/vendor/github.com/google/go-github/v63/github/repos_properties.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_properties.go rename to vendor/github.com/google/go-github/v63/github/repos_properties.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_releases.go b/vendor/github.com/google/go-github/v63/github/repos_releases.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_releases.go rename to vendor/github.com/google/go-github/v63/github/repos_releases.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_rules.go b/vendor/github.com/google/go-github/v63/github/repos_rules.go similarity index 84% rename from vendor/github.com/google/go-github/v62/github/repos_rules.go rename to vendor/github.com/google/go-github/v63/github/repos_rules.go index 6f046a3..2827f85 100644 --- a/vendor/github.com/google/go-github/v62/github/repos_rules.go +++ b/vendor/github.com/google/go-github/v63/github/repos_rules.go @@ -48,12 +48,25 @@ type RulesetRepositoryIDsConditionParameters struct { RepositoryIDs []int64 `json:"repository_ids,omitempty"` } +// RulesetRepositoryPropertyTargetParameters represents a repository_property name and values to be used for targeting. +type RulesetRepositoryPropertyTargetParameters struct { + Name string `json:"name"` + Values []string `json:"property_values"` +} + +// RulesetRepositoryPropertyConditionParameters represents the conditions object for repository_property. +type RulesetRepositoryPropertyConditionParameters struct { + Include []RulesetRepositoryPropertyTargetParameters `json:"include"` + Exclude []RulesetRepositoryPropertyTargetParameters `json:"exclude"` +} + // RulesetConditions represents the conditions object in a ruleset. -// Set either RepositoryName or RepositoryID, not both. +// Set either RepositoryName or RepositoryID or RepositoryProperty, not more than one. type RulesetConditions struct { - RefName *RulesetRefConditionParameters `json:"ref_name,omitempty"` - RepositoryName *RulesetRepositoryNamesConditionParameters `json:"repository_name,omitempty"` - RepositoryID *RulesetRepositoryIDsConditionParameters `json:"repository_id,omitempty"` + RefName *RulesetRefConditionParameters `json:"ref_name,omitempty"` + RepositoryName *RulesetRepositoryNamesConditionParameters `json:"repository_name,omitempty"` + RepositoryID *RulesetRepositoryIDsConditionParameters `json:"repository_id,omitempty"` + RepositoryProperty *RulesetRepositoryPropertyConditionParameters `json:"repository_property,omitempty"` } // RulePatternParameters represents the rule pattern parameters. @@ -395,6 +408,24 @@ type Ruleset struct { Rules []*RepositoryRule `json:"rules,omitempty"` } +// rulesetNoOmitBypassActors represents a GitHub ruleset object. The struct does not omit bypassActors if the field is nil or an empty array is passed. +type rulesetNoOmitBypassActors struct { + ID *int64 `json:"id,omitempty"` + Name string `json:"name"` + // Possible values for Target are branch, tag + Target *string `json:"target,omitempty"` + // Possible values for SourceType are: Repository, Organization + SourceType *string `json:"source_type,omitempty"` + Source string `json:"source"` + // Possible values for Enforcement are: disabled, active, evaluate + Enforcement string `json:"enforcement"` + BypassActors []*BypassActor `json:"bypass_actors"` + NodeID *string `json:"node_id,omitempty"` + Links *RulesetLinks `json:"_links,omitempty"` + Conditions *RulesetConditions `json:"conditions,omitempty"` + Rules []*RepositoryRule `json:"rules,omitempty"` +} + // GetRulesForBranch gets all the rules that apply to the specified branch. // // GitHub API docs: https://docs.github.com/rest/repos/rules#get-rules-for-a-branch @@ -507,6 +538,48 @@ func (s *RepositoriesService) UpdateRuleset(ctx context.Context, owner, repo str return ruleset, resp, nil } +// UpdateRulesetNoBypassActor updates a ruleset for the specified repository. +// +// This function is necessary as the UpdateRuleset function does not marshal ByPassActor if passed as nil or an empty array. +// +// GitHub API docs: https://docs.github.com/rest/repos/rules#update-a-repository-ruleset +// +//meta:operation PUT /repos/{owner}/{repo}/rulesets/{ruleset_id} +func (s *RepositoriesService) UpdateRulesetNoBypassActor(ctx context.Context, owner, repo string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID) + + rsNoBypassActor := &rulesetNoOmitBypassActors{} + + if rs != nil { + rsNoBypassActor = &rulesetNoOmitBypassActors{ + ID: rs.ID, + Name: rs.Name, + Target: rs.Target, + SourceType: rs.SourceType, + Source: rs.Source, + Enforcement: rs.Enforcement, + BypassActors: rs.BypassActors, + NodeID: rs.NodeID, + Links: rs.Links, + Conditions: rs.Conditions, + Rules: rs.Rules, + } + } + + req, err := s.client.NewRequest("PUT", u, rsNoBypassActor) + if err != nil { + return nil, nil, err + } + + var ruleSet *Ruleset + resp, err := s.client.Do(ctx, req, &ruleSet) + if err != nil { + return nil, resp, err + } + + return ruleSet, resp, nil +} + // DeleteRuleset deletes a ruleset for the specified repository. // // GitHub API docs: https://docs.github.com/rest/repos/rules#delete-a-repository-ruleset diff --git a/vendor/github.com/google/go-github/v62/github/repos_stats.go b/vendor/github.com/google/go-github/v63/github/repos_stats.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_stats.go rename to vendor/github.com/google/go-github/v63/github/repos_stats.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_statuses.go b/vendor/github.com/google/go-github/v63/github/repos_statuses.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_statuses.go rename to vendor/github.com/google/go-github/v63/github/repos_statuses.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_tags.go b/vendor/github.com/google/go-github/v63/github/repos_tags.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_tags.go rename to vendor/github.com/google/go-github/v63/github/repos_tags.go diff --git a/vendor/github.com/google/go-github/v62/github/repos_traffic.go b/vendor/github.com/google/go-github/v63/github/repos_traffic.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/repos_traffic.go rename to vendor/github.com/google/go-github/v63/github/repos_traffic.go diff --git a/vendor/github.com/google/go-github/v62/github/scim.go b/vendor/github.com/google/go-github/v63/github/scim.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/scim.go rename to vendor/github.com/google/go-github/v63/github/scim.go diff --git a/vendor/github.com/google/go-github/v62/github/search.go b/vendor/github.com/google/go-github/v63/github/search.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/search.go rename to vendor/github.com/google/go-github/v63/github/search.go diff --git a/vendor/github.com/google/go-github/v62/github/secret_scanning.go b/vendor/github.com/google/go-github/v63/github/secret_scanning.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/secret_scanning.go rename to vendor/github.com/google/go-github/v63/github/secret_scanning.go diff --git a/vendor/github.com/google/go-github/v62/github/security_advisories.go b/vendor/github.com/google/go-github/v63/github/security_advisories.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/security_advisories.go rename to vendor/github.com/google/go-github/v63/github/security_advisories.go diff --git a/vendor/github.com/google/go-github/v62/github/strings.go b/vendor/github.com/google/go-github/v63/github/strings.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/strings.go rename to vendor/github.com/google/go-github/v63/github/strings.go diff --git a/vendor/github.com/google/go-github/v62/github/teams.go b/vendor/github.com/google/go-github/v63/github/teams.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/teams.go rename to vendor/github.com/google/go-github/v63/github/teams.go diff --git a/vendor/github.com/google/go-github/v62/github/teams_discussion_comments.go b/vendor/github.com/google/go-github/v63/github/teams_discussion_comments.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/teams_discussion_comments.go rename to vendor/github.com/google/go-github/v63/github/teams_discussion_comments.go diff --git a/vendor/github.com/google/go-github/v62/github/teams_discussions.go b/vendor/github.com/google/go-github/v63/github/teams_discussions.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/teams_discussions.go rename to vendor/github.com/google/go-github/v63/github/teams_discussions.go diff --git a/vendor/github.com/google/go-github/v62/github/teams_members.go b/vendor/github.com/google/go-github/v63/github/teams_members.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/teams_members.go rename to vendor/github.com/google/go-github/v63/github/teams_members.go diff --git a/vendor/github.com/google/go-github/v62/github/timestamp.go b/vendor/github.com/google/go-github/v63/github/timestamp.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/timestamp.go rename to vendor/github.com/google/go-github/v63/github/timestamp.go diff --git a/vendor/github.com/google/go-github/v62/github/users.go b/vendor/github.com/google/go-github/v63/github/users.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users.go rename to vendor/github.com/google/go-github/v63/github/users.go diff --git a/vendor/github.com/google/go-github/v62/github/users_administration.go b/vendor/github.com/google/go-github/v63/github/users_administration.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_administration.go rename to vendor/github.com/google/go-github/v63/github/users_administration.go diff --git a/vendor/github.com/google/go-github/v62/github/users_blocking.go b/vendor/github.com/google/go-github/v63/github/users_blocking.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_blocking.go rename to vendor/github.com/google/go-github/v63/github/users_blocking.go diff --git a/vendor/github.com/google/go-github/v62/github/users_emails.go b/vendor/github.com/google/go-github/v63/github/users_emails.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_emails.go rename to vendor/github.com/google/go-github/v63/github/users_emails.go diff --git a/vendor/github.com/google/go-github/v62/github/users_followers.go b/vendor/github.com/google/go-github/v63/github/users_followers.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_followers.go rename to vendor/github.com/google/go-github/v63/github/users_followers.go diff --git a/vendor/github.com/google/go-github/v62/github/users_gpg_keys.go b/vendor/github.com/google/go-github/v63/github/users_gpg_keys.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_gpg_keys.go rename to vendor/github.com/google/go-github/v63/github/users_gpg_keys.go diff --git a/vendor/github.com/google/go-github/v62/github/users_keys.go b/vendor/github.com/google/go-github/v63/github/users_keys.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_keys.go rename to vendor/github.com/google/go-github/v63/github/users_keys.go diff --git a/vendor/github.com/google/go-github/v62/github/users_packages.go b/vendor/github.com/google/go-github/v63/github/users_packages.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_packages.go rename to vendor/github.com/google/go-github/v63/github/users_packages.go diff --git a/vendor/github.com/google/go-github/v62/github/users_projects.go b/vendor/github.com/google/go-github/v63/github/users_projects.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_projects.go rename to vendor/github.com/google/go-github/v63/github/users_projects.go diff --git a/vendor/github.com/google/go-github/v62/github/users_ssh_signing_keys.go b/vendor/github.com/google/go-github/v63/github/users_ssh_signing_keys.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/users_ssh_signing_keys.go rename to vendor/github.com/google/go-github/v63/github/users_ssh_signing_keys.go diff --git a/vendor/github.com/google/go-github/v62/github/with_appengine.go b/vendor/github.com/google/go-github/v63/github/with_appengine.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/with_appengine.go rename to vendor/github.com/google/go-github/v63/github/with_appengine.go diff --git a/vendor/github.com/google/go-github/v62/github/without_appengine.go b/vendor/github.com/google/go-github/v63/github/without_appengine.go similarity index 100% rename from vendor/github.com/google/go-github/v62/github/without_appengine.go rename to vendor/github.com/google/go-github/v63/github/without_appengine.go diff --git a/vendor/github.com/gophercloud/gophercloud/v2/.gitignore b/vendor/github.com/gophercloud/gophercloud/v2/.gitignore new file mode 100644 index 0000000..8b1b79e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/.gitignore @@ -0,0 +1,5 @@ +**/*.swp +.idea +.vscode +testing_*.coverprofile +/cover.out diff --git a/vendor/github.com/gophercloud/gophercloud/v2/.golangci.yaml b/vendor/github.com/gophercloud/gophercloud/v2/.golangci.yaml new file mode 100644 index 0000000..828a099 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/.golangci.yaml @@ -0,0 +1,19 @@ +--- +linters: + disable-all: true + enable: + - errcheck + - gofmt + - goimports + - govet + - staticcheck + - unparam + - unused + +issues: + exclude: + - SA1006 # printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck) + exclude-rules: + - linters: + - staticcheck + text: 'SA1019: (x509.EncryptPEMBlock|strings.Title)' diff --git a/vendor/github.com/gophercloud/gophercloud/v2/CHANGELOG.md b/vendor/github.com/gophercloud/gophercloud/v2/CHANGELOG.md new file mode 100644 index 0000000..1d7dfbc --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/CHANGELOG.md @@ -0,0 +1,983 @@ +## v2.0.0 (2024-05-27) + +MAIN BREAKING CHANGES: + +* **Gophercloud now requires Go v1.22.** +* [GH-2821](https://github.com/gophercloud/gophercloud/pull/2821) Gophercloud now escapes container and object names in all `objects` and `containers` functions. If you were previously escaping names (with, for example: `url.PathEscape` or `url.QueryEscape`), then you should REMOVE that and pass the intended names to Gophercloud directly. +* [GH-2821](https://github.com/gophercloud/gophercloud/pull/2821) The `containers.ListOpts#Full` and `objects.ListOpts#Full` properties are REMOVED from the Gophercloud API. The reason for that is: plaintext listing is unfixably wrong and won't handle special characters reliably (i.e. `\n`). +* [GH-2821](https://github.com/gophercloud/gophercloud/pull/2821) Empty container names, container names containing a slash (`/`), and empty object names are now rejected in Gophercloud before any call to Swift. +* [GH-2821](https://github.com/gophercloud/gophercloud/pull/2821) In `objectstorage`: `containers.ErrInvalidContainerName` is now `v1.ErrInvalidContainerName`. +* [GH-2821](https://github.com/gophercloud/gophercloud/pull/2821) New name validation errors in `objectstorage`: + * `v1.ErrEmptyContainerName` + * `v1.ErrEmptyObjectName` +* [GH-2821](https://github.com/gophercloud/gophercloud/pull/2821) In `objects.Copy`: the `destination` field (e.g. `objects.CopyOpts#Destination`) must be in the form `/container/object`: the function will reject a destination path if it doesn't start with a slash (`/`). +* [GH-2560](https://github.com/gophercloud/gophercloud/pull/2560) loadbalancer: Use CreateMemberOpts instead of BatchUpdateMemberOpts in PoolCreateOpts +* [GH-2886](https://github.com/gophercloud/gophercloud/pull/2886) ports: Fix value_specs implementation +* [GH-2665](https://github.com/gophercloud/gophercloud/pull/2665) Cinder: Remove multiatttach request parameter +* [GH-2936](https://github.com/gophercloud/gophercloud/pull/2936) Make Gophercloud context-aware: all function signatures triggering an HTTP call now accept a context.Context for tracing and cancellation +* [GH-2970](https://github.com/gophercloud/gophercloud/pull/2970) Remove context from the Provider client +* [GH-2904](https://github.com/gophercloud/gophercloud/pull/2904) Remove error code types + +New features and improvements: + +* [GH-2486](https://github.com/gophercloud/gophercloud/pull/2486) Fix BareMetalV1 version +* [GH-2492](https://github.com/gophercloud/gophercloud/pull/2492) Add tags for loadbalancer l7policy and l7rule +* [GH-2560](https://github.com/gophercloud/gophercloud/pull/2560) loadbalancer: Use CreateMemberOpts instead of BatchUpdateMemberOpts in PoolCreateOpts +* [GH-2561](https://github.com/gophercloud/gophercloud/pull/2561) compute: add ext_specs to flavor +* [GH-2613](https://github.com/gophercloud/gophercloud/pull/2613) Migrate baremetal inventory to a common location +* [GH-2724](https://github.com/gophercloud/gophercloud/pull/2724) baremetal: introduce Node Inventory API +* [GH-2725](https://github.com/gophercloud/gophercloud/pull/2725) baremetal: finish moving common inventory bits +* [GH-2736](https://github.com/gophercloud/gophercloud/pull/2736) Composable templates +* [GH-2781](https://github.com/gophercloud/gophercloud/pull/2781) baremetal: support ironic native PluginData +* [GH-2791](https://github.com/gophercloud/gophercloud/pull/2791) Add microversion utilities +* [GH-2806](https://github.com/gophercloud/gophercloud/pull/2806) Fix list ports with multiple fixedip parameters +* [GH-2809](https://github.com/gophercloud/gophercloud/pull/2809) Remove code for CDN (poppy) +* [GH-2812](https://github.com/gophercloud/gophercloud/pull/2812) Revert "Fix baremetal jobs on Ubuntu 20.04" +* [GH-2821](https://github.com/gophercloud/gophercloud/pull/2821) objects: Escape names in Gophercloud +* [GH-2828](https://github.com/gophercloud/gophercloud/pull/2828) Octavia: Add tags to resources missing them +* [GH-2834](https://github.com/gophercloud/gophercloud/pull/2834) baremetal: implemented ParsedLLDP in the standard PluginData +* [GH-2866](https://github.com/gophercloud/gophercloud/pull/2866) loadbalancer additional_vips by snigle +* [GH-2881](https://github.com/gophercloud/gophercloud/pull/2881) Adding missing QoS field for router +* [GH-2883](https://github.com/gophercloud/gophercloud/pull/2883) Context-aware methods to ProviderClient and ServiceClient +* [GH-2892](https://github.com/gophercloud/gophercloud/pull/2892) Authenticate with a clouds.yaml + +## v1.12.0 (2024-05-27) + +* [GH-2979](https://github.com/gophercloud/gophercloud/pull/2979) [v1] CI backports +* [GH-2985](https://github.com/gophercloud/gophercloud/pull/2985) [v1] baremetal: fix handling of the "fields" query argument +* [GH-2989](https://github.com/gophercloud/gophercloud/pull/2989) [v1] [CI] Fix portbiding tests +* [GH-2992](https://github.com/gophercloud/gophercloud/pull/2992) [v1] [CI] Fix portbiding tests +* [GH-2993](https://github.com/gophercloud/gophercloud/pull/2993) [v1] build(deps): bump EmilienM/devstack-action from 0.14 to 0.15 +* [GH-2998](https://github.com/gophercloud/gophercloud/pull/2998) [v1] testhelper: mark all helpers with t.Helper +* [GH-3043](https://github.com/gophercloud/gophercloud/pull/3043) [v1] CI: remove Zed from testing coverage + +## v1.11.0 (2024-03-07) + +This version reverts the inclusion of Context in the v1 branch. This inclusion +didn't add much value because no packages were using it; on the other hand, it +introduced a bug when using the Context property of the Provider client. + +## v1.10.0 (2024-02-27) **RETRACTED**: see https://github.com/gophercloud/gophercloud/issues/2969 + +* [GH-2893](https://github.com/gophercloud/gophercloud/pull/2893) [v1] authentication: Add WithContext functions +* [GH-2894](https://github.com/gophercloud/gophercloud/pull/2894) [v1] pager: Add WithContext functions +* [GH-2899](https://github.com/gophercloud/gophercloud/pull/2899) [v1] Authenticate with a clouds.yaml +* [GH-2917](https://github.com/gophercloud/gophercloud/pull/2917) [v1] Add ParseOption type to made clouds.Parse() more usable for optional With* funcs +* [GH-2924](https://github.com/gophercloud/gophercloud/pull/2924) [v1] build(deps): bump EmilienM/devstack-action from 0.11 to 0.14 +* [GH-2933](https://github.com/gophercloud/gophercloud/pull/2933) [v1] Fix AllowReauth reauthentication +* [GH-2950](https://github.com/gophercloud/gophercloud/pull/2950) [v1] compute: Use volumeID, not attachmentID for volume attachments + +## v1.9.0 (2024-02-02) **RETRACTED**: see https://github.com/gophercloud/gophercloud/issues/2969 + +New features and improvements: + +* [GH-2884](https://github.com/gophercloud/gophercloud/pull/2884) [v1] Context-aware methods to ProviderClient and ServiceClient +* [GH-2887](https://github.com/gophercloud/gophercloud/pull/2887) [v1] Add support of Flavors and FlavorProfiles for Octavia +* [GH-2875](https://github.com/gophercloud/gophercloud/pull/2875) [v1] [db/v1/instance]: adding support for availability_zone for a db instance + +CI changes: + +* [GH-2856](https://github.com/gophercloud/gophercloud/pull/2856) [v1] Fix devstack install on EOL magnum branches +* [GH-2857](https://github.com/gophercloud/gophercloud/pull/2857) [v1] Fix networking acceptance tests +* [GH-2858](https://github.com/gophercloud/gophercloud/pull/2858) [v1] build(deps): bump actions/upload-artifact from 3 to 4 +* [GH-2859](https://github.com/gophercloud/gophercloud/pull/2859) [v1] build(deps): bump github/codeql-action from 2 to 3 + +## v1.8.0 (2023-11-30) + +New features and improvements: + +* [GH-2800](https://github.com/gophercloud/gophercloud/pull/2800) [v1] Fix options initialization in ServiceClient.Request (fixes #2798) +* [GH-2823](https://github.com/gophercloud/gophercloud/pull/2823) [v1] Add more godoc to GuestFormat +* [GH-2826](https://github.com/gophercloud/gophercloud/pull/2826) Allow objects.CreateTempURL with names containing /v1/ + +CI changes: + +* [GH-2802](https://github.com/gophercloud/gophercloud/pull/2802) [v1] Add job for bobcat stable/2023.2 +* [GH-2819](https://github.com/gophercloud/gophercloud/pull/2819) [v1] Test files alongside code +* [GH-2814](https://github.com/gophercloud/gophercloud/pull/2814) Make fixtures part of tests +* [GH-2796](https://github.com/gophercloud/gophercloud/pull/2796) [v1] ci/unit: switch to coverallsapp/github-action +* [GH-2840](https://github.com/gophercloud/gophercloud/pull/2840) unit tests: Fix the installation of tools + +## v1.7.0 (2023-09-22) + +New features and improvements: + +* [GH-2782](https://github.com/gophercloud/gophercloud/pull/2782) [v1] (manual clean backport) Add tag field to compute block_device_v2 + +CI changes: + +* [GH-2760](https://github.com/gophercloud/gophercloud/pull/2760) [v1 backports] semver auto labels +* [GH-2775](https://github.com/gophercloud/gophercloud/pull/2775) [v1] Fix typos in comments +* [GH-2783](https://github.com/gophercloud/gophercloud/pull/2783) [v1] (clean manual backport) ci/functional: fix ubuntu version & add antelope +* [GH-2785](https://github.com/gophercloud/gophercloud/pull/2785) [v1] Acceptance: Handle numerical version names in version comparison helpers +* [GH-2787](https://github.com/gophercloud/gophercloud/pull/2787) backport-v1: fixes to semver label +* [GH-2788](https://github.com/gophercloud/gophercloud/pull/2788) [v1] Make acceptance tests internal + + +## v1.6.0 (2023-08-30) + +New features and improvements: + +* [GH-2712](https://github.com/gophercloud/gophercloud/pull/2712) [v1] README: minor change to test backport workflow +* [GH-2713](https://github.com/gophercloud/gophercloud/pull/2713) [v1] tests: run MultiAttach with a capable Cinder Type +* [GH-2714](https://github.com/gophercloud/gophercloud/pull/2714) [v1] Add CRUD support for encryption in volume v3 types +* [GH-2715](https://github.com/gophercloud/gophercloud/pull/2715) [v1] Add projectID to fwaas_v2 policy CreateOpts and ListOpts +* [GH-2716](https://github.com/gophercloud/gophercloud/pull/2716) [v1] Add projectID to fwaas_v2 CreateOpts +* [GH-2717](https://github.com/gophercloud/gophercloud/pull/2717) [v1] [manila]: add reset and force delete actions to a snapshot +* [GH-2718](https://github.com/gophercloud/gophercloud/pull/2718) [v1] [cinder]: add reset and force delete actions to volumes and snapshots +* [GH-2721](https://github.com/gophercloud/gophercloud/pull/2721) [v1] orchestration: Explicit error in optionsmap creation +* [GH-2723](https://github.com/gophercloud/gophercloud/pull/2723) [v1] Add conductor API to Baremetal V1 +* [GH-2729](https://github.com/gophercloud/gophercloud/pull/2729) [v1] networking/v2/ports: allow list filter by security group + +CI changes: + +* [GH-2675](https://github.com/gophercloud/gophercloud/pull/2675) [v1][CI] Drop periodic jobs from stable branch +* [GH-2683](https://github.com/gophercloud/gophercloud/pull/2683) [v1] CI tweaks + + +## v1.5.0 (2023-06-21) + +New features and improvements: + +* [GH-2634](https://github.com/gophercloud/gophercloud/pull/2634) baremetal: update inspection inventory with recent additions +* [GH-2635](https://github.com/gophercloud/gophercloud/pull/2635) [manila]: Add Share Replicas support +* [GH-2637](https://github.com/gophercloud/gophercloud/pull/2637) [FWaaS_v2]: Add FWaaS_V2 workflow and enable tests +* [GH-2639](https://github.com/gophercloud/gophercloud/pull/2639) Implement errors.Unwrap() on unexpected status code errors +* [GH-2648](https://github.com/gophercloud/gophercloud/pull/2648) [manila]: implement share transfer API + + +## v1.4.0 (2023-05-25) + +New features and improvements: + +* [GH-2465](https://github.com/gophercloud/gophercloud/pull/2465) keystone: add v3 limits update operation +* [GH-2596](https://github.com/gophercloud/gophercloud/pull/2596) keystone: add v3 limits get operation +* [GH-2618](https://github.com/gophercloud/gophercloud/pull/2618) keystone: add v3 limits delete operation +* [GH-2616](https://github.com/gophercloud/gophercloud/pull/2616) Add CRUD support for register limit APIs +* [GH-2610](https://github.com/gophercloud/gophercloud/pull/2610) Add PUT/HEAD/DELETE for identity/v3/OS-INHERIT +* [GH-2597](https://github.com/gophercloud/gophercloud/pull/2597) Add validation and optimise objects.BulkDelete +* [GH-2602](https://github.com/gophercloud/gophercloud/pull/2602) [swift v1]: introduce a TempURLKey argument for objects.CreateTempURLOpts struct +* [GH-2623](https://github.com/gophercloud/gophercloud/pull/2623) Add the ability to remove ingress/egress policies from fwaas_v2 groups +* [GH-2625](https://github.com/gophercloud/gophercloud/pull/2625) neutron: Support trunk_details extension + +CI changes: + +* [GH-2608](https://github.com/gophercloud/gophercloud/pull/2608) Drop train and ussuri jobs +* [GH-2589](https://github.com/gophercloud/gophercloud/pull/2589) Bump EmilienM/devstack-action from 0.10 to 0.11 +* [GH-2604](https://github.com/gophercloud/gophercloud/pull/2604) Bump mheap/github-action-required-labels from 3 to 4 +* [GH-2620](https://github.com/gophercloud/gophercloud/pull/2620) Pin goimport dep to a version that works with go 1.14 +* [GH-2619](https://github.com/gophercloud/gophercloud/pull/2619) Fix version comparison for acceptance tests +* [GH-2627](https://github.com/gophercloud/gophercloud/pull/2627) Limits: Fix ToDo to create registered limit and use it +* [GH-2629](https://github.com/gophercloud/gophercloud/pull/2629) [manila]: Add share from snapshot restore functional test + + +## v1.3.0 (2023-03-28) + +* [GH-2464](https://github.com/gophercloud/gophercloud/pull/2464) keystone: add v3 limits create operation +* [GH-2512](https://github.com/gophercloud/gophercloud/pull/2512) Manila: add List for share-access-rules API +* [GH-2529](https://github.com/gophercloud/gophercloud/pull/2529) Added target state "rebuild" for Ironic nodes +* [GH-2539](https://github.com/gophercloud/gophercloud/pull/2539) Add release instructions +* [GH-2540](https://github.com/gophercloud/gophercloud/pull/2540) [all] IsEmpty to check for HTTP status 204 +* [GH-2543](https://github.com/gophercloud/gophercloud/pull/2543) keystone: add v3 OS-FEDERATION mappings get operation +* [GH-2545](https://github.com/gophercloud/gophercloud/pull/2545) baremetal: add inspection_{started,finished}_at to Node +* [GH-2546](https://github.com/gophercloud/gophercloud/pull/2546) Drop train job for baremetal +* [GH-2549](https://github.com/gophercloud/gophercloud/pull/2549) objects: Clarify ExtractContent usage +* [GH-2550](https://github.com/gophercloud/gophercloud/pull/2550) keystone: add v3 OS-FEDERATION mappings update operation +* [GH-2552](https://github.com/gophercloud/gophercloud/pull/2552) objectstorage: Reject container names with a slash +* [GH-2555](https://github.com/gophercloud/gophercloud/pull/2555) nova: introduce servers.ListSimple along with the more detailed servers.List +* [GH-2558](https://github.com/gophercloud/gophercloud/pull/2558) Expand docs on 'clientconfig' usage +* [GH-2563](https://github.com/gophercloud/gophercloud/pull/2563) Support propagate_uplink_status for Ports +* [GH-2567](https://github.com/gophercloud/gophercloud/pull/2567) Fix invalid baremetal-introspection service type +* [GH-2568](https://github.com/gophercloud/gophercloud/pull/2568) Prefer github mirrors over opendev repos +* [GH-2571](https://github.com/gophercloud/gophercloud/pull/2571) Swift V1: support object versioning +* [GH-2572](https://github.com/gophercloud/gophercloud/pull/2572) networking v2: add extraroutes Add and Remove methods +* [GH-2573](https://github.com/gophercloud/gophercloud/pull/2573) Enable tests for object versioning +* [GH-2576](https://github.com/gophercloud/gophercloud/pull/2576) keystone: add v3 OS-FEDERATION mappings delete operation +* [GH-2578](https://github.com/gophercloud/gophercloud/pull/2578) Add periodic jobs for OpenStack zed release and reduce periodic jobs frequency +* [GH-2580](https://github.com/gophercloud/gophercloud/pull/2580) [neutron v2]: Add support for network segments update +* [GH-2583](https://github.com/gophercloud/gophercloud/pull/2583) Add missing rule protocol constants for IPIP +* [GH-2584](https://github.com/gophercloud/gophercloud/pull/2584) CI: workaround mongodb dependency for messaging and clustering master jobs +* [GH-2587](https://github.com/gophercloud/gophercloud/pull/2587) fix: Incorrect Documentation +* [GH-2593](https://github.com/gophercloud/gophercloud/pull/2593) Make TestMTUNetworkCRUDL deterministic +* [GH-2594](https://github.com/gophercloud/gophercloud/pull/2594) Bump actions/setup-go from 3 to 4 + + +## v1.2.0 (2023-01-27) + +Starting with this version, Gophercloud sends its actual version in the +user-agent string in the format `gophercloud/v1.2.0`. It no longer sends the +hardcoded string `gophercloud/2.0.0`. + +* [GH-2542](https://github.com/gophercloud/gophercloud/pull/2542) Add field hidden in containerinfra/v1/clustertemplates +* [GH-2537](https://github.com/gophercloud/gophercloud/pull/2537) Support value_specs for Ports +* [GH-2530](https://github.com/gophercloud/gophercloud/pull/2530) keystone: add v3 OS-FEDERATION mappings create operation +* [GH-2519](https://github.com/gophercloud/gophercloud/pull/2519) Modify user-agent header to ensure current gophercloud version is provided + +## v1.1.1 (2022-12-07) + +The GOPROXY cache for v1.1.0 was corrupted with a tag pointing to the wrong commit. This release fixes the problem by exposing a new release with the same content. + +Please use `v1.1.1` instead of `v1.1.0` to avoid cache issues. + +## v1.1.0 (2022-11-24) + +* [GH-2513](https://github.com/gophercloud/gophercloud/pull/2513) objectstorage: Do not parse NoContent responses +* [GH-2503](https://github.com/gophercloud/gophercloud/pull/2503) Bump golang.org/x/crypto +* [GH-2501](https://github.com/gophercloud/gophercloud/pull/2501) Staskraev/l3 agent scheduler +* [GH-2496](https://github.com/gophercloud/gophercloud/pull/2496) Manila: add Get for share-access-rules API +* [GH-2491](https://github.com/gophercloud/gophercloud/pull/2491) Add VipQosPolicyID to loadbalancer Create and Update +* [GH-2488](https://github.com/gophercloud/gophercloud/pull/2488) Add Persistance for octavia pools.UpdateOpts +* [GH-2487](https://github.com/gophercloud/gophercloud/pull/2487) Add Prometheus protocol for octavia listeners +* [GH-2482](https://github.com/gophercloud/gophercloud/pull/2482) Add createdAt, updatedAt and provisionUpdatedAt fields in Baremetal V1 nodes +* [GH-2479](https://github.com/gophercloud/gophercloud/pull/2479) Add service_types support for neutron subnet +* [GH-2477](https://github.com/gophercloud/gophercloud/pull/2477) Port CreatedAt and UpdatedAt: add back JSON tags +* [GH-2475](https://github.com/gophercloud/gophercloud/pull/2475) Support old time format for port CreatedAt and UpdatedAt +* [GH-2474](https://github.com/gophercloud/gophercloud/pull/2474) Implementing re-image volumeaction +* [GH-2470](https://github.com/gophercloud/gophercloud/pull/2470) keystone: add v3 limits GetEnforcementModel operation +* [GH-2468](https://github.com/gophercloud/gophercloud/pull/2468) keystone: add v3 OS-FEDERATION extension List Mappings +* [GH-2458](https://github.com/gophercloud/gophercloud/pull/2458) Fix typo in blockstorage/v3/attachments docs +* [GH-2456](https://github.com/gophercloud/gophercloud/pull/2456) Add support for Update for flavors +* [GH-2453](https://github.com/gophercloud/gophercloud/pull/2453) Add description to flavor +* [GH-2417](https://github.com/gophercloud/gophercloud/pull/2417) Neutron v2: ScheduleBGPSpeakerOpts, RemoveBGPSpeaker, Lis… + +## 1.0.0 (2022-08-29) + +UPGRADE NOTES + PROMISE OF COMPATIBILITY + +* Introducing Gophercloud v1! Like for every other release so far, all clients will upgrade automatically with `go get -d github.com/gophercloud/gophercloud` unless the dependency is pinned in `go.mod`. +* Gophercloud v1 comes with a promise of compatibility: no breaking changes are expected to merge before v2.0.0. + +IMPROVEMENTS + +* Added `compute.v2/extensions/services.Delete` [GH-2427](https://github.com/gophercloud/gophercloud/pull/2427) +* Added support for `standard-attr-revisions` to `networking/v2/networks`, `networking/v2/ports`, and `networking/v2/subnets` [GH-2437](https://github.com/gophercloud/gophercloud/pull/2437) +* Added `updated_at` and `created_at` fields to `networking/v2/ports.Port` [GH-2445](https://github.com/gophercloud/gophercloud/pull/2445) + +## 0.25.0 (May 30, 2022) + +BREAKING CHANGES + +* Replaced `blockstorage/noauth.NewBlockStorageNoAuth` with `NewBlockStorageNoAuthV2` and `NewBlockStorageNoAuthV3` [GH-2343](https://github.com/gophercloud/gophercloud/pull/2343) +* Renamed `blockstorage/extensions/schedulerstats.Capabilities`'s `GoodnessFuction` field to `GoodnessFunction` [GH-2346](https://github.com/gophercloud/gophercloud/pull/2346) + +IMPROVEMENTS + +* Added `RequestOpts.OmitHeaders` to provider client [GH-2315](https://github.com/gophercloud/gophercloud/pull/2315) +* Added `identity/v3/extensions/projectendpoints.List` [GH-2304](https://github.com/gophercloud/gophercloud/pull/2304) +* Added `identity/v3/extensions/projectendpoints.Create` [GH-2304](https://github.com/gophercloud/gophercloud/pull/2304) +* Added `identity/v3/extensions/projectendpoints.Delete` [GH-2304](https://github.com/gophercloud/gophercloud/pull/2304) +* Added protocol `any` to `networking/v2/extensions/security/rules.Create` [GH-2310](https://github.com/gophercloud/gophercloud/pull/2310) +* Added `REDIRECT_PREFIX` and `REDIRECT_HTTP_CODE` to `loadbalancer/v2/l7policies.Create` [GH-2324](https://github.com/gophercloud/gophercloud/pull/2324) +* Added `SOURCE_IP_PORT` LB method to `loadbalancer/v2/pools.Create` [GH-2300](https://github.com/gophercloud/gophercloud/pull/2300) +* Added `AllocatedCapacityGB` capability to `blockstorage/extensions/schedulerstats.Capabilities` [GH-2348](https://github.com/gophercloud/gophercloud/pull/2348) +* Added `Metadata` to `dns/v2/recordset.RecordSet` [GH-2353](https://github.com/gophercloud/gophercloud/pull/2353) +* Added missing fields to `compute/v2/extensions/servergroups.List` [GH-2355](https://github.com/gophercloud/gophercloud/pull/2355) +* Added missing labels fields to `containerinfra/v1/nodegroups` [GH-2377](https://github.com/gophercloud/gophercloud/pull/2377) +* Added missing fields to `loadbalancer/v2/listeners.Listener` [GH-2407](https://github.com/gophercloud/gophercloud/pull/2407) +* Added `identity/v3/limits.List` [GH-2360](https://github.com/gophercloud/gophercloud/pull/2360) +* Added `ParentProviderUUID` to `placement/v1/resourceproviders.Create` [GH-2356](https://github.com/gophercloud/gophercloud/pull/2356) +* Added `placement/v1/resourceproviders.Delete` [GH-2357](https://github.com/gophercloud/gophercloud/pull/2357) +* Added `placement/v1/resourceproviders.Get` [GH-2358](https://github.com/gophercloud/gophercloud/pull/2358) +* Added `placement/v1/resourceproviders.Update` [GH-2359](https://github.com/gophercloud/gophercloud/pull/2359) +* Added `networking/v2/extensions/bgp/peers.List` [GH-2241](https://github.com/gophercloud/gophercloud/pull/2241) +* Added `networking/v2/extensions/bgp/peers.Get` [GH-2241](https://github.com/gophercloud/gophercloud/pull/2241) +* Added `networking/v2/extensions/bgp/peers.Create` [GH-2388](https://github.com/gophercloud/gophercloud/pull/2388) +* Added `networking/v2/extensions/bgp/peers.Delete` [GH-2388](https://github.com/gophercloud/gophercloud/pull/2388) +* Added `networking/v2/extensions/bgp/peers.Update` [GH-2396](https://github.com/gophercloud/gophercloud/pull/2396) +* Added `networking/v2/extensions/bgp/speakers.Create` [GH-2395](https://github.com/gophercloud/gophercloud/pull/2395) +* Added `networking/v2/extensions/bgp/speakers.Delete` [GH-2395](https://github.com/gophercloud/gophercloud/pull/2395) +* Added `networking/v2/extensions/bgp/speakers.Update` [GH-2400](https://github.com/gophercloud/gophercloud/pull/2400) +* Added `networking/v2/extensions/bgp/speakers.AddBGPPeer` [GH-2400](https://github.com/gophercloud/gophercloud/pull/2400) +* Added `networking/v2/extensions/bgp/speakers.RemoveBGPPeer` [GH-2400](https://github.com/gophercloud/gophercloud/pull/2400) +* Added `networking/v2/extensions/bgp/speakers.GetAdvertisedRoutes` [GH-2406](https://github.com/gophercloud/gophercloud/pull/2406) +* Added `networking/v2/extensions/bgp/speakers.AddGatewayNetwork` [GH-2406](https://github.com/gophercloud/gophercloud/pull/2406) +* Added `networking/v2/extensions/bgp/speakers.RemoveGatewayNetwork` [GH-2406](https://github.com/gophercloud/gophercloud/pull/2406) +* Added `baremetal/v1/nodes.SetMaintenance` and `baremetal/v1/nodes.UnsetMaintenance` [GH-2384](https://github.com/gophercloud/gophercloud/pull/2384) +* Added `sharedfilesystems/v2/services.List` [GH-2350](https://github.com/gophercloud/gophercloud/pull/2350) +* Added `sharedfilesystems/v2/schedulerstats.List` [GH-2350](https://github.com/gophercloud/gophercloud/pull/2350) +* Added `sharedfilesystems/v2/schedulerstats.ListDetail` [GH-2350](https://github.com/gophercloud/gophercloud/pull/2350) +* Added ability to handle 502 and 504 errors [GH-2245](https://github.com/gophercloud/gophercloud/pull/2245) +* Added `IncludeSubtree` to `identity/v3/roles.ListAssignments` [GH-2411](https://github.com/gophercloud/gophercloud/pull/2411) + +## 0.24.0 (December 13, 2021) + +UPGRADE NOTES + +* Set Go minimum version to 1.14 [GH-2294](https://github.com/gophercloud/gophercloud/pull/2294) + +IMPROVEMENTS + +* Added `blockstorage/v3/qos.Get` [GH-2283](https://github.com/gophercloud/gophercloud/pull/2283) +* Added `blockstorage/v3/qos.Update` [GH-2283](https://github.com/gophercloud/gophercloud/pull/2283) +* Added `blockstorage/v3/qos.DeleteKeys` [GH-2283](https://github.com/gophercloud/gophercloud/pull/2283) +* Added `blockstorage/v3/qos.Associate` [GH-2284](https://github.com/gophercloud/gophercloud/pull/2284) +* Added `blockstorage/v3/qos.Disassociate` [GH-2284](https://github.com/gophercloud/gophercloud/pull/2284) +* Added `blockstorage/v3/qos.DisassociateAll` [GH-2284](https://github.com/gophercloud/gophercloud/pull/2284) +* Added `blockstorage/v3/qos.ListAssociations` [GH-2284](https://github.com/gophercloud/gophercloud/pull/2284) + +## 0.23.0 (November 12, 2021) + +IMPROVEMENTS + +* Added `networking/v2/extensions/agents.ListBGPSpeakers` [GH-2229](https://github.com/gophercloud/gophercloud/pull/2229) +* Added `networking/v2/extensions/bgp/speakers.BGPSpeaker` [GH-2229](https://github.com/gophercloud/gophercloud/pull/2229) +* Added `identity/v3/roles.Project.Domain` [GH-2235](https://github.com/gophercloud/gophercloud/pull/2235) +* Added `identity/v3/roles.User.Domain` [GH-2235](https://github.com/gophercloud/gophercloud/pull/2235) +* Added `identity/v3/roles.Group.Domain` [GH-2235](https://github.com/gophercloud/gophercloud/pull/2235) +* Added `loadbalancer/v2/pools.CreateOpts.Tags` [GH-2237](https://github.com/gophercloud/gophercloud/pull/2237) +* Added `loadbalancer/v2/pools.UpdateOpts.Tags` [GH-2237](https://github.com/gophercloud/gophercloud/pull/2237) +* Added `loadbalancer/v2/pools.Pool.Tags` [GH-2237](https://github.com/gophercloud/gophercloud/pull/2237) +* Added `networking/v2/extensions/bgp/speakers.List` [GH-2238](https://github.com/gophercloud/gophercloud/pull/2238) +* Added `networking/v2/extensions/bgp/speakers.Get` [GH-2238](https://github.com/gophercloud/gophercloud/pull/2238) +* Added `compute/v2/extensions/keypairs.CreateOpts.Type` [GH-2231](https://github.com/gophercloud/gophercloud/pull/2231) +* When doing Keystone re-authentification, keep the error if it failed [GH-2259](https://github.com/gophercloud/gophercloud/pull/2259) +* Added new loadbalancer pool monitor types (TLS-HELLO, UDP-CONNECT and SCTP) [GH-2237](https://github.com/gophercloud/gophercloud/pull/2261) + +## 0.22.0 (October 7, 2021) + +BREAKING CHANGES + +* The types of several Object Storage Update fields have been changed to pointers in order to allow the value to be unset via the HTTP headers: + * `objectstorage/v1/accounts.UpdateOpts.ContentType` + * `objectstorage/v1/accounts.UpdateOpts.DetectContentType` + * `objectstorage/v1/containers.UpdateOpts.ContainerRead` + * `objectstorage/v1/containers.UpdateOpts.ContainerSyncTo` + * `objectstorage/v1/containers.UpdateOpts.ContainerSyncKey` + * `objectstorage/v1/containers.UpdateOpts.ContainerWrite` + * `objectstorage/v1/containers.UpdateOpts.ContentType` + * `objectstorage/v1/containers.UpdateOpts.DetectContentType` + * `objectstorage/v1/objects.UpdateOpts.ContentDisposition` + * `objectstorage/v1/objects.UpdateOpts.ContentEncoding` + * `objectstorage/v1/objects.UpdateOpts.ContentType` + * `objectstorage/v1/objects.UpdateOpts.DeleteAfter` + * `objectstorage/v1/objects.UpdateOpts.DeleteAt` + * `objectstorage/v1/objects.UpdateOpts.DetectContentType` + +BUG FIXES + +* Fixed issue with not being able to unset Object Storage values via HTTP headers [GH-2218](https://github.com/gophercloud/gophercloud/pull/2218) + +IMPROVEMENTS + +* Added `compute/v2/servers.Server.ServerGroups` [GH-2217](https://github.com/gophercloud/gophercloud/pull/2217) +* Added `imageservice/v2/images.ReplaceImageProtected` to allow the `protected` field to be updated [GH-2221](https://github.com/gophercloud/gophercloud/pull/2221) +* More details added to the 404/Not Found error message [GH-2223](https://github.com/gophercloud/gophercloud/pull/2223) +* Added `openstack/baremetal/v1/nodes.CreateSubscriptionOpts.HttpHeaders` [GH-2224](https://github.com/gophercloud/gophercloud/pull/2224) + +## 0.21.0 (September 14, 2021) + +IMPROVEMENTS + +* Added `blockstorage/extensions/volumehost` [GH-2212](https://github.com/gophercloud/gophercloud/pull/2212) +* Added `loadbalancer/v2/listeners.CreateOpts.Tags` [GH-2214](https://github.com/gophercloud/gophercloud/pull/2214) +* Added `loadbalancer/v2/listeners.UpdateOpts.Tags` [GH-2214](https://github.com/gophercloud/gophercloud/pull/2214) +* Added `loadbalancer/v2/listeners.Listener.Tags` [GH-2214](https://github.com/gophercloud/gophercloud/pull/2214) + +## 0.20.0 (August 10, 2021) + +IMPROVEMENTS + +* Added `RetryFunc` to enable custom retry functions. [GH-2194](https://github.com/gophercloud/gophercloud/pull/2194) +* Added `openstack/baremetal/v1/nodes.GetVendorPassthruMethods` [GH-2201](https://github.com/gophercloud/gophercloud/pull/2201) +* Added `openstack/baremetal/v1/nodes.GetAllSubscriptions` [GH-2201](https://github.com/gophercloud/gophercloud/pull/2201) +* Added `openstack/baremetal/v1/nodes.GetSubscription` [GH-2201](https://github.com/gophercloud/gophercloud/pull/2201) +* Added `openstack/baremetal/v1/nodes.DeleteSubscription` [GH-2201](https://github.com/gophercloud/gophercloud/pull/2201) +* Added `openstack/baremetal/v1/nodes.CreateSubscription` [GH-2201](https://github.com/gophercloud/gophercloud/pull/2201) + +## 0.19.0 (July 22, 2021) + +NOTES / BREAKING CHANGES + +* `compute/v2/extensions/keypairs.List` now takes a `ListOptsBuilder` argument [GH-2186](https://github.com/gophercloud/gophercloud/pull/2186) +* `compute/v2/extensions/keypairs.Get` now takes a `GetOptsBuilder` argument [GH-2186](https://github.com/gophercloud/gophercloud/pull/2186) +* `compute/v2/extensions/keypairs.Delete` now takes a `DeleteOptsBuilder` argument [GH-2186](https://github.com/gophercloud/gophercloud/pull/2186) +* `compute/v2/extensions/hypervisors.List` now takes a `ListOptsBuilder` argument [GH-2187](https://github.com/gophercloud/gophercloud/pull/2187) + +IMPROVEMENTS + +* Added `blockstorage/v3/qos.List` [GH-2167](https://github.com/gophercloud/gophercloud/pull/2167) +* Added `compute/v2/extensions/volumeattach.CreateOpts.Tag` [GH-2177](https://github.com/gophercloud/gophercloud/pull/2177) +* Added `compute/v2/extensions/volumeattach.CreateOpts.DeleteOnTermination` [GH-2177](https://github.com/gophercloud/gophercloud/pull/2177) +* Added `compute/v2/extensions/volumeattach.VolumeAttachment.Tag` [GH-2177](https://github.com/gophercloud/gophercloud/pull/2177) +* Added `compute/v2/extensions/volumeattach.VolumeAttachment.DeleteOnTermination` [GH-2177](https://github.com/gophercloud/gophercloud/pull/2177) +* Added `db/v1/instances.Instance.Address` [GH-2179](https://github.com/gophercloud/gophercloud/pull/2179) +* Added `compute/v2/servers.ListOpts.AvailabilityZone` [GH-2098](https://github.com/gophercloud/gophercloud/pull/2098) +* Added `compute/v2/extensions/keypairs.ListOpts` [GH-2186](https://github.com/gophercloud/gophercloud/pull/2186) +* Added `compute/v2/extensions/keypairs.GetOpts` [GH-2186](https://github.com/gophercloud/gophercloud/pull/2186) +* Added `compute/v2/extensions/keypairs.DeleteOpts` [GH-2186](https://github.com/gophercloud/gophercloud/pull/2186) +* Added `objectstorage/v2/containers.GetHeader.Timestamp` [GH-2185](https://github.com/gophercloud/gophercloud/pull/2185) +* Added `compute/v2/extensions.ListOpts` [GH-2187](https://github.com/gophercloud/gophercloud/pull/2187) +* Added `sharedfilesystems/v2/shares.Share.CreateShareFromSnapshotSupport` [GH-2191](https://github.com/gophercloud/gophercloud/pull/2191) +* Added `compute/v2/servers.Network.Tag` for use in `CreateOpts` [GH-2193](https://github.com/gophercloud/gophercloud/pull/2193) + +## 0.18.0 (June 11, 2021) + +NOTES / BREAKING CHANGES + +* As of [GH-2160](https://github.com/gophercloud/gophercloud/pull/2160), Gophercloud no longer URL encodes Object Storage containers and object names. You can still encode them yourself before passing the names to the Object Storage functions. + +* `baremetal/v1/nodes.ListBIOSSettings` now takes three parameters. The third, new, parameter is `ListBIOSSettingsOptsBuilder` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) + +BUG FIXES + +* Fixed expected OK codes to use default codes [GH-2173](https://github.com/gophercloud/gophercloud/pull/2173) +* Fixed inablity to create sub-containers (objects with `/` in their name) [GH-2160](https://github.com/gophercloud/gophercloud/pull/2160) + +IMPROVEMENTS + +* Added `orchestration/v1/stacks.ListOpts.ShowHidden` [GH-2104](https://github.com/gophercloud/gophercloud/pull/2104) +* Added `loadbalancer/v2/listeners.ProtocolSCTP` [GH-2149](https://github.com/gophercloud/gophercloud/pull/2149) +* Added `loadbalancer/v2/listeners.CreateOpts.TLSVersions` [GH-2150](https://github.com/gophercloud/gophercloud/pull/2150) +* Added `loadbalancer/v2/listeners.UpdateOpts.TLSVersions` [GH-2150](https://github.com/gophercloud/gophercloud/pull/2150) +* Added `baremetal/v1/nodes.CreateOpts.NetworkData` [GH-2154](https://github.com/gophercloud/gophercloud/pull/2154) +* Added `baremetal/v1/nodes.Node.NetworkData` [GH-2154](https://github.com/gophercloud/gophercloud/pull/2154) +* Added `loadbalancer/v2/pools.ProtocolPROXYV2` [GH-2158](https://github.com/gophercloud/gophercloud/pull/2158) +* Added `loadbalancer/v2/pools.ProtocolSCTP` [GH-2158](https://github.com/gophercloud/gophercloud/pull/2158) +* Added `placement/v1/resourceproviders.GetAllocations` [GH-2162](https://github.com/gophercloud/gophercloud/pull/2162) +* Added `baremetal/v1/nodes.CreateOpts.BIOSInterface` [GH-2164](https://github.com/gophercloud/gophercloud/pull/2164) +* Added `baremetal/v1/nodes.Node.BIOSInterface` [GH-2164](https://github.com/gophercloud/gophercloud/pull/2164) +* Added `baremetal/v1/nodes.NodeValidation.BIOS` [GH-2164](https://github.com/gophercloud/gophercloud/pull/2164) +* Added `baremetal/v1/nodes.ListBIOSSettings` [GH-2171](https://github.com/gophercloud/gophercloud/pull/2171) +* Added `baremetal/v1/nodes.GetBIOSSetting` [GH-2171](https://github.com/gophercloud/gophercloud/pull/2171) +* Added `baremetal/v1/nodes.ListBIOSSettingsOpts` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.AttributeType` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.AllowableValues` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.LowerBound` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.UpperBound` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.MinLength` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.MaxLength` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.ReadOnly` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.ResetRequired` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) +* Added `baremetal/v1/nodes.BIOSSetting.Unique` [GH-2174](https://github.com/gophercloud/gophercloud/pull/2174) + +## 0.17.0 (April 9, 2021) + +IMPROVEMENTS + +* `networking/v2/extensions/quotas.QuotaDetail.Reserved` can handle both `int` and `string` values [GH-2126](https://github.com/gophercloud/gophercloud/pull/2126) +* Added `blockstorage/v3/volumetypes.ListExtraSpecs` [GH-2123](https://github.com/gophercloud/gophercloud/pull/2123) +* Added `blockstorage/v3/volumetypes.GetExtraSpec` [GH-2123](https://github.com/gophercloud/gophercloud/pull/2123) +* Added `blockstorage/v3/volumetypes.CreateExtraSpecs` [GH-2123](https://github.com/gophercloud/gophercloud/pull/2123) +* Added `blockstorage/v3/volumetypes.UpdateExtraSpec` [GH-2123](https://github.com/gophercloud/gophercloud/pull/2123) +* Added `blockstorage/v3/volumetypes.DeleteExtraSpec` [GH-2123](https://github.com/gophercloud/gophercloud/pull/2123) +* Added `identity/v3/roles.ListAssignmentOpts.IncludeNames` [GH-2133](https://github.com/gophercloud/gophercloud/pull/2133) +* Added `identity/v3/roles.AssignedRoles.Name` [GH-2133](https://github.com/gophercloud/gophercloud/pull/2133) +* Added `identity/v3/roles.Domain.Name` [GH-2133](https://github.com/gophercloud/gophercloud/pull/2133) +* Added `identity/v3/roles.Project.Name` [GH-2133](https://github.com/gophercloud/gophercloud/pull/2133) +* Added `identity/v3/roles.User.Name` [GH-2133](https://github.com/gophercloud/gophercloud/pull/2133) +* Added `identity/v3/roles.Group.Name` [GH-2133](https://github.com/gophercloud/gophercloud/pull/2133) +* Added `blockstorage/extensions/availabilityzones.List` [GH-2135](https://github.com/gophercloud/gophercloud/pull/2135) +* Added `blockstorage/v3/volumetypes.ListAccesses` [GH-2138](https://github.com/gophercloud/gophercloud/pull/2138) +* Added `blockstorage/v3/volumetypes.AddAccess` [GH-2138](https://github.com/gophercloud/gophercloud/pull/2138) +* Added `blockstorage/v3/volumetypes.RemoveAccess` [GH-2138](https://github.com/gophercloud/gophercloud/pull/2138) +* Added `blockstorage/v3/qos.Create` [GH-2140](https://github.com/gophercloud/gophercloud/pull/2140) +* Added `blockstorage/v3/qos.Delete` [GH-2140](https://github.com/gophercloud/gophercloud/pull/2140) + +## 0.16.0 (February 23, 2021) + +UPGRADE NOTES + +* `baremetal/v1/nodes.CleanStep.Interface` has changed from `string` to `StepInterface` [GH-2120](https://github.com/gophercloud/gophercloud/pull/2120) + +BUG FIXES + +* Fixed `xor` logic issues in `loadbalancers/v2/l7policies.CreateOpts` [GH-2087](https://github.com/gophercloud/gophercloud/pull/2087) +* Fixed `xor` logic issues in `loadbalancers/v2/listeners.CreateOpts` [GH-2087](https://github.com/gophercloud/gophercloud/pull/2087) +* Fixed `If-Modified-Since` so it's correctly sent in a `objectstorage/v1/objects.Download` request [GH-2108](https://github.com/gophercloud/gophercloud/pull/2108) +* Fixed `If-Unmodified-Since` so it's correctly sent in a `objectstorage/v1/objects.Download` request [GH-2108](https://github.com/gophercloud/gophercloud/pull/2108) + +IMPROVEMENTS + +* Added `blockstorage/extensions/limits.Get` [GH-2084](https://github.com/gophercloud/gophercloud/pull/2084) +* `clustering/v1/clusters.RemoveNodes` now returns an `ActionResult` [GH-2089](https://github.com/gophercloud/gophercloud/pull/2089) +* Added `identity/v3/projects.ListAvailable` [GH-2090](https://github.com/gophercloud/gophercloud/pull/2090) +* Added `blockstorage/extensions/backups.ListDetail` [GH-2085](https://github.com/gophercloud/gophercloud/pull/2085) +* Allow all ports to be removed in `networking/v2/extensions/fwaas_v2/groups.UpdateOpts` [GH-2073] +* Added `imageservice/v2/images.ListOpts.Hidden` [GH-2094](https://github.com/gophercloud/gophercloud/pull/2094) +* Added `imageservice/v2/images.CreateOpts.Hidden` [GH-2094](https://github.com/gophercloud/gophercloud/pull/2094) +* Added `imageservice/v2/images.ReplaceImageHidden` [GH-2094](https://github.com/gophercloud/gophercloud/pull/2094) +* Added `imageservice/v2/images.Image.Hidden` [GH-2094](https://github.com/gophercloud/gophercloud/pull/2094) +* Added `containerinfra/v1/clusters.CreateOpts.MasterLBEnabled` [GH-2102](https://github.com/gophercloud/gophercloud/pull/2102) +* Added the ability to define a custom function to handle "Retry-After" (429) responses [GH-2097](https://github.com/gophercloud/gophercloud/pull/2097) +* Added `baremetal/v1/nodes.JBOD` constant for the `RAIDLevel` type [GH-2103](https://github.com/gophercloud/gophercloud/pull/2103) +* Added support for Block Storage quotas of volume typed resources [GH-2109](https://github.com/gophercloud/gophercloud/pull/2109) +* Added `blockstorage/extensions/volumeactions.ChangeType` [GH-2113](https://github.com/gophercloud/gophercloud/pull/2113) +* Added `baremetal/v1/nodes.DeployStep` [GH-2120](https://github.com/gophercloud/gophercloud/pull/2120) +* Added `baremetal/v1/nodes.ProvisionStateOpts.DeploySteps` [GH-2120](https://github.com/gophercloud/gophercloud/pull/2120) +* Added `baremetal/v1/nodes.CreateOpts.AutomatedClean` [GH-2122](https://github.com/gophercloud/gophercloud/pull/2122) + +## 0.15.0 (December 27, 2020) + +BREAKING CHANGES + +* `compute/v2/extensions/servergroups.List` now takes a `ListOpts` parameter. You can pass `nil` if you don't need to use this. + +IMPROVEMENTS + +* Added `loadbalancer/v2/pools.CreateMemberOpts.Tags` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.UpdateMemberOpts.Backup` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.UpdateMemberOpts.MonitorAddress` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.UpdateMemberOpts.MonitorPort` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.UpdateMemberOpts.Tags` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.BatchUpdateMemberOpts.Backup` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.BatchUpdateMemberOpts.MonitorAddress` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.BatchUpdateMemberOpts.MonitorPort` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `loadbalancer/v2/pools.BatchUpdateMemberOpts.Tags` [GH-2056](https://github.com/gophercloud/gophercloud/pull/2056) +* Added `networking/v2/extensions/quotas.GetDetail` [GH-2061](https://github.com/gophercloud/gophercloud/pull/2061) +* Added `networking/v2/extensions/quotas.UpdateOpts.Trunk` [GH-2061](https://github.com/gophercloud/gophercloud/pull/2061) +* Added `objectstorage/v1/accounts.UpdateOpts.RemoveMetadata` [GH-2063](https://github.com/gophercloud/gophercloud/pull/2063) +* Added `objectstorage/v1/objects.UpdateOpts.RemoveMetadata` [GH-2063](https://github.com/gophercloud/gophercloud/pull/2063) +* Added `identity/v3/catalog.List` [GH-2067](https://github.com/gophercloud/gophercloud/pull/2067) +* Added `networking/v2/extensions/fwaas_v2/policies.List` [GH-2057](https://github.com/gophercloud/gophercloud/pull/2057) +* Added `networking/v2/extensions/fwaas_v2/policies.Create` [GH-2057](https://github.com/gophercloud/gophercloud/pull/2057) +* Added `networking/v2/extensions/fwaas_v2/policies.Get` [GH-2057](https://github.com/gophercloud/gophercloud/pull/2057) +* Added `networking/v2/extensions/fwaas_v2/policies.Update` [GH-2057](https://github.com/gophercloud/gophercloud/pull/2057) +* Added `networking/v2/extensions/fwaas_v2/policies.Delete` [GH-2057](https://github.com/gophercloud/gophercloud/pull/2057) +* Added `compute/v2/extensions/servergroups.ListOpts.AllProjects` [GH-2070](https://github.com/gophercloud/gophercloud/pull/2070) +* Added `objectstorage/v1/containers.CreateOpts.StoragePolicy` [GH-2075](https://github.com/gophercloud/gophercloud/pull/2075) +* Added `blockstorage/v3/snapshots.Update` [GH-2081](https://github.com/gophercloud/gophercloud/pull/2081) +* Added `loadbalancer/v2/l7policies.CreateOpts.Rules` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) +* Added `loadbalancer/v2/listeners.CreateOpts.DefaultPool` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) +* Added `loadbalancer/v2/listeners.CreateOpts.L7Policies` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) +* Added `loadbalancer/v2/listeners.Listener.DefaultPool` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) +* Added `loadbalancer/v2/loadbalancers.CreateOpts.Listeners` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) +* Added `loadbalancer/v2/loadbalancers.CreateOpts.Pools` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) +* Added `loadbalancer/v2/pools.CreateOpts.Members` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) +* Added `loadbalancer/v2/pools.CreateOpts.Monitor` [GH-2077](https://github.com/gophercloud/gophercloud/pull/2077) + + +## 0.14.0 (November 11, 2020) + +IMPROVEMENTS + +* Added `identity/v3/endpoints.Endpoint.Enabled` [GH-2030](https://github.com/gophercloud/gophercloud/pull/2030) +* Added `containerinfra/v1/clusters.Upgrade` [GH-2032](https://github.com/gophercloud/gophercloud/pull/2032) +* Added `compute/apiversions.List` [GH-2037](https://github.com/gophercloud/gophercloud/pull/2037) +* Added `compute/apiversions.Get` [GH-2037](https://github.com/gophercloud/gophercloud/pull/2037) +* Added `compute/v2/servers.ListOpts.IP` [GH-2038](https://github.com/gophercloud/gophercloud/pull/2038) +* Added `compute/v2/servers.ListOpts.IP6` [GH-2038](https://github.com/gophercloud/gophercloud/pull/2038) +* Added `compute/v2/servers.ListOpts.UserID` [GH-2038](https://github.com/gophercloud/gophercloud/pull/2038) +* Added `dns/v2/transfer/accept.List` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/accept.Get` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/accept.Create` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.List` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.Get` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.Update` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `dns/v2/transfer/requests.Delete` [GH-2041](https://github.com/gophercloud/gophercloud/pull/2041) +* Added `baremetal/v1/nodes.RescueWait` [GH-2052](https://github.com/gophercloud/gophercloud/pull/2052) +* Added `baremetal/v1/nodes.Unrescuing` [GH-2052](https://github.com/gophercloud/gophercloud/pull/2052) +* Added `networking/v2/extensions/fwaas_v2/groups.List` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Get` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Create` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Update` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) +* Added `networking/v2/extensions/fwaas_v2/groups.Delete` [GH-2050](https://github.com/gophercloud/gophercloud/pull/2050) + +BUG FIXES + +* Changed `networking/v2/extensions/layer3/routers.Routes` from `[]Route` to `*[]Route` [GH-2043](https://github.com/gophercloud/gophercloud/pull/2043) + +## 0.13.0 (September 27, 2020) + +IMPROVEMENTS + +* Added `ProtocolTerminatedHTTPS` as a valid listener protocol to `loadbalancer/v2/listeners` [GH-1992](https://github.com/gophercloud/gophercloud/pull/1992) +* Added `objectstorage/v1/objects.CreateTempURLOpts.Timestamp` [GH-1994](https://github.com/gophercloud/gophercloud/pull/1994) +* Added `compute/v2/extensions/schedulerhints.SchedulerHints.DifferentCell` [GH-2012](https://github.com/gophercloud/gophercloud/pull/2012) +* Added `loadbalancer/v2/quotas.Get` [GH-2010](https://github.com/gophercloud/gophercloud/pull/2010) +* Added `messaging/v2/queues.CreateOpts.EnableEncryptMessages` [GH-2016](https://github.com/gophercloud/gophercloud/pull/2016) +* Added `messaging/v2/queues.ListOpts.Name` [GH-2018](https://github.com/gophercloud/gophercloud/pull/2018) +* Added `messaging/v2/queues.ListOpts.WithCount` [GH-2018](https://github.com/gophercloud/gophercloud/pull/2018) +* Added `loadbalancer/v2/quotas.Update` [GH-2023](https://github.com/gophercloud/gophercloud/pull/2023) +* Added `loadbalancer/v2/loadbalancers.ListOpts.AvailabilityZone` [GH-2026](https://github.com/gophercloud/gophercloud/pull/2026) +* Added `loadbalancer/v2/loadbalancers.CreateOpts.AvailabilityZone` [GH-2026](https://github.com/gophercloud/gophercloud/pull/2026) +* Added `loadbalancer/v2/loadbalancers.LoadBalancer.AvailabilityZone` [GH-2026](https://github.com/gophercloud/gophercloud/pull/2026) +* Added `networking/v2/extensions/layer3/routers.ListL3Agents` [GH-2025](https://github.com/gophercloud/gophercloud/pull/2025) + +BUG FIXES + +* Fixed URL escaping in `objectstorage/v1/objects.CreateTempURL` [GH-1994](https://github.com/gophercloud/gophercloud/pull/1994) +* Remove unused `ServiceClient` from `compute/v2/servers.CreateOpts` [GH-2004](https://github.com/gophercloud/gophercloud/pull/2004) +* Changed `objectstorage/v1/objects.CreateOpts.DeleteAfter` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) +* Changed `objectstorage/v1/objects.CreateOpts.DeleteAt` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) +* Changed `objectstorage/v1/objects.UpdateOpts.DeleteAfter` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) +* Changed `objectstorage/v1/objects.UpdateOpts.DeleteAt` from `int` to `int64` [GH-2014](https://github.com/gophercloud/gophercloud/pull/2014) + + +## 0.12.0 (June 25, 2020) + +UPGRADE NOTES + +* The URL used in the `compute/v2/extensions/bootfromvolume` package has been changed from `os-volumes_boot` to `servers`. + +IMPROVEMENTS + +* The URL used in the `compute/v2/extensions/bootfromvolume` package has been changed from `os-volumes_boot` to `servers` [GH-1973](https://github.com/gophercloud/gophercloud/pull/1973) +* Modify `baremetal/v1/nodes.LogicalDisk.PhysicalDisks` type to support physical disks hints [GH-1982](https://github.com/gophercloud/gophercloud/pull/1982) +* Added `baremetalintrospection/httpbasic` which provides an HTTP Basic Auth client [GH-1986](https://github.com/gophercloud/gophercloud/pull/1986) +* Added `baremetal/httpbasic` which provides an HTTP Basic Auth client [GH-1983](https://github.com/gophercloud/gophercloud/pull/1983) +* Added `containerinfra/v1/clusters.CreateOpts.MergeLabels` [GH-1985](https://github.com/gophercloud/gophercloud/pull/1985) + +BUG FIXES + +* Changed `containerinfra/v1/clusters.Cluster.HealthStatusReason` from `string` to `map[string]interface{}` [GH-1968](https://github.com/gophercloud/gophercloud/pull/1968) +* Fixed marshalling of `blockstorage/extensions/backups.ImportBackup.Metadata` [GH-1967](https://github.com/gophercloud/gophercloud/pull/1967) +* Fixed typo of "OAUth" to "OAuth" in `identity/v3/extensions/oauth1` [GH-1969](https://github.com/gophercloud/gophercloud/pull/1969) +* Fixed goroutine leak during reauthentication [GH-1978](https://github.com/gophercloud/gophercloud/pull/1978) +* Changed `baremetalintrospection/v1/introspection.RootDiskType.Size` from `int` to `int64` [GH-1988](https://github.com/gophercloud/gophercloud/pull/1988) + +## 0.11.0 (May 14, 2020) + +UPGRADE NOTES + +* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930) +* All responses now have access to the returned headers. Please report any issues this has caused [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942) +* Changes have been made to the internal HTTP client to ensure response bodies are handled in a way that enables connections to be re-used more efficiently [GH-1952](https://github.com/gophercloud/gophercloud/pull/1952) + +IMPROVEMENTS + +* Added `objectstorage/v1/containers.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930) +* Added `objectstorage/v1/objects.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930) +* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930) +* All responses now have access to the returned headers [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942) +* Added `compute/v2/extensions/injectnetworkinfo.InjectNetworkInfo` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941) +* Added `compute/v2/extensions/resetnetwork.ResetNetwork` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941) +* Added `identity/v3/extensions/trusts.ListRoles` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939) +* Added `identity/v3/extensions/trusts.GetRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939) +* Added `identity/v3/extensions/trusts.CheckRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939) +* Added `identity/v3/extensions/oauth1.Create` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.CreateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.DeleteConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.ListConsumers` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.GetConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.UpdateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.RequestToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.AuthorizeToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.CreateAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.GetAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.RevokeAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.ListAccessTokens` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.ListAccessTokenRoles` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `identity/v3/extensions/oauth1.GetAccessTokenRole` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935) +* Added `networking/v2/extensions/agents.Update` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954) +* Added `networking/v2/extensions/agents.Delete` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954) +* Added `networking/v2/extensions/agents.ScheduleDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954) +* Added `networking/v2/extensions/agents.RemoveDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954) +* Added `identity/v3/projects.CreateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951) +* Added `identity/v3/projects.CreateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951) +* Added `identity/v3/projects.UpdateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951) +* Added `identity/v3/projects.UpdateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951) +* Added `identity/v3/projects.Project.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951) +* Added `identity/v3/projects.Options.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951) +* Added `imageservice/v2/images.Image.OpenStackImageImportMethods` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962) +* Added `imageservice/v2/images.Image.OpenStackImageStoreIDs` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962) + +BUG FIXES + +* Changed`identity/v3/extensions/trusts.Trust.RemainingUses` from `bool` to `int` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939) +* Changed `identity/v3/applicationcredentials.CreateOpts.ExpiresAt` from `string` to `*time.Time` [GH-1937](https://github.com/gophercloud/gophercloud/pull/1937) +* Fixed issue with unmarshalling/decoding slices of composed structs [GH-1964](https://github.com/gophercloud/gophercloud/pull/1964) + +## 0.10.0 (April 12, 2020) + +UPGRADE NOTES + +* The various `IDFromName` convenience functions have been moved to https://github.com/gophercloud/utils [GH-1897](https://github.com/gophercloud/gophercloud/pull/1897) +* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932) + +IMPROVEMENTS + +* Added `blockstorage/extensions/volumeactions.SetBootable` [GH-1891](https://github.com/gophercloud/gophercloud/pull/1891) +* Added `blockstorage/extensions/backups.Export` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894) +* Added `blockstorage/extensions/backups.Import` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894) +* Added `placement/v1/resourceproviders.GetTraits` [GH-1899](https://github.com/gophercloud/gophercloud/pull/1899) +* Added the ability to authenticate with Amazon EC2 Credentials [GH-1900](https://github.com/gophercloud/gophercloud/pull/1900) +* Added ability to list Nova services by binary and host [GH-1904](https://github.com/gophercloud/gophercloud/pull/1904) +* Added `compute/v2/extensions/services.Update` [GH-1902](https://github.com/gophercloud/gophercloud/pull/1902) +* Added system scope to v3 authentication [GH-1908](https://github.com/gophercloud/gophercloud/pull/1908) +* Added `identity/v3/extensions/ec2tokens.ValidateS3Token` [GH-1906](https://github.com/gophercloud/gophercloud/pull/1906) +* Added `containerinfra/v1/clusters.Cluster.HealthStatus` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910) +* Added `containerinfra/v1/clusters.Cluster.HealthStatusReason` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910) +* Added `loadbalancer/v2/amphorae.Failover` [GH-1912](https://github.com/gophercloud/gophercloud/pull/1912) +* Added `identity/v3/extensions/ec2credentials.List` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916) +* Added `identity/v3/extensions/ec2credentials.Get` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916) +* Added `identity/v3/extensions/ec2credentials.Create` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916) +* Added `identity/v3/extensions/ec2credentials.Delete` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916) +* Added `ErrUnexpectedResponseCode.ResponseHeader` [GH-1919](https://github.com/gophercloud/gophercloud/pull/1919) +* Added support for TOTP authentication [GH-1922](https://github.com/gophercloud/gophercloud/pull/1922) +* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932) +* Added `sharedfilesystems/v2/shares.GetExportLocation` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932) +* Added `sharedfilesystems/v2/shares.Revert` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931) +* Added `sharedfilesystems/v2/shares.ResetStatus` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931) +* Added `sharedfilesystems/v2/shares.ForceDelete` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931) +* Added `sharedfilesystems/v2/shares.Unmanage` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931) +* Added `blockstorage/v3/attachments.Create` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934) +* Added `blockstorage/v3/attachments.List` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934) +* Added `blockstorage/v3/attachments.Get` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934) +* Added `blockstorage/v3/attachments.Update` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934) +* Added `blockstorage/v3/attachments.Delete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934) +* Added `blockstorage/v3/attachments.Complete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934) + +BUG FIXES + +* Fixed issue with Orchestration `get_file` only being able to read JSON and YAML files [GH-1915](https://github.com/gophercloud/gophercloud/pull/1915) + +## 0.9.0 (March 10, 2020) + +UPGRADE NOTES + +* The way we implement new API result fields added by microversions has changed. Previously, we would declare a dedicated `ExtractFoo` function in a file called `microversions.go`. Now, we are declaring those fields inline of the original result struct as a pointer. [GH-1854](https://github.com/gophercloud/gophercloud/pull/1854) + +* `compute/v2/servers.CreateOpts.Networks` has changed from `[]Network` to `interface{}` in order to support creating servers that have no networks. [GH-1884](https://github.com/gophercloud/gophercloud/pull/1884) + +IMPROVEMENTS + +* Added `compute/v2/extensions/instanceactions.List` [GH-1848](https://github.com/gophercloud/gophercloud/pull/1848) +* Added `compute/v2/extensions/instanceactions.Get` [GH-1848](https://github.com/gophercloud/gophercloud/pull/1848) +* Added `networking/v2/ports.List.FixedIPs` [GH-1849](https://github.com/gophercloud/gophercloud/pull/1849) +* Added `identity/v3/extensions/trusts.List` [GH-1855](https://github.com/gophercloud/gophercloud/pull/1855) +* Added `identity/v3/extensions/trusts.Get` [GH-1855](https://github.com/gophercloud/gophercloud/pull/1855) +* Added `identity/v3/extensions/trusts.Trust.ExpiresAt` [GH-1857](https://github.com/gophercloud/gophercloud/pull/1857) +* Added `identity/v3/extensions/trusts.Trust.DeletedAt` [GH-1857](https://github.com/gophercloud/gophercloud/pull/1857) +* Added `compute/v2/extensions/instanceactions.InstanceActionDetail` [GH-1851](https://github.com/gophercloud/gophercloud/pull/1851) +* Added `compute/v2/extensions/instanceactions.Event` [GH-1851](https://github.com/gophercloud/gophercloud/pull/1851) +* Added `compute/v2/extensions/instanceactions.ListOpts` [GH-1858](https://github.com/gophercloud/gophercloud/pull/1858) +* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey` [GH-1864](https://github.com/gophercloud/gophercloud/pull/1864) +* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey2` [GH-1864](https://github.com/gophercloud/gophercloud/pull/1864) +* Added `placement/v1/resourceproviders.GetUsages` [GH-1862](https://github.com/gophercloud/gophercloud/pull/1862) +* Added `placement/v1/resourceproviders.GetInventories` [GH-1862](https://github.com/gophercloud/gophercloud/pull/1862) +* Added `imageservice/v2/images.ReplaceImageMinRam` [GH-1867](https://github.com/gophercloud/gophercloud/pull/1867) +* Added `objectstorage/v1/containers.UpdateOpts.TempURLKey` [GH-1865](https://github.com/gophercloud/gophercloud/pull/1865) +* Added `objectstorage/v1/containers.CreateOpts.TempURLKey2` [GH-1865](https://github.com/gophercloud/gophercloud/pull/1865) +* Added `blockstorage/extensions/volumetransfers.List` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869) +* Added `blockstorage/extensions/volumetransfers.Create` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869) +* Added `blockstorage/extensions/volumetransfers.Accept` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869) +* Added `blockstorage/extensions/volumetransfers.Get` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869) +* Added `blockstorage/extensions/volumetransfers.Delete` [GH-1869](https://github.com/gophercloud/gophercloud/pull/1869) +* Added `blockstorage/extensions/backups.RestoreFromBackup` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871) +* Added `blockstorage/v3/volumes.CreateOpts.BackupID` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871) +* Added `blockstorage/v3/volumes.Volume.BackupID` [GH-1871](https://github.com/gophercloud/gophercloud/pull/1871) +* Added `identity/v3/projects.ListOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882) +* Added `identity/v3/projects.ListOpts.TagsAny` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882) +* Added `identity/v3/projects.ListOpts.NotTags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882) +* Added `identity/v3/projects.ListOpts.NotTagsAny` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882) +* Added `identity/v3/projects.CreateOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882) +* Added `identity/v3/projects.UpdateOpts.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882) +* Added `identity/v3/projects.Project.Tags` [GH-1882](https://github.com/gophercloud/gophercloud/pull/1882) +* Changed `compute/v2/servers.CreateOpts.Networks` from `[]Network` to `interface{}` to support creating servers with no networks. [GH-1884](https://github.com/gophercloud/gophercloud/pull/1884) + + +BUG FIXES + +* Added support for `int64` headers, which were previously being silently dropped [GH-1860](https://github.com/gophercloud/gophercloud/pull/1860) +* Allow image properties with empty values [GH-1875](https://github.com/gophercloud/gophercloud/pull/1875) +* Fixed `compute/v2/extensions/extendedserverattributes.ServerAttributesExt.Userdata` JSON tag [GH-1881](https://github.com/gophercloud/gophercloud/pull/1881) + +## 0.8.0 (February 8, 2020) + +UPGRADE NOTES + +* The behavior of `keymanager/v1/acls.SetOpts` has changed. Instead of a struct, it is now `[]SetOpt`. See [GH-1816](https://github.com/gophercloud/gophercloud/pull/1816) for implementation details. + +IMPROVEMENTS + +* The result of `containerinfra/v1/clusters.Resize` now returns only the UUID when calling `Extract`. This is a backwards-breaking change from the previous struct that was returned [GH-1649](https://github.com/gophercloud/gophercloud/pull/1649) +* Added `compute/v2/extensions/shelveunshelve.Shelve` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799) +* Added `compute/v2/extensions/shelveunshelve.ShelveOffload` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799) +* Added `compute/v2/extensions/shelveunshelve.Unshelve` [GH-1799](https://github.com/gophercloud/gophercloud/pull/1799) +* Added `containerinfra/v1/nodegroups.Get` [GH-1774](https://github.com/gophercloud/gophercloud/pull/1774) +* Added `containerinfra/v1/nodegroups.List` [GH-1774](https://github.com/gophercloud/gophercloud/pull/1774) +* Added `orchestration/v1/resourcetypes.List` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806) +* Added `orchestration/v1/resourcetypes.GetSchema` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806) +* Added `orchestration/v1/resourcetypes.GenerateTemplate` [GH-1806](https://github.com/gophercloud/gophercloud/pull/1806) +* Added `keymanager/v1/acls.SetOpt` and changed `keymanager/v1/acls.SetOpts` to `[]SetOpt` [GH-1816](https://github.com/gophercloud/gophercloud/pull/1816) +* Added `blockstorage/apiversions.List` [GH-458](https://github.com/gophercloud/gophercloud/pull/458) +* Added `blockstorage/apiversions.Get` [GH-458](https://github.com/gophercloud/gophercloud/pull/458) +* Added `StatusCodeError` interface and `GetStatusCode` convenience method [GH-1820](https://github.com/gophercloud/gophercloud/pull/1820) +* Added pagination support to `compute/v2/extensions/usage.SingleTenant` [GH-1819](https://github.com/gophercloud/gophercloud/pull/1819) +* Added pagination support to `compute/v2/extensions/usage.AllTenants` [GH-1819](https://github.com/gophercloud/gophercloud/pull/1819) +* Added `placement/v1/resourceproviders.List` [GH-1815](https://github.com/gophercloud/gophercloud/pull/1815) +* Allow `CreateMemberOptsBuilder` to be passed in `loadbalancer/v2/pools.Create` [GH-1822](https://github.com/gophercloud/gophercloud/pull/1822) +* Added `Backup` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824) +* Added `MonitorAddress` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824) +* Added `MonitorPort` to `loadbalancer/v2/pools.CreateMemberOpts` [GH-1824](https://github.com/gophercloud/gophercloud/pull/1824) +* Changed `Impersonation` to a non-required field in `identity/v3/extensions/trusts.CreateOpts` [GH-1818](https://github.com/gophercloud/gophercloud/pull/1818) +* Added `InsertHeaders` to `loadbalancer/v2/listeners.UpdateOpts` [GH-1835](https://github.com/gophercloud/gophercloud/pull/1835) +* Added `NUMATopology` to `baremetalintrospection/v1/introspection.Data` [GH-1842](https://github.com/gophercloud/gophercloud/pull/1842) +* Added `placement/v1/resourceproviders.Create` [GH-1841](https://github.com/gophercloud/gophercloud/pull/1841) +* Added `blockstorage/extensions/volumeactions.UploadImageOpts.Visibility` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873) +* Added `blockstorage/extensions/volumeactions.UploadImageOpts.Protected` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873) +* Added `blockstorage/extensions/volumeactions.VolumeImage.Visibility` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873) +* Added `blockstorage/extensions/volumeactions.VolumeImage.Protected` [GH-1873](https://github.com/gophercloud/gophercloud/pull/1873) + +BUG FIXES + +* Changed `sort_key` to `sort_keys` in ` workflow/v2/crontriggers.ListOpts` [GH-1809](https://github.com/gophercloud/gophercloud/pull/1809) +* Allow `blockstorage/extensions/schedulerstats.Capabilities.MaxOverSubscriptionRatio` to accept both string and int/float responses [GH-1817](https://github.com/gophercloud/gophercloud/pull/1817) +* Fixed bug in `NewLoadBalancerV2` for situations when the LBaaS service was advertised without a `/v2.0` endpoint [GH-1829](https://github.com/gophercloud/gophercloud/pull/1829) +* Fixed JSON tags in `baremetal/v1/ports.UpdateOperation` [GH-1840](https://github.com/gophercloud/gophercloud/pull/1840) +* Fixed JSON tags in `networking/v2/extensions/lbaas/vips.commonResult.Extract()` [GH-1840](https://github.com/gophercloud/gophercloud/pull/1840) + +## 0.7.0 (December 3, 2019) + +IMPROVEMENTS + +* Allow a token to be used directly for authentication instead of generating a new token based on a given token [GH-1752](https://github.com/gophercloud/gophercloud/pull/1752) +* Moved `tags.ServerTagsExt` to servers.TagsExt` [GH-1760](https://github.com/gophercloud/gophercloud/pull/1760) +* Added `tags`, `tags-any`, `not-tags`, and `not-tags-any` to `compute/v2/servers.ListOpts` [GH-1759](https://github.com/gophercloud/gophercloud/pull/1759) +* Added `AccessRule` to `identity/v3/applicationcredentials` [GH-1758](https://github.com/gophercloud/gophercloud/pull/1758) +* Gophercloud no longer returns an error when multiple endpoints are found. Instead, it will choose the first endpoint and discard the others [GH-1766](https://github.com/gophercloud/gophercloud/pull/1766) +* Added `networking/v2/extensions/fwaas_v2/rules.Create` [GH-1768](https://github.com/gophercloud/gophercloud/pull/1768) +* Added `networking/v2/extensions/fwaas_v2/rules.Delete` [GH-1771](https://github.com/gophercloud/gophercloud/pull/1771) +* Added `loadbalancer/v2/providers.List` [GH-1765](https://github.com/gophercloud/gophercloud/pull/1765) +* Added `networking/v2/extensions/fwaas_v2/rules.Get` [GH-1772](https://github.com/gophercloud/gophercloud/pull/1772) +* Added `networking/v2/extensions/fwaas_v2/rules.Update` [GH-1776](https://github.com/gophercloud/gophercloud/pull/1776) +* Added `networking/v2/extensions/fwaas_v2/rules.List` [GH-1783](https://github.com/gophercloud/gophercloud/pull/1783) +* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.CreateOpts` [GH-1785](https://github.com/gophercloud/gophercloud/pull/1785) +* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.UpdateOpts` [GH-1786](https://github.com/gophercloud/gophercloud/pull/1786) +* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.Monitor` [GH-1787](https://github.com/gophercloud/gophercloud/pull/1787) +* Added `MaxRetriesDown` into `loadbalancer/v2/monitors.ListOpts` [GH-1788](https://github.com/gophercloud/gophercloud/pull/1788) +* Updated `go.mod` dependencies, specifically to account for CVE-2019-11840 with `golang.org/x/crypto` [GH-1793](https://github.com/gophercloud/gophercloud/pull/1788) + +## 0.6.0 (October 17, 2019) + +UPGRADE NOTES + +* The way reauthentication works has been refactored. This should not cause a problem, but please report bugs if it does. See [GH-1746](https://github.com/gophercloud/gophercloud/pull/1746) for more information. + +IMPROVEMENTS + +* Added `networking/v2/extensions/quotas.Get` [GH-1742](https://github.com/gophercloud/gophercloud/pull/1742) +* Added `networking/v2/extensions/quotas.Update` [GH-1747](https://github.com/gophercloud/gophercloud/pull/1747) +* Refactored the reauthentication implementation to use goroutines and added a check to prevent an infinite loop in certain situations. [GH-1746](https://github.com/gophercloud/gophercloud/pull/1746) + +BUG FIXES + +* Changed `Flavor` to `FlavorID` in `loadbalancer/v2/loadbalancers` [GH-1744](https://github.com/gophercloud/gophercloud/pull/1744) +* Changed `Flavor` to `FlavorID` in `networking/v2/extensions/lbaas_v2/loadbalancers` [GH-1744](https://github.com/gophercloud/gophercloud/pull/1744) +* The `go-yaml` dependency was updated to `v2.2.4` to fix possible DDOS vulnerabilities [GH-1751](https://github.com/gophercloud/gophercloud/pull/1751) + +## 0.5.0 (October 13, 2019) + +IMPROVEMENTS + +* Added `VolumeType` to `compute/v2/extensions/bootfromvolume.BlockDevice`[GH-1690](https://github.com/gophercloud/gophercloud/pull/1690) +* Added `networking/v2/extensions/layer3/portforwarding.List` [GH-1688](https://github.com/gophercloud/gophercloud/pull/1688) +* Added `networking/v2/extensions/layer3/portforwarding.Get` [GH-1698](https://github.com/gophercloud/gophercloud/pull/1696) +* Added `compute/v2/extensions/tags.ReplaceAll` [GH-1696](https://github.com/gophercloud/gophercloud/pull/1696) +* Added `compute/v2/extensions/tags.Add` [GH-1696](https://github.com/gophercloud/gophercloud/pull/1696) +* Added `networking/v2/extensions/layer3/portforwarding.Update` [GH-1703](https://github.com/gophercloud/gophercloud/pull/1703) +* Added `ExtractDomain` method to token results in `identity/v3/tokens` [GH-1712](https://github.com/gophercloud/gophercloud/pull/1712) +* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.CreateOpts` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710) +* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.UpdateOpts` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710) +* Added `AllowedCIDRs` to `loadbalancer/v2/listeners.Listener` [GH-1710](https://github.com/gophercloud/gophercloud/pull/1710) +* Added `compute/v2/extensions/tags.Add` [GH-1695](https://github.com/gophercloud/gophercloud/pull/1695) +* Added `compute/v2/extensions/tags.ReplaceAll` [GH-1694](https://github.com/gophercloud/gophercloud/pull/1694) +* Added `compute/v2/extensions/tags.Delete` [GH-1699](https://github.com/gophercloud/gophercloud/pull/1699) +* Added `compute/v2/extensions/tags.DeleteAll` [GH-1700](https://github.com/gophercloud/gophercloud/pull/1700) +* Added `ImageStatusImporting` as an image status [GH-1725](https://github.com/gophercloud/gophercloud/pull/1725) +* Added `ByPath` to `baremetalintrospection/v1/introspection.RootDiskType` [GH-1730](https://github.com/gophercloud/gophercloud/pull/1730) +* Added `AttachedVolumes` to `compute/v2/servers.Server` [GH-1732](https://github.com/gophercloud/gophercloud/pull/1732) +* Enable unmarshaling server tags to a `compute/v2/servers.Server` struct [GH-1734] +* Allow setting an empty members list in `loadbalancer/v2/pools.BatchUpdateMembers` [GH-1736](https://github.com/gophercloud/gophercloud/pull/1736) +* Allow unsetting members' subnet ID and name in `loadbalancer/v2/pools.BatchUpdateMemberOpts` [GH-1738](https://github.com/gophercloud/gophercloud/pull/1738) + +BUG FIXES + +* Changed struct type for options in `networking/v2/extensions/lbaas_v2/listeners` to `UpdateOptsBuilder` interface instead of specific UpdateOpts type [GH-1705](https://github.com/gophercloud/gophercloud/pull/1705) +* Changed struct type for options in `networking/v2/extensions/lbaas_v2/loadbalancers` to `UpdateOptsBuilder` interface instead of specific UpdateOpts type [GH-1706](https://github.com/gophercloud/gophercloud/pull/1706) +* Fixed issue with `blockstorage/v1/volumes.Create` where the response was expected to be 202 [GH-1720](https://github.com/gophercloud/gophercloud/pull/1720) +* Changed `DefaultTlsContainerRef` from `string` to `*string` in `loadbalancer/v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723) +* Changed `SniContainerRefs` from `[]string{}` to `*[]string{}` in `loadbalancer/v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723) +* Changed `DefaultTlsContainerRef` from `string` to `*string` in `networking/v2/extensions/lbaas_v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723) +* Changed `SniContainerRefs` from `[]string{}` to `*[]string{}` in `networking/v2/extensions/lbaas_v2/listeners.UpdateOpts` to allow the value to be removed during update. [GH-1723](https://github.com/gophercloud/gophercloud/pull/1723) + + +## 0.4.0 (September 3, 2019) + +IMPROVEMENTS + +* Added `blockstorage/extensions/quotasets.results.QuotaSet.Groups` [GH-1668](https://github.com/gophercloud/gophercloud/pull/1668) +* Added `blockstorage/extensions/quotasets.results.QuotaUsageSet.Groups` [GH-1668](https://github.com/gophercloud/gophercloud/pull/1668) +* Added `containerinfra/v1/clusters.CreateOpts.FixedNetwork` [GH-1674](https://github.com/gophercloud/gophercloud/pull/1674) +* Added `containerinfra/v1/clusters.CreateOpts.FixedSubnet` [GH-1676](https://github.com/gophercloud/gophercloud/pull/1676) +* Added `containerinfra/v1/clusters.CreateOpts.FloatingIPEnabled` [GH-1677](https://github.com/gophercloud/gophercloud/pull/1677) +* Added `CreatedAt` and `UpdatedAt` to `loadbalancers/v2/loadbalancers.LoadBalancer` [GH-1681](https://github.com/gophercloud/gophercloud/pull/1681) +* Added `networking/v2/extensions/layer3/portforwarding.Create` [GH-1651](https://github.com/gophercloud/gophercloud/pull/1651) +* Added `networking/v2/extensions/agents.ListDHCPNetworks` [GH-1686](https://github.com/gophercloud/gophercloud/pull/1686) +* Added `networking/v2/extensions/layer3/portforwarding.Delete` [GH-1652](https://github.com/gophercloud/gophercloud/pull/1652) +* Added `compute/v2/extensions/tags.List` [GH-1679](https://github.com/gophercloud/gophercloud/pull/1679) +* Added `compute/v2/extensions/tags.Check` [GH-1679](https://github.com/gophercloud/gophercloud/pull/1679) + +BUG FIXES + +* Changed `identity/v3/endpoints.ListOpts.RegionID` from `int` to `string` [GH-1664](https://github.com/gophercloud/gophercloud/pull/1664) +* Fixed issue where older time formats in some networking APIs/resources were unable to be parsed [GH-1671](https://github.com/gophercloud/gophercloud/pull/1664) +* Changed `SATA`, `SCSI`, and `SAS` types to `InterfaceType` in `baremetal/v1/nodes` [GH-1683] + +## 0.3.0 (July 31, 2019) + +IMPROVEMENTS + +* Added `baremetal/apiversions.List` [GH-1577](https://github.com/gophercloud/gophercloud/pull/1577) +* Added `baremetal/apiversions.Get` [GH-1577](https://github.com/gophercloud/gophercloud/pull/1577) +* Added `compute/v2/extensions/servergroups.CreateOpts.Policy` [GH-1636](https://github.com/gophercloud/gophercloud/pull/1636) +* Added `identity/v3/extensions/trusts.Create` [GH-1644](https://github.com/gophercloud/gophercloud/pull/1644) +* Added `identity/v3/extensions/trusts.Delete` [GH-1644](https://github.com/gophercloud/gophercloud/pull/1644) +* Added `CreatedAt` and `UpdatedAt` to `networking/v2/extensions/layer3/floatingips.FloatingIP` [GH-1647](https://github.com/gophercloud/gophercloud/issues/1646) +* Added `CreatedAt` and `UpdatedAt` to `networking/v2/extensions/security/groups.SecGroup` [GH-1654](https://github.com/gophercloud/gophercloud/issues/1654) +* Added `CreatedAt` and `UpdatedAt` to `networking/v2/networks.Network` [GH-1657](https://github.com/gophercloud/gophercloud/issues/1657) +* Added `keymanager/v1/containers.CreateSecretRef` [GH-1659](https://github.com/gophercloud/gophercloud/issues/1659) +* Added `keymanager/v1/containers.DeleteSecretRef` [GH-1659](https://github.com/gophercloud/gophercloud/issues/1659) +* Added `sharedfilesystems/v2/shares.GetMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656) +* Added `sharedfilesystems/v2/shares.GetMetadatum` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656) +* Added `sharedfilesystems/v2/shares.SetMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656) +* Added `sharedfilesystems/v2/shares.UpdateMetadata` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656) +* Added `sharedfilesystems/v2/shares.DeleteMetadatum` [GH-1656](https://github.com/gophercloud/gophercloud/issues/1656) +* Added `sharedfilesystems/v2/sharetypes.IDFromName` [GH-1662](https://github.com/gophercloud/gophercloud/issues/1662) + + + +BUG FIXES + +* Changed `baremetal/v1/nodes.CleanStep.Args` from `map[string]string` to `map[string]interface{}` [GH-1638](https://github.com/gophercloud/gophercloud/pull/1638) +* Removed `URLPath` and `ExpectedCodes` from `loadbalancer/v2/monitors.ToMonitorCreateMap` since Octavia now provides default values when these fields are not specified [GH-1640](https://github.com/gophercloud/gophercloud/pull/1540) + + +## 0.2.0 (June 17, 2019) + +IMPROVEMENTS + +* Added `networking/v2/extensions/qos/rules.ListBandwidthLimitRules` [GH-1584](https://github.com/gophercloud/gophercloud/pull/1584) +* Added `networking/v2/extensions/qos/rules.GetBandwidthLimitRule` [GH-1584](https://github.com/gophercloud/gophercloud/pull/1584) +* Added `networking/v2/extensions/qos/rules.CreateBandwidthLimitRule` [GH-1584](https://github.com/gophercloud/gophercloud/pull/1584) +* Added `networking/v2/extensions/qos/rules.UpdateBandwidthLimitRule` [GH-1589](https://github.com/gophercloud/gophercloud/pull/1589) +* Added `networking/v2/extensions/qos/rules.DeleteBandwidthLimitRule` [GH-1590](https://github.com/gophercloud/gophercloud/pull/1590) +* Added `networking/v2/extensions/qos/policies.List` [GH-1591](https://github.com/gophercloud/gophercloud/pull/1591) +* Added `networking/v2/extensions/qos/policies.Get` [GH-1593](https://github.com/gophercloud/gophercloud/pull/1593) +* Added `networking/v2/extensions/qos/rules.ListDSCPMarkingRules` [GH-1594](https://github.com/gophercloud/gophercloud/pull/1594) +* Added `networking/v2/extensions/qos/policies.Create` [GH-1595](https://github.com/gophercloud/gophercloud/pull/1595) +* Added `compute/v2/extensions/diagnostics.Get` [GH-1592](https://github.com/gophercloud/gophercloud/pull/1592) +* Added `networking/v2/extensions/qos/policies.Update` [GH-1603](https://github.com/gophercloud/gophercloud/pull/1603) +* Added `networking/v2/extensions/qos/policies.Delete` [GH-1603](https://github.com/gophercloud/gophercloud/pull/1603) +* Added `networking/v2/extensions/qos/rules.CreateDSCPMarkingRule` [GH-1605](https://github.com/gophercloud/gophercloud/pull/1605) +* Added `networking/v2/extensions/qos/rules.UpdateDSCPMarkingRule` [GH-1605](https://github.com/gophercloud/gophercloud/pull/1605) +* Added `networking/v2/extensions/qos/rules.GetDSCPMarkingRule` [GH-1609](https://github.com/gophercloud/gophercloud/pull/1609) +* Added `networking/v2/extensions/qos/rules.DeleteDSCPMarkingRule` [GH-1609](https://github.com/gophercloud/gophercloud/pull/1609) +* Added `networking/v2/extensions/qos/rules.ListMinimumBandwidthRules` [GH-1615](https://github.com/gophercloud/gophercloud/pull/1615) +* Added `networking/v2/extensions/qos/rules.GetMinimumBandwidthRule` [GH-1615](https://github.com/gophercloud/gophercloud/pull/1615) +* Added `networking/v2/extensions/qos/rules.CreateMinimumBandwidthRule` [GH-1615](https://github.com/gophercloud/gophercloud/pull/1615) +* Added `Hostname` to `baremetalintrospection/v1/introspection.Data` [GH-1627](https://github.com/gophercloud/gophercloud/pull/1627) +* Added `networking/v2/extensions/qos/rules.UpdateMinimumBandwidthRule` [GH-1624](https://github.com/gophercloud/gophercloud/pull/1624) +* Added `networking/v2/extensions/qos/rules.DeleteMinimumBandwidthRule` [GH-1624](https://github.com/gophercloud/gophercloud/pull/1624) +* Added `networking/v2/extensions/qos/ruletypes.GetRuleType` [GH-1625](https://github.com/gophercloud/gophercloud/pull/1625) +* Added `Extra` to `baremetalintrospection/v1/introspection.Data` [GH-1611](https://github.com/gophercloud/gophercloud/pull/1611) +* Added `blockstorage/extensions/volumeactions.SetImageMetadata` [GH-1621](https://github.com/gophercloud/gophercloud/pull/1621) + +BUG FIXES + +* Updated `networking/v2/extensions/qos/rules.UpdateBandwidthLimitRule` to use return code 200 [GH-1606](https://github.com/gophercloud/gophercloud/pull/1606) +* Fixed bug in `compute/v2/extensions/schedulerhints.SchedulerHints.Query` where contents will now be marshalled to a string [GH-1620](https://github.com/gophercloud/gophercloud/pull/1620) + +## 0.1.0 (May 27, 2019) + +Initial tagged release. diff --git a/vendor/github.com/gophercloud/gophercloud/v2/LICENSE b/vendor/github.com/gophercloud/gophercloud/v2/LICENSE new file mode 100644 index 0000000..c3f4f2f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/LICENSE @@ -0,0 +1,192 @@ +Copyright 2012-2013 Rackspace, Inc. +Copyright Gophercloud authors + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/vendor/github.com/gophercloud/gophercloud/v2/Makefile b/vendor/github.com/gophercloud/gophercloud/v2/Makefile new file mode 100644 index 0000000..128beec --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/Makefile @@ -0,0 +1,109 @@ +undefine GOFLAGS + +GOLANGCI_LINT_VERSION?=v1.57.1 + +ifeq ($(shell command -v podman 2> /dev/null),) + RUNNER=docker +else + RUNNER=podman +endif + +# if the golangci-lint steps fails with the following error message: +# +# directory prefix . does not contain main module or its selected dependencies +# +# you probably have to fix the SELinux security context for root directory plus your cache +# +# chcon -Rt svirt_sandbox_file_t . +# chcon -Rt svirt_sandbox_file_t ~/.cache/golangci-lint +lint: + $(RUNNER) run -t --rm \ + -v $(shell pwd):/app \ + -v ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION):/root/.cache \ + -w /app \ + -e GOFLAGS="-tags=acceptance" \ + golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint run +.PHONY: lint + +unit: + go test ./... +.PHONY: unit + +coverage: + go test -covermode count -coverprofile cover.out -coverpkg=./... ./... +.PHONY: coverage + +acceptance: acceptance-baremetal acceptance-blockstorage acceptance-compute acceptance-container acceptance-containerinfra acceptance-db acceptance-dns acceptance-identity acceptance-imageservice acceptance-keymanager acceptance-loadbalancer acceptance-messaging acceptance-networking acceptance-objectstorage acceptance-orchestration acceptance-placement acceptance-sharedfilesystems acceptance-workflow +.PHONY: acceptance + +acceptance-baremetal: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/baremetal/... +.PHONY: acceptance-baremetal + +acceptance-blockstorage: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/blockstorage/... +.PHONY: acceptance-blockstorage + +acceptance-compute: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/compute/... +.PHONY: acceptance-compute + +acceptance-container: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/container/... +.PHONY: acceptance-container + +acceptance-containerinfra: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/containerinfra/... +.PHONY: acceptance-containerinfra + +acceptance-db: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/db/... +.PHONY: acceptance-db + +acceptance-dns: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/dns/... +.PHONY: acceptance-dns + +acceptance-identity: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/identity/... +.PHONY: acceptance-identity + +acceptance-image: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/imageservice/... +.PHONY: acceptance-image + +acceptance-keymanager: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/keymanager/... +.PHONY: acceptance-keymanager + +acceptance-loadbalancer: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/loadbalancer/... +.PHONY: acceptance-loadbalancer + +acceptance-messaging: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/messaging/... +.PHONY: acceptance-messaging + +acceptance-networking: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/networking/... +.PHONY: acceptance-networking + +acceptance-objectstorage: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/objectstorage/... +.PHONY: acceptance-objectstorage + +acceptance-orchestration: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/orchestration/... +.PHONY: acceptance-orchestration + +acceptance-placement: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/placement/... +.PHONY: acceptance-placement + +acceptance-sharedfilesystems: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/sharedfilesystems/... +.PHONY: acceptance-sharefilesystems + +acceptance-workflow: + go test -tags "fixtures acceptance" ./internal/acceptance/openstack/workflow/... +.PHONY: acceptance-workflow diff --git a/vendor/github.com/gophercloud/gophercloud/v2/README.md b/vendor/github.com/gophercloud/gophercloud/v2/README.md new file mode 100644 index 0000000..e9ba39b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/README.md @@ -0,0 +1,254 @@ +# Gophercloud: an OpenStack SDK for Go +[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master) + +[Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud/v2) + +Gophercloud is a Go SDK for OpenStack. + +Join us on kubernetes slack, on [#gophercloud](https://kubernetes.slack.com/archives/C05G4NJ6P6X). Visit [slack.k8s.io](https://slack.k8s.io) for an invitation. + +> **Note** +> This branch contains the current stable branch of Gophercloud: `v2`. +> The legacy stable version can be found in the `v1` branch. + +## How to install + +Reference a Gophercloud package in your code: + +```go +import "github.com/gophercloud/gophercloud/v2" +``` + +Then update your `go.mod`: + +```shell +go mod tidy +``` + +## Getting started + +### Credentials + +Because you'll be hitting an API, you will need to retrieve your OpenStack +credentials and either store them in a `clouds.yaml` file, as environment +variables, or in your local Go files. The first method is recommended because +it decouples credential information from source code, allowing you to push the +latter to your version control system without any security risk. + +You will need to retrieve the following: + +* A valid Keystone identity URL +* Credentials. These can be a username/password combo, a set of Application + Credentials, a pre-generated token, or any other supported authentication + mechanism. + +For users who have the OpenStack dashboard installed, there's a shortcut. If +you visit the `project/api_access` path in Horizon and click on the +"Download OpenStack RC File" button at the top right hand corner, you can +download either a `clouds.yaml` file or an `openrc` bash file that exports all +of your access details to environment variables. To use the `clouds.yaml` file, +place it at `~/.config/openstack/clouds.yaml`. To use the `openrc` file, run +`source openrc` and you will be prompted for your password. + +### Gophercloud authentication + +Gophercloud authentication is organized into two layered abstractions: +* `ProviderClient` holds the authentication token and can be used to build a + `ServiceClient`. +* `ServiceClient` specializes against one specific OpenStack module and can + directly be used to make API calls. + +A provider client is a top-level client that all of your OpenStack service +clients derive from. The provider contains all of the authentication details +that allow your Go code to access the API - such as the base URL and token ID. + +One single Provider client can be used to build as many Service clients as needed. + +**With `clouds.yaml`** + +```go +package main + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/gophercloud/v2/openstack/config" + "github.com/gophercloud/gophercloud/v2/openstack/config/clouds" +) + +func main() { + ctx := context.Background() + + // Fetch coordinates from a `cloud.yaml` in the current directory, or + // in the well-known config directories (different for each operating + // system). + authOptions, endpointOptions, tlsConfig, err := clouds.Parse() + if err != nil { + panic(err) + } + + // Call Keystone to get an authentication token, and use it to + // construct a ProviderClient. All functions hitting the OpenStack API + // accept a `context.Context` to enable tracing and cancellation. + providerClient, err := config.NewProviderClient(ctx, authOptions, config.WithTLSConfig(tlsConfig)) + if err != nil { + panic(err) + } + + // Use the ProviderClient and the endpoint options fetched from + // `clouds.yaml` to build a service client: a compute client in this + // case. Note that the contructor does not accept a `context.Context`: + // no further call to the OpenStack API is needed at this stage. + computeClient, err := openstack.NewComputeV2(providerClient, endpointOptions) + if err != nil { + panic(err) + } + + // use the computeClient +} +``` + +**With environment variables (`openrc`)** + +Gophercloud can parse the environment variables set by running `source openrc`: + +```go +package main + +import ( + "context" + "os" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" +) + +func main() { + ctx := context.Background() + + opts, err := openstack.AuthOptionsFromEnv() + if err != nil { + panic(err) + } + + providerClient, err := openstack.AuthenticatedClient(ctx, opts) + if err != nil { + panic(err) + } + + computeClient, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + if err != nil { + panic(err) + } + + // use the computeClient +} +``` + +**Manually** + +You can also generate a "Provider" by passing in your credentials +explicitly: + +```go +package main + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" +) + +func main() { + ctx := context.Background() + + providerClient, err := openstack.AuthenticatedClient(ctx, gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "username", + Password: "password", + }) + if err != nil { + panic(err) + } + + computeClient, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{ + Region: "RegionName", + }) + if err != nil { + panic(err) + } + + // use the computeClient +} +``` + +### Provision a server + +We can use the Compute service client generated above for any Compute API +operation we want. In our case, we want to provision a new server. To do this, +we invoke the `Create` method and pass in the flavor ID (hardware +specification) and image ID (operating system) we're interested in: + +```go +import "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" + +func main() { + // [...] + + server, err := servers.Create(context.TODO(), computeClient, servers.CreateOpts{ + Name: "My new server!", + FlavorRef: "flavor_id", + ImageRef: "image_id", + }).Extract() + + // [...] +``` + +The above code sample creates a new server with the parameters, and returns a +[`servers.Server`](https://pkg.go.dev/github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers#Server). + +## Supported Services + +| **Service** | **Name** | **Module** | **1.x** | **2.x** | +|:------------------------:|------------------|:----------------------------------:|:-------:|:-------:| +| Baremetal | Ironic | `openstack/baremetal` | ✔ | ✔ | +| Baremetal Introspection | Ironic Inspector | `openstack/baremetalintrospection` | ✔ | ✔ | +| Block Storage | Cinder | `openstack/blockstorage` | ✔ | ✔ | +| Clustering | Senlin | `openstack/clustering` | ✔ | ✘ | +| Compute | Nova | `openstack/compute` | ✔ | ✔ | +| Container | Zun | `openstack/container` | ✔ | ✔ | +| Container Infrastructure | Magnum | `openstack/containerinfra` | ✔ | ✔ | +| Database | Trove | `openstack/db` | ✔ | ✔ | +| DNS | Designate | `openstack/dns` | ✔ | ✔ | +| Identity | Keystone | `openstack/identity` | ✔ | ✔ | +| Image | Glance | `openstack/image` | ✔ | ✔ | +| Key Management | Barbican | `openstack/keymanager` | ✔ | ✔ | +| Load Balancing | Octavia | `openstack/loadbalancer` | ✔ | ✔ | +| Messaging | Zaqar | `openstack/messaging` | ✔ | ✔ | +| Networking | Neutron | `openstack/networking` | ✔ | ✔ | +| Object Storage | Swift | `openstack/objectstorage` | ✔ | ✔ | + +## Advanced Usage + +Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works. + +## Backwards-Compatibility Guarantees + +Gophercloud versioning follows [semver](https://semver.org/spec/v2.0.0.html). + +Before `v1.0.0`, there were no guarantees. Starting with v1, there will be no breaking changes within a major release. + +See the [Release instructions](./RELEASE.md). + +## Contributing + +See the [contributing guide](./.github/CONTRIBUTING.md). + +## Help and feedback + +If you're struggling with something or have spotted a potential bug, feel free +to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues). diff --git a/vendor/github.com/gophercloud/gophercloud/v2/RELEASE.md b/vendor/github.com/gophercloud/gophercloud/v2/RELEASE.md new file mode 100644 index 0000000..6490ed8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/RELEASE.md @@ -0,0 +1,79 @@ +# Gophercloud release + +## Contributions + +### The semver label + +Gophercloud follows [semver](https://semver.org/). + +Each Pull request must have a label indicating its impact on the API: +* `semver:patch` for changes that don't impact the API +* `semver:minor` for changes that impact the API in a backwards-compatible fashion +* `semver:major` for changes that introduce a breaking change in the API + +Automation prevents merges if the label is not present. + +### Metadata + +The release notes for a given release are generated based on the PR title: make +sure that the PR title is descriptive. + +## Release of a new version + +Requirements: +* [`gh`](https://github.com/cli/cli) +* [`jq`](https://stedolan.github.io/jq/) + +### Step 1: Collect all PRs since the last release + +Supposing that the base release is `v1.2.0`: + +``` +for commit_sha in $(git log --pretty=format:"%h" v1.2.0..HEAD); do + gh pr list --search "$commit_sha" --state merged --json number,title,labels,url +done | jq '.[]' | jq --slurp 'unique_by(.number)' > prs.json +``` + +This JSON file will be useful later. + +### Step 2: Determine the version + +In order to determine the version of the next release, we first check that no incompatible change is detected in the code that has been merged since the last release. This step can be automated with the `gorelease` tool: + +```shell +gorelease | grep -B2 -A0 '^## incompatible changes' +``` + +If the tool detects incompatible changes outside a `testing` package, then the bump is major. + +Next, we check all PRs merged since the last release using the file `prs.json` that we generated above. + +* Find PRs labeled with `semver:major`: `jq 'map(select(contains({labels: [{name: "semver:major"}]}) ))' prs.json` +* Find PRs labeled with `semver:minor`: `jq 'map(select(contains({labels: [{name: "semver:minor"}]}) ))' prs.json` + +The highest semver descriptor determines the release bump. + +### Step 3: Release notes and version string + +Once all PRs have a sensible title, generate the release notes: + +```shell +jq -r '.[] | "* [GH-\(.number)](\(.url)) \(.title)"' prs.json +``` + +Add that to the top of `CHANGELOG.md`. Also add any information that could be useful to consumers willing to upgrade. + +**Set the new version string in the `DefaultUserAgent` constant in `provider_client.go`.** + +Create a PR with these two changes. The new PR should be labeled with the semver label corresponding to the type of bump. + +### Step 3: Git tag and Github release + +The Go mod system relies on Git tags. In order to simulate a review mechanism, we rely on Github to create the tag through the Release mechanism. + +* [Prepare a new release](https://github.com/gophercloud/gophercloud/releases/new) +* Let Github generate the release notes by clicking on Generate release notes +* Click on **Save draft** +* Ask another Gophercloud maintainer to review and publish the release + +_Note: never change a release or force-push a tag. Tags are almost immediately picked up by the Go proxy and changing the commit it points to will be detected as tampering._ diff --git a/vendor/github.com/gophercloud/gophercloud/v2/auth_options.go b/vendor/github.com/gophercloud/gophercloud/v2/auth_options.go new file mode 100644 index 0000000..616919d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/auth_options.go @@ -0,0 +1,517 @@ +package gophercloud + +/* +AuthOptions stores information needed to authenticate to an OpenStack Cloud. +You can populate one manually, or use a provider's AuthOptionsFromEnv() function +to read relevant information from the standard environment variables. Pass one +to a provider's AuthenticatedClient function to authenticate and obtain a +ProviderClient representing an active session on that provider. + +Its fields are the union of those recognized by each identity implementation and +provider. + +An example of manually providing authentication information: + + opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "{username}", + Password: "{password}", + TenantID: "{tenant_id}", + } + + provider, err := openstack.AuthenticatedClient(context.TODO(), opts) + +An example of using AuthOptionsFromEnv(), where the environment variables can +be read from a file, such as a standard openrc file: + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(context.TODO(), opts) +*/ +type AuthOptions struct { + // IdentityEndpoint specifies the HTTP endpoint that is required to work with + // the Identity API of the appropriate version. While it's ultimately needed by + // all of the identity services, it will often be populated by a provider-level + // function. + // + // The IdentityEndpoint is typically referred to as the "auth_url" or + // "OS_AUTH_URL" in the information provided by the cloud operator. + IdentityEndpoint string `json:"-"` + + // Username is required if using Identity V2 API. Consult with your provider's + // control panel to discover your account's username. In Identity V3, either + // UserID or a combination of Username and DomainID or DomainName are needed. + Username string `json:"username,omitempty"` + UserID string `json:"-"` + + Password string `json:"password,omitempty"` + + // Passcode is used in TOTP authentication method + Passcode string `json:"passcode,omitempty"` + + // At most one of DomainID and DomainName must be provided if using Username + // with Identity V3. Otherwise, either are optional. + DomainID string `json:"-"` + DomainName string `json:"name,omitempty"` + + // The TenantID and TenantName fields are optional for the Identity V2 API. + // The same fields are known as project_id and project_name in the Identity + // V3 API, but are collected as TenantID and TenantName here in both cases. + // Some providers allow you to specify a TenantName instead of the TenantId. + // Some require both. Your provider's authentication policies will determine + // how these fields influence authentication. + // If DomainID or DomainName are provided, they will also apply to TenantName. + // It is not currently possible to authenticate with Username and a Domain + // and scope to a Project in a different Domain by using TenantName. To + // accomplish that, the ProjectID will need to be provided as the TenantID + // option. + TenantID string `json:"tenantId,omitempty"` + TenantName string `json:"tenantName,omitempty"` + + // AllowReauth should be set to true if you grant permission for Gophercloud to + // cache your credentials in memory, and to allow Gophercloud to attempt to + // re-authenticate automatically if/when your token expires. If you set it to + // false, it will not cache these settings, but re-authentication will not be + // possible. This setting defaults to false. + // + // NOTE: The reauth function will try to re-authenticate endlessly if left + // unchecked. The way to limit the number of attempts is to provide a custom + // HTTP client to the provider client and provide a transport that implements + // the RoundTripper interface and stores the number of failed retries. For an + // example of this, see here: + // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311 + AllowReauth bool `json:"-"` + + // TokenID allows users to authenticate (possibly as another user) with an + // authentication token ID. + TokenID string `json:"-"` + + // Scope determines the scoping of the authentication request. + Scope *AuthScope `json:"-"` + + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` +} + +// AuthScope allows a created token to be limited to a specific domain or project. +type AuthScope struct { + ProjectID string + ProjectName string + DomainID string + DomainName string + System bool + TrustID string +} + +// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v2 tokens package +func (opts AuthOptions) ToTokenV2CreateMap() (map[string]any, error) { + // Populate the request map. + authMap := make(map[string]any) + + if opts.Username != "" { + if opts.Password != "" { + authMap["passwordCredentials"] = map[string]any{ + "username": opts.Username, + "password": opts.Password, + } + } else { + return nil, ErrMissingInput{Argument: "Password"} + } + } else if opts.TokenID != "" { + authMap["token"] = map[string]any{ + "id": opts.TokenID, + } + } else { + return nil, ErrMissingInput{Argument: "Username"} + } + + if opts.TenantID != "" { + authMap["tenantId"] = opts.TenantID + } + if opts.TenantName != "" { + authMap["tenantName"] = opts.TenantName + } + + return map[string]any{"auth": authMap}, nil +} + +// ToTokenV3CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v3 tokens package +func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]any) (map[string]any, error) { + type domainReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + } + + type userReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Password *string `json:"password,omitempty"` + Passcode *string `json:"passcode,omitempty"` + Domain *domainReq `json:"domain,omitempty"` + } + + type passwordReq struct { + User userReq `json:"user"` + } + + type tokenReq struct { + ID string `json:"id"` + } + + type applicationCredentialReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + User *userReq `json:"user,omitempty"` + Secret *string `json:"secret,omitempty"` + } + + type totpReq struct { + User *userReq `json:"user,omitempty"` + } + + type identityReq struct { + Methods []string `json:"methods"` + Password *passwordReq `json:"password,omitempty"` + Token *tokenReq `json:"token,omitempty"` + ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` + TOTP *totpReq `json:"totp,omitempty"` + } + + type authReq struct { + Identity identityReq `json:"identity"` + } + + type request struct { + Auth authReq `json:"auth"` + } + + // Populate the request structure based on the provided arguments. Create and return an error + // if insufficient or incompatible information is present. + var req request + + if opts.Password == "" && opts.Passcode == "" { + if opts.TokenID != "" { + // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication + // parameters. + if opts.Username != "" { + return nil, ErrUsernameWithToken{} + } + if opts.UserID != "" { + return nil, ErrUserIDWithToken{} + } + if opts.DomainID != "" { + return nil, ErrDomainIDWithToken{} + } + if opts.DomainName != "" { + return nil, ErrDomainNameWithToken{} + } + + // Configure the request for Token authentication. + req.Auth.Identity.Methods = []string{"token"} + req.Auth.Identity.Token = &tokenReq{ + ID: opts.TokenID, + } + + } else if opts.ApplicationCredentialID != "" { + // Configure the request for ApplicationCredentialID authentication. + // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67 + // There are three kinds of possible application_credential requests + // 1. application_credential id + secret + // 2. application_credential name + secret + user_id + // 3. application_credential name + secret + username + domain_id / domain_name + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + ID: &opts.ApplicationCredentialID, + Secret: &opts.ApplicationCredentialSecret, + } + } else if opts.ApplicationCredentialName != "" { + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + + var userRequest *userReq + + if opts.UserID != "" { + // UserID could be used without the domain information + userRequest = &userReq{ + ID: &opts.UserID, + } + } + + if userRequest == nil && opts.Username == "" { + // Make sure that Username or UserID are provided + return nil, ErrUsernameOrUserID{} + } + + if userRequest == nil && opts.DomainID != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{ID: &opts.DomainID}, + } + } + + if userRequest == nil && opts.DomainName != "" { + userRequest = &userReq{ + Name: &opts.Username, + Domain: &domainReq{Name: &opts.DomainName}, + } + } + + // Make sure that DomainID or DomainName are provided among Username + if userRequest == nil { + return nil, ErrDomainIDOrDomainName{} + } + + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + Name: &opts.ApplicationCredentialName, + User: userRequest, + Secret: &opts.ApplicationCredentialSecret, + } + } else { + // If no password or token ID or ApplicationCredential are available, authentication can't continue. + return nil, ErrMissingPassword{} + } + } else { + // Password authentication. + if opts.Password != "" { + req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password") + } + + // TOTP authentication. + if opts.Passcode != "" { + req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "totp") + } + + // At least one of Username and UserID must be specified. + if opts.Username == "" && opts.UserID == "" { + return nil, ErrUsernameOrUserID{} + } + + if opts.Username != "" { + // If Username is provided, UserID may not be provided. + if opts.UserID != "" { + return nil, ErrUsernameOrUserID{} + } + + // Either DomainID or DomainName must also be specified. + if opts.DomainID == "" && opts.DomainName == "" { + return nil, ErrDomainIDOrDomainName{} + } + + if opts.DomainID != "" { + if opts.DomainName != "" { + return nil, ErrDomainIDOrDomainName{} + } + + // Configure the request for Username and Password authentication with a DomainID. + if opts.Password != "" { + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + Name: &opts.Username, + Password: &opts.Password, + Domain: &domainReq{ID: &opts.DomainID}, + }, + } + } + if opts.Passcode != "" { + req.Auth.Identity.TOTP = &totpReq{ + User: &userReq{ + Name: &opts.Username, + Passcode: &opts.Passcode, + Domain: &domainReq{ID: &opts.DomainID}, + }, + } + } + } + + if opts.DomainName != "" { + // Configure the request for Username and Password authentication with a DomainName. + if opts.Password != "" { + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + Name: &opts.Username, + Password: &opts.Password, + Domain: &domainReq{Name: &opts.DomainName}, + }, + } + } + + if opts.Passcode != "" { + req.Auth.Identity.TOTP = &totpReq{ + User: &userReq{ + Name: &opts.Username, + Passcode: &opts.Passcode, + Domain: &domainReq{Name: &opts.DomainName}, + }, + } + } + } + } + + if opts.UserID != "" { + // If UserID is specified, neither DomainID nor DomainName may be. + if opts.DomainID != "" { + return nil, ErrDomainIDWithUserID{} + } + if opts.DomainName != "" { + return nil, ErrDomainNameWithUserID{} + } + + // Configure the request for UserID and Password authentication. + if opts.Password != "" { + req.Auth.Identity.Password = &passwordReq{ + User: userReq{ + ID: &opts.UserID, + Password: &opts.Password, + }, + } + } + + if opts.Passcode != "" { + req.Auth.Identity.TOTP = &totpReq{ + User: &userReq{ + ID: &opts.UserID, + Passcode: &opts.Passcode, + }, + } + } + } + } + + b, err := BuildRequestBody(req, "") + if err != nil { + return nil, err + } + + if len(scope) != 0 { + b["auth"].(map[string]any)["scope"] = scope + } + + return b, nil +} + +// ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in +// the v3 tokens package. +func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]any, error) { + // For backwards compatibility. + // If AuthOptions.Scope was not set, try to determine it. + // This works well for common scenarios. + if opts.Scope == nil { + opts.Scope = new(AuthScope) + if opts.TenantID != "" { + opts.Scope.ProjectID = opts.TenantID + } else { + if opts.TenantName != "" { + opts.Scope.ProjectName = opts.TenantName + opts.Scope.DomainID = opts.DomainID + opts.Scope.DomainName = opts.DomainName + } + } + } + + if opts.Scope.System { + return map[string]any{ + "system": map[string]any{ + "all": true, + }, + }, nil + } + + if opts.Scope.TrustID != "" { + return map[string]any{ + "OS-TRUST:trust": map[string]string{ + "id": opts.Scope.TrustID, + }, + }, nil + } + + if opts.Scope.ProjectName != "" { + // ProjectName provided: either DomainID or DomainName must also be supplied. + // ProjectID may not be supplied. + if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" { + return nil, ErrScopeDomainIDOrDomainName{} + } + if opts.Scope.ProjectID != "" { + return nil, ErrScopeProjectIDOrProjectName{} + } + + if opts.Scope.DomainID != "" { + // ProjectName + DomainID + return map[string]any{ + "project": map[string]any{ + "name": &opts.Scope.ProjectName, + "domain": map[string]any{"id": &opts.Scope.DomainID}, + }, + }, nil + } + + if opts.Scope.DomainName != "" { + // ProjectName + DomainName + return map[string]any{ + "project": map[string]any{ + "name": &opts.Scope.ProjectName, + "domain": map[string]any{"name": &opts.Scope.DomainName}, + }, + }, nil + } + } else if opts.Scope.ProjectID != "" { + // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided. + if opts.Scope.DomainID != "" { + return nil, ErrScopeProjectIDAlone{} + } + if opts.Scope.DomainName != "" { + return nil, ErrScopeProjectIDAlone{} + } + + // ProjectID + return map[string]any{ + "project": map[string]any{ + "id": &opts.Scope.ProjectID, + }, + }, nil + } else if opts.Scope.DomainID != "" { + // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided. + if opts.Scope.DomainName != "" { + return nil, ErrScopeDomainIDOrDomainName{} + } + + // DomainID + return map[string]any{ + "domain": map[string]any{ + "id": &opts.Scope.DomainID, + }, + }, nil + } else if opts.Scope.DomainName != "" { + // DomainName + return map[string]any{ + "domain": map[string]any{ + "name": &opts.Scope.DomainName, + }, + }, nil + } + + return nil, nil +} + +func (opts AuthOptions) CanReauth() bool { + if opts.Passcode != "" { + // cannot reauth using TOTP passcode + return false + } + + return opts.AllowReauth +} + +// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v3 tokens package. +func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]any) (map[string]string, error) { + return nil, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/auth_result.go b/vendor/github.com/gophercloud/gophercloud/v2/auth_result.go new file mode 100644 index 0000000..9a49cce --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/auth_result.go @@ -0,0 +1,52 @@ +package gophercloud + +/* +AuthResult is the result from the request that was used to obtain a provider +client's Keystone token. It is returned from ProviderClient.GetAuthResult(). + +The following types satisfy this interface: + + github.com/gophercloud/gophercloud/openstack/identity/v2/tokens.CreateResult + github.com/gophercloud/gophercloud/openstack/identity/v3/tokens.CreateResult + +Usage example: + + import ( + "github.com/gophercloud/gophercloud/v2" + tokens2 "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" + ) + + func GetAuthenticatedUserID(providerClient *gophercloud.ProviderClient) (string, error) { + r := providerClient.GetAuthResult() + if r == nil { + //ProviderClient did not use openstack.Authenticate(), e.g. because token + //was set manually with ProviderClient.SetToken() + return "", errors.New("no AuthResult available") + } + switch r := r.(type) { + case tokens2.CreateResult: + u, err := r.ExtractUser() + if err != nil { + return "", err + } + return u.ID, nil + case tokens3.CreateResult: + u, err := r.ExtractUser() + if err != nil { + return "", err + } + return u.ID, nil + default: + panic(fmt.Sprintf("got unexpected AuthResult type %t", r)) + } + } + +Both implementing types share a lot of methods by name, like ExtractUser() in +this example. But those methods cannot be part of the AuthResult interface +because the return types are different (in this case, type tokens2.User vs. +type tokens3.User). +*/ +type AuthResult interface { + ExtractTokenID() (string, error) +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/doc.go b/vendor/github.com/gophercloud/gophercloud/v2/doc.go new file mode 100644 index 0000000..a755ecb --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/doc.go @@ -0,0 +1,148 @@ +/* +Package gophercloud provides a multi-vendor interface to OpenStack-compatible +clouds. The library has a three-level hierarchy: providers, services, and +resources. + +# Authenticating with Providers + +Provider structs represent the cloud providers that offer and manage a +collection of services. You will generally want to create one Provider +client per OpenStack cloud. + + It is now recommended to use the `clientconfig` package found at + https://github.com/gophercloud/utils/tree/master/openstack/clientconfig + for all authentication purposes. + + The below documentation is still relevant. clientconfig simply implements + the below and presents it in an easier and more flexible way. + +Use your OpenStack credentials to create a Provider client. The +IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in +information provided by the cloud operator. Additionally, the cloud may refer to +TenantID or TenantName as project_id and project_name. Credentials are +specified like so: + + opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + Username: "{username}", + Password: "{password}", + TenantID: "{tenant_id}", + } + + provider, err := openstack.AuthenticatedClient(context.TODO(), opts) + +You can authenticate with a token by doing: + + opts := gophercloud.AuthOptions{ + IdentityEndpoint: "https://openstack.example.com:5000/v2.0", + TokenID: "{token_id}", + TenantID: "{tenant_id}", + } + + provider, err := openstack.AuthenticatedClient(context.TODO(), opts) + +You may also use the openstack.AuthOptionsFromEnv() helper function. This +function reads in standard environment variables frequently found in an +OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant" +instead of "project". + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(context.TODO(), opts) + +# Service Clients + +Service structs are specific to a provider and handle all of the logic and +operations for a particular OpenStack service. Examples of services include: +Compute, Object Storage, Block Storage. In order to define one, you need to +pass in the parent provider, like so: + + opts := gophercloud.EndpointOpts{Region: "RegionOne"} + + client, err := openstack.NewComputeV2(provider, opts) + +# Resources + +Resource structs are the domain models that services make use of in order +to work with and represent the state of API resources: + + server, err := servers.Get(context.TODO(), client, "{serverId}").Extract() + +Intermediate Result structs are returned for API operations, which allow +generic access to the HTTP headers, response body, and any errors associated +with the network transaction. To turn a result into a usable resource struct, +you must call the Extract method which is chained to the response, or an +Extract function from an applicable extension: + + result := servers.Get(context.TODO(), client, "{serverId}") + + // Attempt to extract the disk configuration from the OS-DCF disk config + // extension: + config, err := diskconfig.ExtractGet(result) + +All requests that enumerate a collection return a Pager struct that is used to +iterate through the results one page at a time. Use the EachPage method on that +Pager to handle each successive Page in a closure, then use the appropriate +extraction method from that request's package to interpret that Page as a slice +of results: + + err := servers.List(client, nil).EachPage(context.TODO(), func (_ context.Context, page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + + // Handle the []servers.Server slice. + + // Return "false" or an error to prematurely stop fetching new pages. + return true, nil + }) + +If you want to obtain the entire collection of pages without doing any +intermediary processing on each page, you can use the AllPages method: + + allPages, err := servers.List(client, nil).AllPages(context.TODO()) + allServers, err := servers.ExtractServers(allPages) + +This top-level package contains utility functions and data types that are used +throughout the provider and service packages. Of particular note for end users +are the AuthOptions and EndpointOpts structs. + +An example retry backoff function, which respects the 429 HTTP response code and a "Retry-After" header: + + endpoint := "http://localhost:5000" + provider, err := openstack.NewClient(endpoint) + if err != nil { + panic(err) + } + provider.MaxBackoffRetries = 3 // max three retries + provider.RetryBackoffFunc = func(ctx context.Context, respErr *ErrUnexpectedResponseCode, e error, retries uint) error { + retryAfter := respErr.ResponseHeader.Get("Retry-After") + if retryAfter == "" { + return e + } + + var sleep time.Duration + + // Parse delay seconds or HTTP date + if v, err := strconv.ParseUint(retryAfter, 10, 32); err == nil { + sleep = time.Duration(v) * time.Second + } else if v, err := time.Parse(http.TimeFormat, retryAfter); err == nil { + sleep = time.Until(v) + } else { + return e + } + + if ctx != nil { + select { + case <-time.After(sleep): + case <-ctx.Done(): + return e + } + } else { + time.Sleep(sleep) + } + + return nil + } +*/ +package gophercloud diff --git a/vendor/github.com/gophercloud/gophercloud/v2/endpoint_search.go b/vendor/github.com/gophercloud/gophercloud/v2/endpoint_search.go new file mode 100644 index 0000000..2fbc3c9 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/endpoint_search.go @@ -0,0 +1,76 @@ +package gophercloud + +// Availability indicates to whom a specific service endpoint is accessible: +// the internet at large, internal networks only, or only to administrators. +// Different identity services use different terminology for these. Identity v2 +// lists them as different kinds of URLs within the service catalog ("adminURL", +// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an +// endpoint's response. +type Availability string + +const ( + // AvailabilityAdmin indicates that an endpoint is only available to + // administrators. + AvailabilityAdmin Availability = "admin" + + // AvailabilityPublic indicates that an endpoint is available to everyone on + // the internet. + AvailabilityPublic Availability = "public" + + // AvailabilityInternal indicates that an endpoint is only available within + // the cluster's internal network. + AvailabilityInternal Availability = "internal" +) + +// EndpointOpts specifies search criteria used by queries against an +// OpenStack service catalog. The options must contain enough information to +// unambiguously identify one, and only one, endpoint within the catalog. +// +// Usually, these are passed to service client factory functions in a provider +// package, like "openstack.NewComputeV2()". +type EndpointOpts struct { + // Type [required] is the service type for the client (e.g., "compute", + // "object-store"). Generally, this will be supplied by the service client + // function, but a user-given value will be honored if provided. + Type string + + // Name [optional] is the service name for the client (e.g., "nova") as it + // appears in the service catalog. Services can have the same Type but a + // different Name, which is why both Type and Name are sometimes needed. + Name string + + // Region [required] is the geographic region in which the endpoint resides, + // generally specifying which datacenter should house your resources. + // Required only for services that span multiple regions. + Region string + + // Availability [optional] is the visibility of the endpoint to be returned. + // Valid types include the constants AvailabilityPublic, AvailabilityInternal, + // or AvailabilityAdmin from this package. + // + // Availability is not required, and defaults to AvailabilityPublic. Not all + // providers or services offer all Availability options. + Availability Availability +} + +/* +EndpointLocator is an internal function to be used by provider implementations. + +It provides an implementation that locates a single endpoint from a service +catalog for a specific ProviderClient based on user-provided EndpointOpts. The +provider then uses it to discover related ServiceClients. +*/ +type EndpointLocator func(EndpointOpts) (string, error) + +// ApplyDefaults is an internal method to be used by provider implementations. +// +// It sets EndpointOpts fields if not already set, including a default type. +// Currently, EndpointOpts.Availability defaults to the public endpoint. +func (eo *EndpointOpts) ApplyDefaults(t string) { + if eo.Type == "" { + eo.Type = t + } + if eo.Availability == "" { + eo.Availability = AvailabilityPublic + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/errors.go b/vendor/github.com/gophercloud/gophercloud/v2/errors.go new file mode 100644 index 0000000..2698c20 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/errors.go @@ -0,0 +1,351 @@ +package gophercloud + +import ( + "bytes" + "errors" + "fmt" + "net/http" + "strings" +) + +// BaseError is an error type that all other error types embed. +type BaseError struct { + DefaultErrString string + Info string +} + +func (e BaseError) Error() string { + e.DefaultErrString = "An error occurred while executing a Gophercloud request." + return e.choseErrString() +} + +func (e BaseError) choseErrString() string { + if e.Info != "" { + return e.Info + } + return e.DefaultErrString +} + +// ErrMissingInput is the error when input is required in a particular +// situation but not provided by the user +type ErrMissingInput struct { + BaseError + Argument string +} + +func (e ErrMissingInput) Error() string { + e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument) + return e.choseErrString() +} + +// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors. +type ErrInvalidInput struct { + ErrMissingInput + Value any +} + +func (e ErrInvalidInput) Error() string { + e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value) + return e.choseErrString() +} + +// ErrMissingEnvironmentVariable is the error when environment variable is required +// in a particular situation but not provided by the user +type ErrMissingEnvironmentVariable struct { + BaseError + EnvironmentVariable string +} + +func (e ErrMissingEnvironmentVariable) Error() string { + e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable) + return e.choseErrString() +} + +// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables +// is required in a particular situation but not provided by the user +type ErrMissingAnyoneOfEnvironmentVariables struct { + BaseError + EnvironmentVariables []string +} + +func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Missing one of the following environment variables [%s]", + strings.Join(e.EnvironmentVariables, ", "), + ) + return e.choseErrString() +} + +// ErrUnexpectedResponseCode is returned by the Request method when a response code other than +// those listed in OkCodes is encountered. +type ErrUnexpectedResponseCode struct { + BaseError + URL string + Method string + Expected []int + Actual int + Body []byte + ResponseHeader http.Header +} + +func (e ErrUnexpectedResponseCode) Error() string { + e.DefaultErrString = fmt.Sprintf( + "Expected HTTP response code %v when accessing [%s %s], but got %d instead: %s", + e.Expected, e.Method, e.URL, e.Actual, bytes.TrimSpace(e.Body), + ) + return e.choseErrString() +} + +// GetStatusCode returns the actual status code of the error. +func (e ErrUnexpectedResponseCode) GetStatusCode() int { + return e.Actual +} + +// ResponseCodeIs returns true if this error is or contains an ErrUnexpectedResponseCode reporting +// that the request failed with the given response code. For example, this checks if a request +// failed because of a 404 error: +// +// allServers, err := servers.List(client, servers.ListOpts{}) +// if gophercloud.ResponseCodeIs(err, http.StatusNotFound) { +// handleNotFound() +// } +// +// It is safe to pass a nil error, in which case this function always returns false. +func ResponseCodeIs(err error, status int) bool { + var codeError ErrUnexpectedResponseCode + if errors.As(err, &codeError) { + return codeError.Actual == status + } + return false +} + +// ErrTimeOut is the error type returned when an operations times out. +type ErrTimeOut struct { + BaseError +} + +func (e ErrTimeOut) Error() string { + e.DefaultErrString = "A time out occurred" + return e.choseErrString() +} + +// ErrUnableToReauthenticate is the error type returned when reauthentication fails. +type ErrUnableToReauthenticate struct { + BaseError + ErrOriginal error + ErrReauth error +} + +func (e ErrUnableToReauthenticate) Error() string { + e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s: %s", e.ErrOriginal, e.ErrReauth) + return e.choseErrString() +} + +// ErrErrorAfterReauthentication is the error type returned when reauthentication +// succeeds, but an error occurs afterword (usually an HTTP error). +type ErrErrorAfterReauthentication struct { + BaseError + ErrOriginal error +} + +func (e ErrErrorAfterReauthentication) Error() string { + e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal) + return e.choseErrString() +} + +// ErrServiceNotFound is returned when no service in a service catalog matches +// the provided EndpointOpts. This is generally returned by provider service +// factory methods like "NewComputeV2()" and can mean that a service is not +// enabled for your account. +type ErrServiceNotFound struct { + BaseError +} + +func (e ErrServiceNotFound) Error() string { + e.DefaultErrString = "No suitable service could be found in the service catalog." + return e.choseErrString() +} + +// ErrEndpointNotFound is returned when no available endpoints match the +// provided EndpointOpts. This is also generally returned by provider service +// factory methods, and usually indicates that a region was specified +// incorrectly. +type ErrEndpointNotFound struct { + BaseError +} + +func (e ErrEndpointNotFound) Error() string { + e.DefaultErrString = "No suitable endpoint could be found in the service catalog." + return e.choseErrString() +} + +// ErrResourceNotFound is the error when trying to retrieve a resource's +// ID by name and the resource doesn't exist. +type ErrResourceNotFound struct { + BaseError + Name string + ResourceType string +} + +func (e ErrResourceNotFound) Error() string { + e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name) + return e.choseErrString() +} + +// ErrMultipleResourcesFound is the error when trying to retrieve a resource's +// ID by name and multiple resources have the user-provided name. +type ErrMultipleResourcesFound struct { + BaseError + Name string + Count int + ResourceType string +} + +func (e ErrMultipleResourcesFound) Error() string { + e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name) + return e.choseErrString() +} + +// ErrUnexpectedType is the error when an unexpected type is encountered +type ErrUnexpectedType struct { + BaseError + Expected string + Actual string +} + +func (e ErrUnexpectedType) Error() string { + e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual) + return e.choseErrString() +} + +func unacceptedAttributeErr(attribute string) string { + return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute) +} + +func redundantWithTokenErr(attribute string) string { + return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute) +} + +func redundantWithUserID(attribute string) string { + return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute) +} + +// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used. +type ErrAPIKeyProvided struct{ BaseError } + +func (e ErrAPIKeyProvided) Error() string { + return unacceptedAttributeErr("APIKey") +} + +// ErrTenantIDProvided indicates that a TenantID was provided but can't be used. +type ErrTenantIDProvided struct{ BaseError } + +func (e ErrTenantIDProvided) Error() string { + return unacceptedAttributeErr("TenantID") +} + +// ErrTenantNameProvided indicates that a TenantName was provided but can't be used. +type ErrTenantNameProvided struct{ BaseError } + +func (e ErrTenantNameProvided) Error() string { + return unacceptedAttributeErr("TenantName") +} + +// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead. +type ErrUsernameWithToken struct{ BaseError } + +func (e ErrUsernameWithToken) Error() string { + return redundantWithTokenErr("Username") +} + +// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead. +type ErrUserIDWithToken struct{ BaseError } + +func (e ErrUserIDWithToken) Error() string { + return redundantWithTokenErr("UserID") +} + +// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead. +type ErrDomainIDWithToken struct{ BaseError } + +func (e ErrDomainIDWithToken) Error() string { + return redundantWithTokenErr("DomainID") +} + +// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s +type ErrDomainNameWithToken struct{ BaseError } + +func (e ErrDomainNameWithToken) Error() string { + return redundantWithTokenErr("DomainName") +} + +// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once. +type ErrUsernameOrUserID struct{ BaseError } + +func (e ErrUsernameOrUserID) Error() string { + return "Exactly one of Username and UserID must be provided for password authentication" +} + +// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used. +type ErrDomainIDWithUserID struct{ BaseError } + +func (e ErrDomainIDWithUserID) Error() string { + return redundantWithUserID("DomainID") +} + +// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used. +type ErrDomainNameWithUserID struct{ BaseError } + +func (e ErrDomainNameWithUserID) Error() string { + return redundantWithUserID("DomainName") +} + +// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it. +// It may also indicate that both a DomainID and a DomainName were provided at once. +type ErrDomainIDOrDomainName struct{ BaseError } + +func (e ErrDomainIDOrDomainName) Error() string { + return "You must provide exactly one of DomainID or DomainName to authenticate by Username" +} + +// ErrMissingPassword indicates that no password was provided and no token is available. +type ErrMissingPassword struct{ BaseError } + +func (e ErrMissingPassword) Error() string { + return "You must provide a password to authenticate" +} + +// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present. +type ErrScopeDomainIDOrDomainName struct{ BaseError } + +func (e ErrScopeDomainIDOrDomainName) Error() string { + return "You must provide exactly one of DomainID or DomainName in a Scope with ProjectName" +} + +// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope. +type ErrScopeProjectIDOrProjectName struct{ BaseError } + +func (e ErrScopeProjectIDOrProjectName) Error() string { + return "You must provide at most one of ProjectID or ProjectName in a Scope" +} + +// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope. +type ErrScopeProjectIDAlone struct{ BaseError } + +func (e ErrScopeProjectIDAlone) Error() string { + return "ProjectID must be supplied alone in a Scope" +} + +// ErrScopeEmpty indicates that no credentials were provided in a Scope. +type ErrScopeEmpty struct{ BaseError } + +func (e ErrScopeEmpty) Error() string { + return "You must provide either a Project or Domain in a Scope" +} + +// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name +type ErrAppCredMissingSecret struct{ BaseError } + +func (e ErrAppCredMissingSecret) Error() string { + return "You must provide an Application Credential Secret" +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/auth_env.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/auth_env.go new file mode 100644 index 0000000..893787b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/auth_env.go @@ -0,0 +1,137 @@ +package openstack + +import ( + "os" + + "github.com/gophercloud/gophercloud/v2" +) + +var nilOptions = gophercloud.AuthOptions{} + +/* +AuthOptionsFromEnv fills out an identity.AuthOptions structure with the +settings found on the various OpenStack OS_* environment variables. + +The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME, +OS_PASSWORD and OS_PROJECT_ID. + +Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings, +or an error will result. OS_PROJECT_ID, is optional. + +OS_TENANT_ID and OS_TENANT_NAME are deprecated forms of OS_PROJECT_ID and +OS_PROJECT_NAME and the latter are expected against a v3 auth api. + +If OS_PROJECT_ID and OS_PROJECT_NAME are set, they will still be referred +as "tenant" in Gophercloud. + +If OS_PROJECT_NAME is set, it requires OS_PROJECT_ID to be set as well to +handle projects not on the default domain. + +To use this function, first set the OS_* environment variables (for example, +by sourcing an `openrc` file), then: + + opts, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(context.TODO(), opts) +*/ +func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) { + authURL := os.Getenv("OS_AUTH_URL") + username := os.Getenv("OS_USERNAME") + userID := os.Getenv("OS_USERID") + password := os.Getenv("OS_PASSWORD") + passcode := os.Getenv("OS_PASSCODE") + tenantID := os.Getenv("OS_TENANT_ID") + tenantName := os.Getenv("OS_TENANT_NAME") + domainID := os.Getenv("OS_DOMAIN_ID") + domainName := os.Getenv("OS_DOMAIN_NAME") + applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID") + applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME") + applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET") + systemScope := os.Getenv("OS_SYSTEM_SCOPE") + + // If OS_PROJECT_ID is set, overwrite tenantID with the value. + if v := os.Getenv("OS_PROJECT_ID"); v != "" { + tenantID = v + } + + // If OS_PROJECT_NAME is set, overwrite tenantName with the value. + if v := os.Getenv("OS_PROJECT_NAME"); v != "" { + tenantName = v + } + + if authURL == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_AUTH_URL", + } + return nilOptions, err + } + + if userID == "" && username == "" { + // Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set + if applicationCredentialID == "" && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + return nilOptions, err + } + } + + if password == "" && passcode == "" && applicationCredentialID == "" && applicationCredentialName == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + // silently ignore TOTP passcode warning, since it is not a common auth method + EnvironmentVariable: "OS_PASSWORD", + } + return nilOptions, err + } + + if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET", + } + return nilOptions, err + } + + if domainID == "" && domainName == "" && tenantID == "" && tenantName != "" { + err := gophercloud.ErrMissingEnvironmentVariable{ + EnvironmentVariable: "OS_PROJECT_ID", + } + return nilOptions, err + } + + if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" { + if userID == "" && username == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"}, + } + } + if username != "" && domainID == "" && domainName == "" { + return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ + EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"}, + } + } + } + + var scope *gophercloud.AuthScope + if systemScope == "all" { + scope = &gophercloud.AuthScope{ + System: true, + } + } + + ao := gophercloud.AuthOptions{ + IdentityEndpoint: authURL, + UserID: userID, + Username: username, + Password: password, + Passcode: passcode, + TenantID: tenantID, + TenantName: tenantName, + DomainID: domainID, + DomainName: domainName, + ApplicationCredentialID: applicationCredentialID, + ApplicationCredentialName: applicationCredentialName, + ApplicationCredentialSecret: applicationCredentialSecret, + Scope: scope, + } + + return ao, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/client.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/client.go new file mode 100644 index 0000000..43b569d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/client.go @@ -0,0 +1,492 @@ +package openstack + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/gophercloud/gophercloud/v2" + tokens2 "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1" + tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" + "github.com/gophercloud/gophercloud/v2/openstack/utils" +) + +const ( + // v2 represents Keystone v2. + // It should never increase beyond 2.0. + v2 = "v2.0" + + // v3 represents Keystone v3. + // The version can be anything from v3 to v3.x. + v3 = "v3" +) + +// NewClient prepares an unauthenticated ProviderClient instance. +// Most users will probably prefer using the AuthenticatedClient function +// instead. +// +// This is useful if you wish to explicitly control the version of the identity +// service that's used for authentication explicitly, for example. +// +// A basic example of using this would be: +// +// ao, err := openstack.AuthOptionsFromEnv() +// provider, err := openstack.NewClient(ao.IdentityEndpoint) +// client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{}) +func NewClient(endpoint string) (*gophercloud.ProviderClient, error) { + base, err := utils.BaseEndpoint(endpoint) + if err != nil { + return nil, err + } + + endpoint = gophercloud.NormalizeURL(endpoint) + base = gophercloud.NormalizeURL(base) + + p := new(gophercloud.ProviderClient) + p.IdentityBase = base + p.IdentityEndpoint = endpoint + p.UseTokenLock() + + return p, nil +} + +// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint +// specified by the options, acquires a token, and returns a Provider Client +// instance that's ready to operate. +// +// If the full path to a versioned identity endpoint was specified (example: +// http://example.com:5000/v3), that path will be used as the endpoint to query. +// +// If a versionless endpoint was specified (example: http://example.com:5000/), +// the endpoint will be queried to determine which versions of the identity service +// are available, then chooses the most recent or most supported version. +// +// Example: +// +// ao, err := openstack.AuthOptionsFromEnv() +// provider, err := openstack.AuthenticatedClient(ctx, ao) +// client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ +// Region: os.Getenv("OS_REGION_NAME"), +// }) +func AuthenticatedClient(ctx context.Context, options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) { + client, err := NewClient(options.IdentityEndpoint) + if err != nil { + return nil, err + } + + err = Authenticate(ctx, client, options) + if err != nil { + return nil, err + } + return client, nil +} + +// Authenticate authenticates or re-authenticates against the most +// recent identity service supported at the provided endpoint. +func Authenticate(ctx context.Context, client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error { + versions := []*utils.Version{ + {ID: v2, Priority: 20, Suffix: "/v2.0/"}, + {ID: v3, Priority: 30, Suffix: "/v3/"}, + } + + chosen, endpoint, err := utils.ChooseVersion(ctx, client, versions) + if err != nil { + return err + } + + switch chosen.ID { + case v2: + return v2auth(ctx, client, endpoint, &options, gophercloud.EndpointOpts{}) + case v3: + return v3auth(ctx, client, endpoint, &options, gophercloud.EndpointOpts{}) + default: + // The switch statement must be out of date from the versions list. + return fmt.Errorf("Unrecognized identity version: %s", chosen.ID) + } +} + +// AuthenticateV2 explicitly authenticates against the identity v2 endpoint. +func AuthenticateV2(ctx context.Context, client *gophercloud.ProviderClient, options tokens2.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { + return v2auth(ctx, client, "", options, eo) +} + +type v2TokenNoReauth struct { + tokens2.AuthOptionsBuilder +} + +func (v2TokenNoReauth) CanReauth() bool { return false } + +func v2auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint string, options tokens2.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { + v2Client, err := NewIdentityV2(client, eo) + if err != nil { + return err + } + + if endpoint != "" { + v2Client.Endpoint = endpoint + } + + result := tokens2.Create(ctx, v2Client, options) + + err = client.SetTokenAndAuthResult(result) + if err != nil { + return err + } + + catalog, err := result.ExtractServiceCatalog() + if err != nil { + return err + } + + if options.CanReauth() { + // here we're creating a throw-away client (tac). it's a copy of the user's provider client, but + // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`, + // this should retry authentication only once + tac := *client + tac.SetThrowaway(true) + tac.ReauthFunc = nil + err := tac.SetTokenAndAuthResult(nil) + if err != nil { + return err + } + client.ReauthFunc = func(ctx context.Context) error { + err := v2auth(ctx, &tac, endpoint, &v2TokenNoReauth{options}, eo) + if err != nil { + return err + } + client.CopyTokenFrom(&tac) + return nil + } + } + client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { + return V2EndpointURL(catalog, opts) + } + + return nil +} + +// AuthenticateV3 explicitly authenticates against the identity v3 service. +func AuthenticateV3(ctx context.Context, client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { + return v3auth(ctx, client, "", options, eo) +} + +func v3auth(ctx context.Context, client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error { + // Override the generated service endpoint with the one returned by the version endpoint. + v3Client, err := NewIdentityV3(client, eo) + if err != nil { + return err + } + + if endpoint != "" { + v3Client.Endpoint = endpoint + } + + var catalog *tokens3.ServiceCatalog + + var tokenID string + // passthroughToken allows to passthrough the token without a scope + var passthroughToken bool + switch v := opts.(type) { + case *gophercloud.AuthOptions: + tokenID = v.TokenID + passthroughToken = (v.Scope == nil || *v.Scope == gophercloud.AuthScope{}) + case *tokens3.AuthOptions: + tokenID = v.TokenID + passthroughToken = (v.Scope == tokens3.Scope{}) + } + + if tokenID != "" && passthroughToken { + // passing through the token ID without requesting a new scope + if opts.CanReauth() { + return fmt.Errorf("cannot use AllowReauth, when the token ID is defined and auth scope is not set") + } + + v3Client.SetToken(tokenID) + result := tokens3.Get(ctx, v3Client, tokenID) + if result.Err != nil { + return result.Err + } + + err = client.SetTokenAndAuthResult(result) + if err != nil { + return err + } + + catalog, err = result.ExtractServiceCatalog() + if err != nil { + return err + } + } else { + var result tokens3.CreateResult + switch opts.(type) { + case *ec2tokens.AuthOptions: + result = ec2tokens.Create(ctx, v3Client, opts) + case *oauth1.AuthOptions: + result = oauth1.Create(ctx, v3Client, opts) + default: + result = tokens3.Create(ctx, v3Client, opts) + } + + err = client.SetTokenAndAuthResult(result) + if err != nil { + return err + } + + catalog, err = result.ExtractServiceCatalog() + if err != nil { + return err + } + } + + if opts.CanReauth() { + // here we're creating a throw-away client (tac). it's a copy of the user's provider client, but + // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`, + // this should retry authentication only once + tac := *client + tac.SetThrowaway(true) + tac.ReauthFunc = nil + err = tac.SetTokenAndAuthResult(nil) + if err != nil { + return err + } + var tao tokens3.AuthOptionsBuilder + switch ot := opts.(type) { + case *gophercloud.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o + case *tokens3.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o + case *ec2tokens.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o + case *oauth1.AuthOptions: + o := *ot + o.AllowReauth = false + tao = &o + default: + tao = opts + } + client.ReauthFunc = func(ctx context.Context) error { + err := v3auth(ctx, &tac, endpoint, tao, eo) + if err != nil { + return err + } + client.CopyTokenFrom(&tac) + return nil + } + } + client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { + return V3EndpointURL(catalog, opts) + } + + return nil +} + +// NewIdentityV2 creates a ServiceClient that may be used to interact with the +// v2 identity service. +func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + endpoint := client.IdentityBase + "v2.0/" + clientType := "identity" + var err error + if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { + eo.ApplyDefaults(clientType) + endpoint, err = client.EndpointLocator(eo) + if err != nil { + return nil, err + } + } + + return &gophercloud.ServiceClient{ + ProviderClient: client, + Endpoint: endpoint, + Type: clientType, + }, nil +} + +// NewIdentityV3 creates a ServiceClient that may be used to access the v3 +// identity service. +func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + endpoint := client.IdentityBase + "v3/" + clientType := "identity" + var err error + if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { + eo.ApplyDefaults(clientType) + endpoint, err = client.EndpointLocator(eo) + if err != nil { + return nil, err + } + } + + // Ensure endpoint still has a suffix of v3. + // This is because EndpointLocator might have found a versionless + // endpoint or the published endpoint is still /v2.0. In both + // cases, we need to fix the endpoint to point to /v3. + base, err := utils.BaseEndpoint(endpoint) + if err != nil { + return nil, err + } + + base = gophercloud.NormalizeURL(base) + + endpoint = base + "v3/" + + return &gophercloud.ServiceClient{ + ProviderClient: client, + Endpoint: endpoint, + Type: clientType, + }, nil +} + +func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) { + sc := new(gophercloud.ServiceClient) + eo.ApplyDefaults(clientType) + url, err := client.EndpointLocator(eo) + if err != nil { + return sc, err + } + sc.ProviderClient = client + sc.Endpoint = url + sc.Type = clientType + return sc, nil +} + +// NewBareMetalV1 creates a ServiceClient that may be used with the v1 +// bare metal package. +func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "baremetal") + if !strings.HasSuffix(strings.TrimSuffix(sc.Endpoint, "/"), "v1") { + sc.ResourceBase = sc.Endpoint + "v1/" + } + return sc, err +} + +// NewBareMetalIntrospectionV1 creates a ServiceClient that may be used with the v1 +// bare metal introspection package. +func NewBareMetalIntrospectionV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "baremetal-introspection") +} + +// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 +// object storage package. +func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "object-store") +} + +// NewComputeV2 creates a ServiceClient that may be used with the v2 compute +// package. +func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "compute") +} + +// NewNetworkV2 creates a ServiceClient that may be used with the v2 network +// package. +func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "network") + sc.ResourceBase = sc.Endpoint + "v2.0/" + return sc, err +} + +// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 +// block storage service. +func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "volume") +} + +// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 +// block storage service. +func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "volumev2") +} + +// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service. +func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "volumev3") +} + +// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. +func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "sharev2") +} + +// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 +// orchestration service. +func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "orchestration") +} + +// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. +func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "database") +} + +// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS +// service. +func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "dns") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err +} + +// NewImageV2 creates a ServiceClient that may be used to access the v2 image +// service. +func NewImageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "image") + sc.ResourceBase = sc.Endpoint + "v2/" + return sc, err +} + +// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2 +// load balancer service. +func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "load-balancer") + + // Fixes edge case having an OpenStack lb endpoint with trailing version number. + endpoint := strings.Replace(sc.Endpoint, "v2.0/", "", -1) + + sc.ResourceBase = endpoint + "v2.0/" + return sc, err +} + +// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging +// service. +func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "messaging") + sc.MoreHeaders = map[string]string{"Client-ID": clientID} + return sc, err +} + +// NewContainerV1 creates a ServiceClient that may be used with v1 container package +func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "container") +} + +// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key +// manager service. +func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "key-manager") + sc.ResourceBase = sc.Endpoint + "v1/" + return sc, err +} + +// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management +// package. +func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "container-infra") +} + +// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package. +func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "workflowv2") +} + +// NewPlacementV1 creates a ServiceClient that may be used with the placement package. +func NewPlacementV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "placement") +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/doc.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/doc.go new file mode 100644 index 0000000..4d89fb5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/doc.go @@ -0,0 +1,14 @@ +/* +Package openstack contains resources for the individual OpenStack projects +supported in Gophercloud. It also includes functions to authenticate to an +OpenStack cloud and for provisioning various service-level clients. + +Example of Creating a Service Client + + ao, err := openstack.AuthOptionsFromEnv() + provider, err := openstack.AuthenticatedClient(context.TODO(), ao) + client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +*/ +package openstack diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/endpoint_location.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/endpoint_location.go new file mode 100644 index 0000000..2cdbd3e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/endpoint_location.go @@ -0,0 +1,111 @@ +package openstack + +import ( + "github.com/gophercloud/gophercloud/v2" + tokens2 "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens" + tokens3 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" +) + +/* +V2EndpointURL discovers the endpoint URL for a specific service from a +ServiceCatalog acquired during the v2 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ +func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { + // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided. + var endpoints = make([]tokens2.Endpoint, 0, 1) + for _, entry := range catalog.Entries { + if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + for _, endpoint := range entry.Endpoints { + if opts.Region == "" || endpoint.Region == opts.Region { + endpoints = append(endpoints, endpoint) + } + } + } + } + + // If multiple endpoints were found, use the first result + // and disregard the other endpoints. + // + // This behavior matches the Python library. See GH-1764. + if len(endpoints) > 1 { + endpoints = endpoints[0:1] + } + + // Extract the appropriate URL from the matching Endpoint. + for _, endpoint := range endpoints { + switch opts.Availability { + case gophercloud.AvailabilityPublic: + return gophercloud.NormalizeURL(endpoint.PublicURL), nil + case gophercloud.AvailabilityInternal: + return gophercloud.NormalizeURL(endpoint.InternalURL), nil + case gophercloud.AvailabilityAdmin: + return gophercloud.NormalizeURL(endpoint.AdminURL), nil + default: + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } + } + + // Report an error if there were no matching endpoints. + err := &gophercloud.ErrEndpointNotFound{} + return "", err +} + +/* +V3EndpointURL discovers the endpoint URL for a specific service from a Catalog +acquired during the v3 identity service. + +The specified EndpointOpts are used to identify a unique, unambiguous endpoint +to return. It's an error both when multiple endpoints match the provided +criteria and when none do. The minimum that can be specified is a Type, but you +will also often need to specify a Name and/or a Region depending on what's +available on your OpenStack deployment. +*/ +func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) { + // Extract Endpoints from the catalog entries that match the requested Type, Interface, + // Name if provided, and Region if provided. + var endpoints = make([]tokens3.Endpoint, 0, 1) + for _, entry := range catalog.Entries { + if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) { + for _, endpoint := range entry.Endpoints { + if opts.Availability != gophercloud.AvailabilityAdmin && + opts.Availability != gophercloud.AvailabilityPublic && + opts.Availability != gophercloud.AvailabilityInternal { + err := &ErrInvalidAvailabilityProvided{} + err.Argument = "Availability" + err.Value = opts.Availability + return "", err + } + if (opts.Availability == gophercloud.Availability(endpoint.Interface)) && + (opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) { + endpoints = append(endpoints, endpoint) + } + } + } + } + + // If multiple endpoints were found, use the first result + // and disregard the other endpoints. + // + // This behavior matches the Python library. See GH-1764. + if len(endpoints) > 1 { + endpoints = endpoints[0:1] + } + + // Extract the URL from the matching Endpoint. + for _, endpoint := range endpoints { + return gophercloud.NormalizeURL(endpoint.URL), nil + } + + // Report an error if there were no matching endpoints. + err := &gophercloud.ErrEndpointNotFound{} + return "", err +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/errors.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/errors.go new file mode 100644 index 0000000..f527348 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/errors.go @@ -0,0 +1,47 @@ +package openstack + +import ( + "fmt" + + "github.com/gophercloud/gophercloud/v2" +) + +// ErrEndpointNotFound is the error when no suitable endpoint can be found +// in the user's catalog +type ErrEndpointNotFound struct{ gophercloud.BaseError } + +func (e ErrEndpointNotFound) Error() string { + return "No suitable endpoint could be found in the service catalog." +} + +// ErrInvalidAvailabilityProvided is the error when an invalid endpoint +// availability is provided +type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput } + +func (e ErrInvalidAvailabilityProvided) Error() string { + return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value) +} + +// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not +// found +type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput } + +func (e ErrNoAuthURL) Error() string { + return "Environment variable OS_AUTH_URL needs to be set." +} + +// ErrNoUsername is the error when the OS_USERNAME environment variable is not +// found +type ErrNoUsername struct{ gophercloud.ErrInvalidInput } + +func (e ErrNoUsername) Error() string { + return "Environment variable OS_USERNAME needs to be set." +} + +// ErrNoPassword is the error when the OS_PASSWORD environment variable is not +// found +type ErrNoPassword struct{ gophercloud.ErrInvalidInput } + +func (e ErrNoPassword) Error() string { + return "Environment variable OS_PASSWORD needs to be set." +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/doc.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/doc.go new file mode 100644 index 0000000..b14d69a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/doc.go @@ -0,0 +1,65 @@ +/* +Package tenants provides information and interaction with the +tenants API resource for the OpenStack Identity service. + +See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 +and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants +for more information. + +Example to List Tenants + + listOpts := &tenants.ListOpts{ + Limit: 2, + } + + allPages, err := tenants.List(identityClient, listOpts).AllPages(context.TODO()) + if err != nil { + panic(err) + } + + allTenants, err := tenants.ExtractTenants(allPages) + if err != nil { + panic(err) + } + + for _, tenant := range allTenants { + fmt.Printf("%+v\n", tenant) + } + +Example to Create a Tenant + + createOpts := tenants.CreateOpts{ + Name: "tenant_name", + Description: "this is a tenant", + Enabled: gophercloud.Enabled, + } + + tenant, err := tenants.Create(context.TODO(), identityClient, createOpts).Extract() + if err != nil { + panic(err) + } + +Example to Update a Tenant + + tenantID := "e6db6ed6277c461a853458589063b295" + + updateOpts := tenants.UpdateOpts{ + Description: "this is a new description", + Enabled: gophercloud.Disabled, + } + + tenant, err := tenants.Update(context.TODO(), identityClient, tenantID, updateOpts).Extract() + if err != nil { + panic(err) + } + +Example to Delete a Tenant + + tenantID := "e6db6ed6277c461a853458589063b295" + + err := tenants.Delete(context.TODO(), identitYClient, tenantID).ExtractErr() + if err != nil { + panic(err) + } +*/ +package tenants diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/requests.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/requests.go new file mode 100644 index 0000000..a08980d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/requests.go @@ -0,0 +1,122 @@ +package tenants + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// ListOpts filters the Tenants that are returned by the List call. +type ListOpts struct { + // Marker is the ID of the last Tenant on the previous page. + Marker string `q:"marker"` + + // Limit specifies the page size. + Limit int `q:"limit"` +} + +// List enumerates the Tenants to which the current token has access. +func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager { + url := listURL(client) + if opts != nil { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + url += q.String() + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return TenantPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// CreateOpts represents the options needed when creating new tenant. +type CreateOpts struct { + // Name is the name of the tenant. + Name string `json:"name" required:"true"` + + // Description is the description of the tenant. + Description string `json:"description,omitempty"` + + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// CreateOptsBuilder enables extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToTenantCreateMap() (map[string]any, error) +} + +// ToTenantCreateMap assembles a request body based on the contents of +// a CreateOpts. +func (opts CreateOpts) ToTenantCreateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Create is the operation responsible for creating new tenant. +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToTenantCreateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(ctx, createURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get requests details on a single tenant by ID. +func Get(ctx context.Context, client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(ctx, getURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToTenantUpdateMap() (map[string]any, error) +} + +// UpdateOpts specifies the base attributes that may be updated on an existing +// tenant. +type UpdateOpts struct { + // Name is the name of the tenant. + Name string `json:"name,omitempty"` + + // Description is the description of the tenant. + Description *string `json:"description,omitempty"` + + // Enabled sets the tenant status to enabled or disabled. + Enabled *bool `json:"enabled,omitempty"` +} + +// ToTenantUpdateMap formats an UpdateOpts structure into a request body. +func (opts UpdateOpts) ToTenantUpdateMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "tenant") +} + +// Update is the operation responsible for updating exist tenants by their TenantID. +func Update(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToTenantUpdateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Put(ctx, updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Delete is the operation responsible for permanently deleting a tenant. +func Delete(ctx context.Context, client *gophercloud.ServiceClient, id string) (r DeleteResult) { + resp, err := client.Delete(ctx, deleteURL(client, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/results.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/results.go new file mode 100644 index 0000000..2569ffe --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/results.go @@ -0,0 +1,95 @@ +package tenants + +import ( + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// Tenant is a grouping of users in the identity service. +type Tenant struct { + // ID is a unique identifier for this tenant. + ID string `json:"id"` + + // Name is a friendlier user-facing name for this tenant. + Name string `json:"name"` + + // Description is a human-readable explanation of this Tenant's purpose. + Description string `json:"description"` + + // Enabled indicates whether or not a tenant is active. + Enabled bool `json:"enabled"` +} + +// TenantPage is a single page of Tenant results. +type TenantPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a page of Tenants contains any results. +func (r TenantPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + tenants, err := ExtractTenants(r) + return len(tenants) == 0, err +} + +// NextPageURL extracts the "next" link from the tenants_links section of the result. +func (r TenantPage) NextPageURL() (string, error) { + var s struct { + Links []gophercloud.Link `json:"tenants_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return gophercloud.ExtractNextURL(s.Links) +} + +// ExtractTenants returns a slice of Tenants contained in a single page of +// results. +func ExtractTenants(r pagination.Page) ([]Tenant, error) { + var s struct { + Tenants []Tenant `json:"tenants"` + } + err := (r.(TenantPage)).ExtractInto(&s) + return s.Tenants, err +} + +type tenantResult struct { + gophercloud.Result +} + +// Extract interprets any tenantResults as a Tenant. +func (r tenantResult) Extract() (*Tenant, error) { + var s struct { + Tenant *Tenant `json:"tenant"` + } + err := r.ExtractInto(&s) + return s.Tenant, err +} + +// GetResult is the response from a Get request. Call its Extract method to +// interpret it as a Tenant. +type GetResult struct { + tenantResult +} + +// CreateResult is the response from a Create request. Call its Extract method +// to interpret it as a Tenant. +type CreateResult struct { + tenantResult +} + +// DeleteResult is the response from a Get request. Call its ExtractErr method +// to determine if the call succeeded or failed. +type DeleteResult struct { + gophercloud.ErrResult +} + +// UpdateResult is the response from a Update request. Call its Extract method +// to interpret it as a Tenant. +type UpdateResult struct { + tenantResult +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/urls.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/urls.go new file mode 100644 index 0000000..4c2aaf3 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants/urls.go @@ -0,0 +1,23 @@ +package tenants + +import "github.com/gophercloud/gophercloud/v2" + +func listURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tenants") +} + +func getURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func createURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tenants") +} + +func deleteURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} + +func updateURL(client *gophercloud.ServiceClient, tenantID string) string { + return client.ServiceURL("tenants", tenantID) +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/doc.go new file mode 100644 index 0000000..7cfc367 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/doc.go @@ -0,0 +1,46 @@ +/* +Package tokens provides information and interaction with the token API +resource for the OpenStack Identity service. + +For more information, see: +http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2 + +Example to Create an Unscoped Token from a Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "pass" + } + + token, err := tokens.Create(context.TODO(), identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Tenant ID and Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "password", + TenantID: "fc394f2ab2df4114bde39905f800dc57" + } + + token, err := tokens.Create(context.TODO(), identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Tenant Name and Password + + authOpts := gophercloud.AuthOptions{ + Username: "user", + Password: "password", + TenantName: "tenantname" + } + + token, err := tokens.Create(context.TODO(), identityClient, authOpts).ExtractToken() + if err != nil { + panic(err) + } +*/ +package tokens diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/requests.go new file mode 100644 index 0000000..5afa8fd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/requests.go @@ -0,0 +1,114 @@ +package tokens + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" +) + +// PasswordCredentialsV2 represents the required options to authenticate +// with a username and password. +type PasswordCredentialsV2 struct { + Username string `json:"username" required:"true"` + Password string `json:"password" required:"true"` +} + +// TokenCredentialsV2 represents the required options to authenticate +// with a token. +type TokenCredentialsV2 struct { + ID string `json:"id,omitempty" required:"true"` +} + +// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the +// AuthOptionsBuilder interface. +type AuthOptionsV2 struct { + PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"` + + // The TenantID and TenantName fields are optional for the Identity V2 API. + // Some providers allow you to specify a TenantName instead of the TenantId. + // Some require both. Your provider's authentication policies will determine + // how these fields influence authentication. + TenantID string `json:"tenantId,omitempty"` + TenantName string `json:"tenantName,omitempty"` + + // TokenCredentials allows users to authenticate (possibly as another user) + // with an authentication token ID. + TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"` +} + +// AuthOptionsBuilder allows extensions to add additional parameters to the +// token create request. +type AuthOptionsBuilder interface { + // ToTokenCreateMap assembles the Create request body, returning an error + // if parameters are missing or inconsistent. + ToTokenV2CreateMap() (map[string]any, error) + CanReauth() bool +} + +// AuthOptions are the valid options for Openstack Identity v2 authentication. +// For field descriptions, see gophercloud.AuthOptions. +type AuthOptions struct { + IdentityEndpoint string `json:"-"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + TenantID string `json:"tenantId,omitempty"` + TenantName string `json:"tenantName,omitempty"` + AllowReauth bool `json:"-"` + TokenID string +} + +// ToTokenV2CreateMap builds a token request body from the given AuthOptions. +func (opts AuthOptions) ToTokenV2CreateMap() (map[string]any, error) { + v2Opts := AuthOptionsV2{ + TenantID: opts.TenantID, + TenantName: opts.TenantName, + } + + if opts.Password != "" { + v2Opts.PasswordCredentials = &PasswordCredentialsV2{ + Username: opts.Username, + Password: opts.Password, + } + } else { + v2Opts.TokenCredentials = &TokenCredentialsV2{ + ID: opts.TokenID, + } + } + + b, err := gophercloud.BuildRequestBody(v2Opts, "auth") + if err != nil { + return nil, err + } + return b, nil +} + +func (opts AuthOptions) CanReauth() bool { + return opts.AllowReauth +} + +// Create authenticates to the identity service and attempts to acquire a Token. +// Generally, rather than interact with this call directly, end users should +// call openstack.AuthenticatedClient(), which abstracts all of the gory details +// about navigating service catalogs and such. +func Create(ctx context.Context, client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) { + b, err := auth.ToTokenV2CreateMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(ctx, CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 203}, + OmitHeaders: []string{"X-Auth-Token"}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get validates and retrieves information for user's token. +func Get(ctx context.Context, client *gophercloud.ServiceClient, token string) (r GetResult) { + resp, err := client.Get(ctx, GetURL(client, token), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 203}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/results.go new file mode 100644 index 0000000..516371e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/results.go @@ -0,0 +1,174 @@ +package tokens + +import ( + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants" +) + +// Token provides only the most basic information related to an authentication +// token. +type Token struct { + // ID provides the primary means of identifying a user to the OpenStack API. + // OpenStack defines this field as an opaque value, so do not depend on its + // content. It is safe, however, to compare for equality. + ID string + + // ExpiresAt provides a timestamp in ISO 8601 format, indicating when the + // authentication token becomes invalid. After this point in time, future + // API requests made using this authentication token will respond with + // errors. Either the caller will need to reauthenticate manually, or more + // preferably, the caller should exploit automatic re-authentication. + // See the AuthOptions structure for more details. + ExpiresAt time.Time + + // Tenant provides information about the tenant to which this token grants + // access. + Tenant tenants.Tenant +} + +// Role is a role for a user. +type Role struct { + Name string `json:"name"` +} + +// User is an OpenStack user. +type User struct { + ID string `json:"id"` + Name string `json:"name"` + UserName string `json:"username"` + Roles []Role `json:"roles"` +} + +// Endpoint represents a single API endpoint offered by a service. +// It provides the public and internal URLs, if supported, along with a region +// specifier, again if provided. +// +// The significance of the Region field will depend upon your provider. +// +// In addition, the interface offered by the service will have version +// information associated with it through the VersionId, VersionInfo, and +// VersionList fields, if provided or supported. +// +// In all cases, fields which aren't supported by the provider and service +// combined will assume a zero-value (""). +type Endpoint struct { + TenantID string `json:"tenantId"` + PublicURL string `json:"publicURL"` + InternalURL string `json:"internalURL"` + AdminURL string `json:"adminURL"` + Region string `json:"region"` + VersionID string `json:"versionId"` + VersionInfo string `json:"versionInfo"` + VersionList string `json:"versionList"` +} + +// CatalogEntry provides a type-safe interface to an Identity API V2 service +// catalog listing. +// +// Each class of service, such as cloud DNS or block storage services, will have +// a single CatalogEntry representing it. +// +// Note: when looking for the desired service, try, whenever possible, to key +// off the type field. Otherwise, you'll tie the representation of the service +// to a specific provider. +type CatalogEntry struct { + // Name will contain the provider-specified name for the service. + Name string `json:"name"` + + // Type will contain a type string if OpenStack defines a type for the + // service. Otherwise, for provider-specific services, the provider may assign + // their own type strings. + Type string `json:"type"` + + // Endpoints will let the caller iterate over all the different endpoints that + // may exist for the service. + Endpoints []Endpoint `json:"endpoints"` +} + +// ServiceCatalog provides a view into the service catalog from a previous, +// successful authentication. +type ServiceCatalog struct { + Entries []CatalogEntry +} + +// CreateResult is the response from a Create request. Use ExtractToken() to +// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a +// service catalog. +type CreateResult struct { + gophercloud.Result +} + +// GetResult is the deferred response from a Get call, which is the same with a +// Created token. Use ExtractUser() to interpret it as a User. +type GetResult struct { + CreateResult +} + +// ExtractToken returns the just-created Token from a CreateResult. +func (r CreateResult) ExtractToken() (*Token, error) { + var s struct { + Access struct { + Token struct { + Expires string `json:"expires"` + ID string `json:"id"` + Tenant tenants.Tenant `json:"tenant"` + } `json:"token"` + } `json:"access"` + } + + err := r.ExtractInto(&s) + if err != nil { + return nil, err + } + + expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires) + if err != nil { + return nil, err + } + + return &Token{ + ID: s.Access.Token.ID, + ExpiresAt: expiresTs, + Tenant: s.Access.Token.Tenant, + }, nil +} + +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + var s struct { + Access struct { + Token struct { + ID string `json:"id"` + } `json:"token"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return s.Access.Token.ID, err +} + +// ExtractServiceCatalog returns the ServiceCatalog that was generated along +// with the user's Token. +func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { + var s struct { + Access struct { + Entries []CatalogEntry `json:"serviceCatalog"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return &ServiceCatalog{Entries: s.Access.Entries}, err +} + +// ExtractUser returns the User from a GetResult. +func (r GetResult) ExtractUser() (*User, error) { + var s struct { + Access struct { + User User `json:"user"` + } `json:"access"` + } + err := r.ExtractInto(&s) + return &s.Access.User, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/urls.go new file mode 100644 index 0000000..845cdb5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens/urls.go @@ -0,0 +1,13 @@ +package tokens + +import "github.com/gophercloud/gophercloud/v2" + +// CreateURL generates the URL used to create new Tokens. +func CreateURL(client *gophercloud.ServiceClient) string { + return client.ServiceURL("tokens") +} + +// GetURL generates the URL used to Validate Tokens. +func GetURL(client *gophercloud.ServiceClient, token string) string { + return client.ServiceURL("tokens", token) +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/doc.go new file mode 100644 index 0000000..bd74473 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/doc.go @@ -0,0 +1,40 @@ +/* +Package tokens provides information and interaction with the EC2 token API +resource for the OpenStack Identity service. + +For more information, see: +https://docs.openstack.org/api-ref/identity/v2-ext/ + +Example to Create a Token From an EC2 access and secret keys + + var authOptions tokens.AuthOptionsBuilder + authOptions = &ec2tokens.AuthOptions{ + Access: "a7f1e798b7c2417cba4a02de97dc3cdc", + Secret: "18f4f6761ada4e3795fa5273c30349b9", + } + + token, err := ec2tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to auth a client using EC2 access and secret keys + + client, err := openstack.NewClient("http://localhost:5000/v3") + if err != nil { + panic(err) + } + + var authOptions tokens.AuthOptionsBuilder + authOptions = &ec2tokens.AuthOptions{ + Access: "a7f1e798b7c2417cba4a02de97dc3cdc", + Secret: "18f4f6761ada4e3795fa5273c30349b9", + AllowReauth: true, + } + + err = openstack.AuthenticateV3(context.TODO(), client, authOptions, gophercloud.EndpointOpts{}) + if err != nil { + panic(err) + } +*/ +package ec2tokens diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/requests.go new file mode 100644 index 0000000..5b1f3d6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/requests.go @@ -0,0 +1,378 @@ +package ec2tokens + +import ( + "context" + "crypto/hmac" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + "fmt" + "net/url" + "sort" + "strings" + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" +) + +const ( + // EC2CredentialsAwsRequestV4 is a constant, used to generate AWS + // Credential V4. + EC2CredentialsAwsRequestV4 = "aws4_request" + // EC2CredentialsHmacSha1V2 is a HMAC SHA1 signature method. Used to + // generate AWS Credential V2. + EC2CredentialsHmacSha1V2 = "HmacSHA1" + // EC2CredentialsHmacSha256V2 is a HMAC SHA256 signature method. Used + // to generate AWS Credential V2. + EC2CredentialsHmacSha256V2 = "HmacSHA256" + // EC2CredentialsAwsHmacV4 is an AWS signature V4 signing method. + // More details: + // https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html + EC2CredentialsAwsHmacV4 = "AWS4-HMAC-SHA256" + // EC2CredentialsTimestampFormatV4 is an AWS signature V4 timestamp + // format. + EC2CredentialsTimestampFormatV4 = "20060102T150405Z" + // EC2CredentialsDateFormatV4 is an AWS signature V4 date format. + EC2CredentialsDateFormatV4 = "20060102" +) + +// AuthOptions represents options for authenticating a user using EC2 credentials. +type AuthOptions struct { + // Access is the EC2 Credential Access ID. + Access string `json:"access" required:"true"` + // Secret is the EC2 Credential Secret, used to calculate signature. + // Not used, when a Signature is is. + Secret string `json:"-"` + // Host is a HTTP request Host header. Used to calculate an AWS + // signature V2. For signature V4 set the Host inside Headers map. + // Optional. + Host string `json:"host"` + // Path is a HTTP request path. Optional. + Path string `json:"path"` + // Verb is a HTTP request method. Optional. + Verb string `json:"verb"` + // Headers is a map of HTTP request headers. Optional. + Headers map[string]string `json:"headers"` + // Region is a region name to calculate an AWS signature V4. Optional. + Region string `json:"-"` + // Service is a service name to calculate an AWS signature V4. Optional. + Service string `json:"-"` + // Params is a map of GET method parameters. Optional. + Params map[string]string `json:"params"` + // AllowReauth allows Gophercloud to re-authenticate automatically + // if/when your token expires. + AllowReauth bool `json:"-"` + // Signature can be either a []byte (encoded to base64 automatically) or + // a string. You can set the singature explicitly, when you already know + // it. In this case default Params won't be automatically set. Optional. + Signature any `json:"signature"` + // BodyHash is a HTTP request body sha256 hash. When nil and Signature + // is not set, a random hash is generated. Optional. + BodyHash *string `json:"body_hash"` + // Timestamp is a timestamp to calculate a V4 signature. Optional. + Timestamp *time.Time `json:"-"` + // Token is a []byte string (encoded to base64 automatically) which was + // signed by an EC2 secret key. Used by S3 tokens for validation only. + // Token must be set with a Signature. If a Signature is not provided, + // a Token will be generated automatically along with a Signature. + Token []byte `json:"token,omitempty"` +} + +// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string +// for an AWS signature V2. +// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L133 +func EC2CredentialsBuildCanonicalQueryStringV2(params map[string]string) string { + var keys []string + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + var pairs []string + for _, k := range keys { + pairs = append(pairs, fmt.Sprintf("%s=%s", k, url.QueryEscape(params[k]))) + } + + return strings.Join(pairs, "&") +} + +// EC2CredentialsBuildStringToSignV2 builds a string to sign an AWS signature +// V2. +// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L148 +func EC2CredentialsBuildStringToSignV2(opts AuthOptions) []byte { + stringToSign := strings.Join([]string{ + opts.Verb, + opts.Host, + opts.Path, + }, "\n") + + return []byte(strings.Join([]string{ + stringToSign, + EC2CredentialsBuildCanonicalQueryStringV2(opts.Params), + }, "\n")) +} + +// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string +// for an AWS signature V4. +// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L244 +func EC2CredentialsBuildCanonicalQueryStringV4(verb string, params map[string]string) string { + if verb == "POST" { + return "" + } + return EC2CredentialsBuildCanonicalQueryStringV2(params) +} + +// EC2CredentialsBuildCanonicalHeadersV4 builds a canonical string based on +// "headers" map and "signedHeaders" string parameters. +// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L216 +func EC2CredentialsBuildCanonicalHeadersV4(headers map[string]string, signedHeaders string) string { + headersLower := make(map[string]string, len(headers)) + for k, v := range headers { + headersLower[strings.ToLower(k)] = v + } + + var headersList []string + for _, h := range strings.Split(signedHeaders, ";") { + if v, ok := headersLower[h]; ok { + headersList = append(headersList, h+":"+v) + } + } + + return strings.Join(headersList, "\n") + "\n" +} + +// EC2CredentialsBuildSignatureKeyV4 builds a HMAC 256 signature key based on +// input parameters. +// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L169 +func EC2CredentialsBuildSignatureKeyV4(secret, region, service string, date time.Time) []byte { + kDate := sumHMAC256([]byte("AWS4"+secret), []byte(date.Format(EC2CredentialsDateFormatV4))) + kRegion := sumHMAC256(kDate, []byte(region)) + kService := sumHMAC256(kRegion, []byte(service)) + return sumHMAC256(kService, []byte(EC2CredentialsAwsRequestV4)) +} + +// EC2CredentialsBuildStringToSignV4 builds an AWS v4 signature string to sign +// based on input parameters. +// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L251 +func EC2CredentialsBuildStringToSignV4(opts AuthOptions, signedHeaders string, bodyHash string, date time.Time) []byte { + scope := strings.Join([]string{ + date.Format(EC2CredentialsDateFormatV4), + opts.Region, + opts.Service, + EC2CredentialsAwsRequestV4, + }, "/") + + canonicalRequest := strings.Join([]string{ + opts.Verb, + opts.Path, + EC2CredentialsBuildCanonicalQueryStringV4(opts.Verb, opts.Params), + EC2CredentialsBuildCanonicalHeadersV4(opts.Headers, signedHeaders), + signedHeaders, + bodyHash, + }, "\n") + hash := sha256.Sum256([]byte(canonicalRequest)) + + return []byte(strings.Join([]string{ + EC2CredentialsAwsHmacV4, + date.Format(EC2CredentialsTimestampFormatV4), + scope, + hex.EncodeToString(hash[:]), + }, "\n")) +} + +// EC2CredentialsBuildSignatureV4 builds an AWS v4 signature based on input +// parameters. +// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L285..L286 +func EC2CredentialsBuildSignatureV4(key []byte, stringToSign []byte) string { + return hex.EncodeToString(sumHMAC256(key, stringToSign)) +} + +// EC2CredentialsBuildAuthorizationHeaderV4 builds an AWS v4 Authorization +// header based on auth parameters, date and signature +func EC2CredentialsBuildAuthorizationHeaderV4(opts AuthOptions, signedHeaders string, signature string, date time.Time) string { + return fmt.Sprintf("%s Credential=%s/%s/%s/%s/%s, SignedHeaders=%s, Signature=%s", + EC2CredentialsAwsHmacV4, + opts.Access, + date.Format(EC2CredentialsDateFormatV4), + opts.Region, + opts.Service, + EC2CredentialsAwsRequestV4, + signedHeaders, + signature) +} + +// ToTokenV3ScopeMap is a dummy method to satisfy tokens.AuthOptionsBuilder +// interface. +func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]any, error) { + return nil, nil +} + +// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v3 tokens package. +func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]any) (map[string]string, error) { + return nil, nil +} + +// CanReauth is a method method to satisfy tokens.AuthOptionsBuilder interface +func (opts *AuthOptions) CanReauth() bool { + return opts.AllowReauth +} + +// ToTokenV3CreateMap formats an AuthOptions into a create request. +func (opts *AuthOptions) ToTokenV3CreateMap(map[string]any) (map[string]any, error) { + b, err := gophercloud.BuildRequestBody(opts, "credentials") + if err != nil { + return nil, err + } + + if opts.Signature != nil { + return b, nil + } + + // calculate signature, when it is not set + c, _ := b["credentials"].(map[string]any) + h := interfaceToMap(c, "headers") + p := interfaceToMap(c, "params") + + // detect and process a signature v2 + if v, ok := p["SignatureVersion"]; ok && v == "2" { + delete(c, "body_hash") + delete(c, "headers") + if v, ok := p["SignatureMethod"]; ok { + // params is a map of strings + strToSign := EC2CredentialsBuildStringToSignV2(*opts) + switch v { + case EC2CredentialsHmacSha1V2: + // keystone uses this method only when HmacSHA256 is not available on the server side + // https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L151..L156 + c["signature"] = sumHMAC1([]byte(opts.Secret), strToSign) + return b, nil + case EC2CredentialsHmacSha256V2: + c["signature"] = sumHMAC256([]byte(opts.Secret), strToSign) + return b, nil + } + return nil, fmt.Errorf("unsupported signature method: %s", v) + } + return nil, fmt.Errorf("signature method must be provided") + } else if ok { + return nil, fmt.Errorf("unsupported signature version: %s", v) + } + + // it is not a signature v2, but a signature v4 + date := time.Now().UTC() + if opts.Timestamp != nil { + date = *opts.Timestamp + } + if v := c["body_hash"]; v == nil { + // when body_hash is not set, generate a random one + bodyHash, err := randomBodyHash() + if err != nil { + return nil, fmt.Errorf("failed to generate random hash") + } + c["body_hash"] = bodyHash + } + + signedHeaders := h["X-Amz-SignedHeaders"] + + stringToSign := EC2CredentialsBuildStringToSignV4(*opts, signedHeaders, c["body_hash"].(string), date) + key := EC2CredentialsBuildSignatureKeyV4(opts.Secret, opts.Region, opts.Service, date) + c["signature"] = EC2CredentialsBuildSignatureV4(key, stringToSign) + h["X-Amz-Date"] = date.Format(EC2CredentialsTimestampFormatV4) + h["Authorization"] = EC2CredentialsBuildAuthorizationHeaderV4(*opts, signedHeaders, c["signature"].(string), date) + + // token is only used for S3 tokens validation and will be removed when using EC2 validation + c["token"] = stringToSign + + return b, nil +} + +// Create authenticates and either generates a new token from EC2 credentials +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) { + b, err := opts.ToTokenV3CreateMap(nil) + if err != nil { + r.Err = err + return + } + + // delete "token" element, since it is used in s3tokens + deleteBodyElements(b, "token") + + resp, err := c.Post(ctx, ec2tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{ + MoreHeaders: map[string]string{"X-Auth-Token": ""}, + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ValidateS3Token authenticates an S3 request using EC2 credentials. Doesn't +// generate a new token ID, but returns a tokens.CreateResult. +func ValidateS3Token(ctx context.Context, c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) { + b, err := opts.ToTokenV3CreateMap(nil) + if err != nil { + r.Err = err + return + } + + // delete unused element, since it is used in ec2tokens only + deleteBodyElements(b, "body_hash", "headers", "host", "params", "path", "verb") + + resp, err := c.Post(ctx, s3tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{ + MoreHeaders: map[string]string{"X-Auth-Token": ""}, + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// The following are small helper functions used to help build the signature. + +// sumHMAC1 is a func to implement the HMAC SHA1 signature method. +func sumHMAC1(key []byte, data []byte) []byte { + hash := hmac.New(sha1.New, key) + hash.Write(data) + return hash.Sum(nil) +} + +// sumHMAC256 is a func to implement the HMAC SHA256 signature method. +func sumHMAC256(key []byte, data []byte) []byte { + hash := hmac.New(sha256.New, key) + hash.Write(data) + return hash.Sum(nil) +} + +// randomBodyHash is a func to generate a random sha256 hexdigest. +func randomBodyHash() (string, error) { + h := make([]byte, 64) + if _, err := rand.Read(h); err != nil { + return "", err + } + return hex.EncodeToString(h), nil +} + +// interfaceToMap is a func used to represent a "credentials" map element as a +// "map[string]string" +func interfaceToMap(c map[string]any, key string) map[string]string { + // convert map[string]any to map[string]string + m := make(map[string]string) + if v, _ := c[key].(map[string]any); v != nil { + for k, v := range v { + m[k] = v.(string) + } + } + + c[key] = m + + return m +} + +// deleteBodyElements deletes map body elements +func deleteBodyElements(b map[string]any, elements ...string) { + if c, ok := b["credentials"].(map[string]any); ok { + for _, k := range elements { + delete(c, k) + } + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/urls.go new file mode 100644 index 0000000..91add91 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens/urls.go @@ -0,0 +1,11 @@ +package ec2tokens + +import "github.com/gophercloud/gophercloud/v2" + +func ec2tokensURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("ec2tokens") +} + +func s3tokensURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("s3tokens") +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/doc.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/doc.go new file mode 100644 index 0000000..c0cfa92 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/doc.go @@ -0,0 +1,122 @@ +/* +Package oauth1 enables management of OpenStack OAuth1 tokens and Authentication. + +Example to Create an OAuth1 Consumer + + createConsumerOpts := oauth1.CreateConsumerOpts{ + Description: "My consumer", + } + consumer, err := oauth1.CreateConsumer(context.TODO(), identityClient, createConsumerOpts).Extract() + if err != nil { + panic(err) + } + + // NOTE: Consumer secret is available only on create response + fmt.Printf("Consumer: %+v\n", consumer) + +Example to Request an unauthorized OAuth1 token + + requestTokenOpts := oauth1.RequestTokenOpts{ + OAuthConsumerKey: consumer.ID, + OAuthConsumerSecret: consumer.Secret, + OAuthSignatureMethod: oauth1.HMACSHA1, + RequestedProjectID: projectID, + } + requestToken, err := oauth1.RequestToken(context.TODO(), identityClient, requestTokenOpts).Extract() + if err != nil { + panic(err) + } + + // NOTE: Request token secret is available only on request response + fmt.Printf("Request token: %+v\n", requestToken) + +Example to Authorize an unauthorized OAuth1 token + + authorizeTokenOpts := oauth1.AuthorizeTokenOpts{ + Roles: []oauth1.Role{ + {Name: "member"}, + }, + } + authToken, err := oauth1.AuthorizeToken(context.TODO(), identityClient, requestToken.OAuthToken, authorizeTokenOpts).Extract() + if err != nil { + panic(err) + } + + fmt.Printf("Verifier ID of the unauthorized Token: %+v\n", authToken.OAuthVerifier) + +Example to Create an OAuth1 Access Token + + accessTokenOpts := oauth1.CreateAccessTokenOpts{ + OAuthConsumerKey: consumer.ID, + OAuthConsumerSecret: consumer.Secret, + OAuthToken: requestToken.OAuthToken, + OAuthTokenSecret: requestToken.OAuthTokenSecret, + OAuthVerifier: authToken.OAuthVerifier, + OAuthSignatureMethod: oauth1.HMACSHA1, + } + accessToken, err := oauth1.CreateAccessToken(context.TODO(), identityClient, accessTokenOpts).Extract() + if err != nil { + panic(err) + } + + // NOTE: Access token secret is available only on create response + fmt.Printf("OAuth1 Access Token: %+v\n", accessToken) + +Example to List User's OAuth1 Access Tokens + + allPages, err := oauth1.ListAccessTokens(identityClient, userID).AllPages(context.TODO()) + if err != nil { + panic(err) + } + accessTokens, err := oauth1.ExtractAccessTokens(allPages) + if err != nil { + panic(err) + } + + for _, accessToken := range accessTokens { + fmt.Printf("Access Token: %+v\n", accessToken) + } + +Example to Authenticate a client using OAuth1 method + + client, err := openstack.NewClient("http://localhost:5000/v3") + if err != nil { + panic(err) + } + + authOptions := &oauth1.AuthOptions{ + // consumer token, created earlier + OAuthConsumerKey: consumer.ID, + OAuthConsumerSecret: consumer.Secret, + // access token, created earlier + OAuthToken: accessToken.OAuthToken, + OAuthTokenSecret: accessToken.OAuthTokenSecret, + OAuthSignatureMethod: oauth1.HMACSHA1, + } + err = openstack.AuthenticateV3(context.TODO(), client, authOptions, gophercloud.EndpointOpts{}) + if err != nil { + panic(err) + } + +Example to Create a Token using OAuth1 method + + var oauth1Token struct { + tokens.Token + oauth1.TokenExt + } + + createOpts := &oauth1.AuthOptions{ + // consumer token, created earlier + OAuthConsumerKey: consumer.ID, + OAuthConsumerSecret: consumer.Secret, + // access token, created earlier + OAuthToken: accessToken.OAuthToken, + OAuthTokenSecret: accessToken.OAuthTokenSecret, + OAuthSignatureMethod: oauth1.HMACSHA1, + } + err := tokens.Create(context.TODO(), identityClient, createOpts).ExtractInto(&oauth1Token) + if err != nil { + panic(err) + } +*/ +package oauth1 diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/requests.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/requests.go new file mode 100644 index 0000000..8c66b36 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/requests.go @@ -0,0 +1,588 @@ +package oauth1 + +import ( + "context" + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "fmt" + "io" + "math/rand" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// Type SignatureMethod is a OAuth1 SignatureMethod type. +type SignatureMethod string + +const ( + // HMACSHA1 is a recommended OAuth1 signature method. + HMACSHA1 SignatureMethod = "HMAC-SHA1" + + // PLAINTEXT signature method is not recommended to be used in + // production environment. + PLAINTEXT SignatureMethod = "PLAINTEXT" + + // OAuth1TokenContentType is a supported content type for an OAuth1 + // token. + OAuth1TokenContentType = "application/x-www-form-urlencoded" +) + +// AuthOptions represents options for authenticating a user using OAuth1 tokens. +type AuthOptions struct { + // OAuthConsumerKey is the OAuth1 Consumer Key. + OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"` + + // OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate + // an OAuth1 request signature. + OAuthConsumerSecret string `required:"true"` + + // OAuthToken is the OAuth1 Request Token. + OAuthToken string `q:"oauth_token" required:"true"` + + // OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate + // an OAuth1 request signature. + OAuthTokenSecret string `required:"true"` + + // OAuthSignatureMethod is the OAuth1 signature method the Consumer used + // to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT". + // "PLAINTEXT" is not recommended for production usage. + OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"` + + // OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix + // timestamp will be used. + OAuthTimestamp *time.Time + + // OAuthNonce is an OAuth1 request nonce. Nonce must be a random string, + // uniquely generated for each request. Will be generated automatically + // when it is not set. + OAuthNonce string `q:"oauth_nonce"` + + // AllowReauth allows Gophercloud to re-authenticate automatically + // if/when your token expires. + AllowReauth bool +} + +// ToTokenV3HeadersMap builds the headers required for an OAuth1-based create +// request. +func (opts AuthOptions) ToTokenV3HeadersMap(headerOpts map[string]any) (map[string]string, error) { + q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "") + if err != nil { + return nil, err + } + + signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret} + + method := headerOpts["method"].(string) + u := headerOpts["url"].(string) + stringToSign := buildStringToSign(method, u, q.Query()) + signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys)) + + authHeader := buildAuthHeader(q.Query(), signature) + + headers := map[string]string{ + "Authorization": authHeader, + "X-Auth-Token": "", + } + + return headers, nil +} + +// ToTokenV3ScopeMap allows AuthOptions to satisfy the tokens.AuthOptionsBuilder +// interface. +func (opts AuthOptions) ToTokenV3ScopeMap() (map[string]any, error) { + return nil, nil +} + +// CanReauth allows AuthOptions to satisfy the tokens.AuthOptionsBuilder +// interface. +func (opts AuthOptions) CanReauth() bool { + return opts.AllowReauth +} + +// ToTokenV3CreateMap builds a create request body. +func (opts AuthOptions) ToTokenV3CreateMap(map[string]any) (map[string]any, error) { + // identityReq defines the "identity" portion of an OAuth1-based authentication + // create request body. + type identityReq struct { + Methods []string `json:"methods"` + OAuth1 struct{} `json:"oauth1"` + } + + // authReq defines the "auth" portion of an OAuth1-based authentication + // create request body. + type authReq struct { + Identity identityReq `json:"identity"` + } + + // oauth1Request defines how an OAuth1-based authentication create + // request body looks. + type oauth1Request struct { + Auth authReq `json:"auth"` + } + + var req oauth1Request + + req.Auth.Identity.Methods = []string{"oauth1"} + return gophercloud.BuildRequestBody(req, "") +} + +// Create authenticates and either generates a new OpenStack token +// from an OAuth1 token. +func Create(ctx context.Context, client *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) { + b, err := opts.ToTokenV3CreateMap(nil) + if err != nil { + r.Err = err + return + } + + headerOpts := map[string]any{ + "method": "POST", + "url": authURL(client), + } + + h, err := opts.ToTokenV3HeadersMap(headerOpts) + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, authURL(client), b, &r.Body, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// CreateConsumerOptsBuilder allows extensions to add additional parameters to +// the CreateConsumer request. +type CreateConsumerOptsBuilder interface { + ToOAuth1CreateConsumerMap() (map[string]any, error) +} + +// CreateConsumerOpts provides options used to create a new Consumer. +type CreateConsumerOpts struct { + // Description is the consumer description. + Description string `json:"description"` +} + +// ToOAuth1CreateConsumerMap formats a CreateConsumerOpts into a create request. +func (opts CreateConsumerOpts) ToOAuth1CreateConsumerMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "consumer") +} + +// CreateConsumer creates a new Consumer. +func CreateConsumer(ctx context.Context, client *gophercloud.ServiceClient, opts CreateConsumerOptsBuilder) (r CreateConsumerResult) { + b, err := opts.ToOAuth1CreateConsumerMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Post(ctx, consumersURL(client), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// DeleteConsumer deletes a Consumer. +func DeleteConsumer(ctx context.Context, client *gophercloud.ServiceClient, id string) (r DeleteConsumerResult) { + resp, err := client.Delete(ctx, consumerURL(client, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// List enumerates Consumers. +func ListConsumers(client *gophercloud.ServiceClient) pagination.Pager { + return pagination.NewPager(client, consumersURL(client), func(r pagination.PageResult) pagination.Page { + return ConsumersPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// GetConsumer retrieves details on a single Consumer by ID. +func GetConsumer(ctx context.Context, client *gophercloud.ServiceClient, id string) (r GetConsumerResult) { + resp, err := client.Get(ctx, consumerURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// UpdateConsumerOpts provides options used to update a consumer. +type UpdateConsumerOpts struct { + // Description is the consumer description. + Description string `json:"description"` +} + +// ToOAuth1UpdateConsumerMap formats an UpdateConsumerOpts into a consumer update +// request. +func (opts UpdateConsumerOpts) ToOAuth1UpdateConsumerMap() (map[string]any, error) { + return gophercloud.BuildRequestBody(opts, "consumer") +} + +// UpdateConsumer updates an existing Consumer. +func UpdateConsumer(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateConsumerOpts) (r UpdateConsumerResult) { + b, err := opts.ToOAuth1UpdateConsumerMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Patch(ctx, consumerURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// RequestTokenOptsBuilder allows extensions to add additional parameters to the +// RequestToken request. +type RequestTokenOptsBuilder interface { + ToOAuth1RequestTokenHeaders(string, string) (map[string]string, error) +} + +// RequestTokenOpts provides options used to get a consumer unauthorized +// request token. +type RequestTokenOpts struct { + // OAuthConsumerKey is the OAuth1 Consumer Key. + OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"` + + // OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate + // an OAuth1 request signature. + OAuthConsumerSecret string `required:"true"` + + // OAuthSignatureMethod is the OAuth1 signature method the Consumer used + // to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT". + // "PLAINTEXT" is not recommended for production usage. + OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"` + + // OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix + // timestamp will be used. + OAuthTimestamp *time.Time + + // OAuthNonce is an OAuth1 request nonce. Nonce must be a random string, + // uniquely generated for each request. Will be generated automatically + // when it is not set. + OAuthNonce string `q:"oauth_nonce"` + + // RequestedProjectID is a Project ID a consumer user requested an + // access to. + RequestedProjectID string `h:"Requested-Project-Id"` +} + +// ToOAuth1RequestTokenHeaders formats a RequestTokenOpts into a map of request +// headers. +func (opts RequestTokenOpts) ToOAuth1RequestTokenHeaders(method, u string) (map[string]string, error) { + q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "oob") + if err != nil { + return nil, err + } + + h, err := gophercloud.BuildHeaders(opts) + if err != nil { + return nil, err + } + + signatureKeys := []string{opts.OAuthConsumerSecret} + stringToSign := buildStringToSign(method, u, q.Query()) + signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys)) + authHeader := buildAuthHeader(q.Query(), signature) + + h["Authorization"] = authHeader + + return h, nil +} + +// RequestToken requests an unauthorized OAuth1 Token. +func RequestToken(ctx context.Context, client *gophercloud.ServiceClient, opts RequestTokenOptsBuilder) (r TokenResult) { + h, err := opts.ToOAuth1RequestTokenHeaders("POST", requestTokenURL(client)) + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, requestTokenURL(client), nil, nil, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{201}, + KeepResponseBody: true, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + if r.Err != nil { + return + } + defer resp.Body.Close() + if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType { + r.Err = fmt.Errorf("unsupported Content-Type: %q", v) + return + } + r.Body, r.Err = io.ReadAll(resp.Body) + return +} + +// AuthorizeTokenOptsBuilder allows extensions to add additional parameters to +// the AuthorizeToken request. +type AuthorizeTokenOptsBuilder interface { + ToOAuth1AuthorizeTokenMap() (map[string]any, error) +} + +// AuthorizeTokenOpts provides options used to authorize a request token. +type AuthorizeTokenOpts struct { + Roles []Role `json:"roles"` +} + +// Role is a struct representing a role object in a AuthorizeTokenOpts struct. +type Role struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} + +// ToOAuth1AuthorizeTokenMap formats an AuthorizeTokenOpts into an authorize token +// request. +func (opts AuthorizeTokenOpts) ToOAuth1AuthorizeTokenMap() (map[string]any, error) { + for _, r := range opts.Roles { + if r == (Role{}) { + return nil, fmt.Errorf("role must not be empty") + } + } + return gophercloud.BuildRequestBody(opts, "") +} + +// AuthorizeToken authorizes an unauthorized consumer token. +func AuthorizeToken(ctx context.Context, client *gophercloud.ServiceClient, id string, opts AuthorizeTokenOptsBuilder) (r AuthorizeTokenResult) { + b, err := opts.ToOAuth1AuthorizeTokenMap() + if err != nil { + r.Err = err + return + } + resp, err := client.Put(ctx, authorizeTokenURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// CreateAccessTokenOptsBuilder allows extensions to add additional parameters +// to the CreateAccessToken request. +type CreateAccessTokenOptsBuilder interface { + ToOAuth1CreateAccessTokenHeaders(string, string) (map[string]string, error) +} + +// CreateAccessTokenOpts provides options used to create an OAuth1 token. +type CreateAccessTokenOpts struct { + // OAuthConsumerKey is the OAuth1 Consumer Key. + OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"` + + // OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate + // an OAuth1 request signature. + OAuthConsumerSecret string `required:"true"` + + // OAuthToken is the OAuth1 Request Token. + OAuthToken string `q:"oauth_token" required:"true"` + + // OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate + // an OAuth1 request signature. + OAuthTokenSecret string `required:"true"` + + // OAuthVerifier is the OAuth1 verification code. + OAuthVerifier string `q:"oauth_verifier" required:"true"` + + // OAuthSignatureMethod is the OAuth1 signature method the Consumer used + // to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT". + // "PLAINTEXT" is not recommended for production usage. + OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"` + + // OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix + // timestamp will be used. + OAuthTimestamp *time.Time + + // OAuthNonce is an OAuth1 request nonce. Nonce must be a random string, + // uniquely generated for each request. Will be generated automatically + // when it is not set. + OAuthNonce string `q:"oauth_nonce"` +} + +// ToOAuth1CreateAccessTokenHeaders formats a CreateAccessTokenOpts into a map of +// request headers. +func (opts CreateAccessTokenOpts) ToOAuth1CreateAccessTokenHeaders(method, u string) (map[string]string, error) { + q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "") + if err != nil { + return nil, err + } + + signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret} + stringToSign := buildStringToSign(method, u, q.Query()) + signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys)) + authHeader := buildAuthHeader(q.Query(), signature) + + headers := map[string]string{ + "Authorization": authHeader, + } + + return headers, nil +} + +// CreateAccessToken creates a new OAuth1 Access Token +func CreateAccessToken(ctx context.Context, client *gophercloud.ServiceClient, opts CreateAccessTokenOptsBuilder) (r TokenResult) { + h, err := opts.ToOAuth1CreateAccessTokenHeaders("POST", createAccessTokenURL(client)) + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(ctx, createAccessTokenURL(client), nil, nil, &gophercloud.RequestOpts{ + MoreHeaders: h, + OkCodes: []int{201}, + KeepResponseBody: true, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + if r.Err != nil { + return + } + defer resp.Body.Close() + if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType { + r.Err = fmt.Errorf("unsupported Content-Type: %q", v) + return + } + r.Body, r.Err = io.ReadAll(resp.Body) + return +} + +// GetAccessToken retrieves details on a single OAuth1 access token by an ID. +func GetAccessToken(ctx context.Context, client *gophercloud.ServiceClient, userID string, id string) (r GetAccessTokenResult) { + resp, err := client.Get(ctx, userAccessTokenURL(client, userID, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// RevokeAccessToken revokes an OAuth1 access token. +func RevokeAccessToken(ctx context.Context, client *gophercloud.ServiceClient, userID string, id string) (r RevokeAccessTokenResult) { + resp, err := client.Delete(ctx, userAccessTokenURL(client, userID, id), nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ListAccessTokens enumerates authorized access tokens. +func ListAccessTokens(client *gophercloud.ServiceClient, userID string) pagination.Pager { + url := userAccessTokensURL(client, userID) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return AccessTokensPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ListAccessTokenRoles enumerates authorized access token roles. +func ListAccessTokenRoles(client *gophercloud.ServiceClient, userID string, id string) pagination.Pager { + url := userAccessTokenRolesURL(client, userID, id) + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + return AccessTokenRolesPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// GetAccessTokenRole retrieves details on a single OAuth1 access token role by +// an ID. +func GetAccessTokenRole(ctx context.Context, client *gophercloud.ServiceClient, userID string, id string, roleID string) (r GetAccessTokenRoleResult) { + resp, err := client.Get(ctx, userAccessTokenRoleURL(client, userID, id, roleID), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// The following are small helper functions used to help build the signature. + +// buildOAuth1QueryString builds a URLEncoded parameters string specific for +// OAuth1-based requests. +func buildOAuth1QueryString(opts any, timestamp *time.Time, callback string) (*url.URL, error) { + q, err := gophercloud.BuildQueryString(opts) + if err != nil { + return nil, err + } + + query := q.Query() + + if timestamp != nil { + // use provided timestamp + query.Set("oauth_timestamp", strconv.FormatInt(timestamp.Unix(), 10)) + } else { + // use current timestamp + query.Set("oauth_timestamp", strconv.FormatInt(time.Now().UTC().Unix(), 10)) + } + + if query.Get("oauth_nonce") == "" { + // when nonce is not set, generate a random one + query.Set("oauth_nonce", strconv.FormatInt(rand.Int63(), 10)+query.Get("oauth_timestamp")) + } + + if callback != "" { + query.Set("oauth_callback", callback) + } + query.Set("oauth_version", "1.0") + + return &url.URL{RawQuery: query.Encode()}, nil +} + +// buildStringToSign builds a string to be signed. +func buildStringToSign(method string, u string, query url.Values) []byte { + parsedURL, _ := url.Parse(u) + p := parsedURL.Port() + s := parsedURL.Scheme + + // Default scheme port must be stripped + if s == "http" && p == "80" || s == "https" && p == "443" { + parsedURL.Host = strings.TrimSuffix(parsedURL.Host, ":"+p) + } + + // Ensure that URL doesn't contain queries + parsedURL.RawQuery = "" + + v := strings.Join( + []string{method, url.QueryEscape(parsedURL.String()), url.QueryEscape(query.Encode())}, "&") + + return []byte(v) +} + +// signString signs a string using an OAuth1 signature method. +func signString(signatureMethod SignatureMethod, strToSign []byte, signatureKeys []string) string { + var key []byte + for i, k := range signatureKeys { + key = append(key, []byte(url.QueryEscape(k))...) + if i == 0 { + key = append(key, '&') + } + } + + var signedString string + switch signatureMethod { + case PLAINTEXT: + signedString = string(key) + default: + h := hmac.New(sha1.New, key) + h.Write(strToSign) + signedString = base64.StdEncoding.EncodeToString(h.Sum(nil)) + } + + return signedString +} + +// buildAuthHeader generates an OAuth1 Authorization header with a signature +// calculated using an OAuth1 signature method. +func buildAuthHeader(query url.Values, signature string) string { + var authHeader []string + var keys []string + for k := range query { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + for _, v := range query[k] { + authHeader = append(authHeader, fmt.Sprintf("%s=%q", k, url.QueryEscape(v))) + } + } + + authHeader = append(authHeader, fmt.Sprintf("oauth_signature=%q", signature)) + + return "OAuth " + strings.Join(authHeader, ", ") +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/results.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/results.go new file mode 100644 index 0000000..2ed75bd --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/results.go @@ -0,0 +1,317 @@ +package oauth1 + +import ( + "encoding/json" + "net/url" + "time" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/pagination" +) + +// Consumer represents a delegated authorization request between two +// identities. +type Consumer struct { + ID string `json:"id"` + Secret string `json:"secret"` + Description string `json:"description"` +} + +type consumerResult struct { + gophercloud.Result +} + +// CreateConsumerResult is the response from a Create operation. Call its +// Extract method to interpret it as a Consumer. +type CreateConsumerResult struct { + consumerResult +} + +// UpdateConsumerResult is the response from a Create operation. Call its +// Extract method to interpret it as a Consumer. +type UpdateConsumerResult struct { + consumerResult +} + +// DeleteConsumerResult is the response from a Delete operation. Call its +// ExtractErr to determine if the request succeeded or failed. +type DeleteConsumerResult struct { + gophercloud.ErrResult +} + +// ConsumersPage is a single page of Region results. +type ConsumersPage struct { + pagination.LinkedPageBase +} + +// GetConsumerResult is the response from a Get operation. Call its Extract +// method to interpret it as a Consumer. +type GetConsumerResult struct { + consumerResult +} + +// IsEmpty determines whether or not a page of Consumers contains any results. +func (c ConsumersPage) IsEmpty() (bool, error) { + if c.StatusCode == 204 { + return true, nil + } + + consumers, err := ExtractConsumers(c) + return len(consumers) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (c ConsumersPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := c.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractConsumers returns a slice of Consumers contained in a single page of +// results. +func ExtractConsumers(r pagination.Page) ([]Consumer, error) { + var s struct { + Consumers []Consumer `json:"consumers"` + } + err := (r.(ConsumersPage)).ExtractInto(&s) + return s.Consumers, err +} + +// Extract interprets any consumer result as a Consumer. +func (c consumerResult) Extract() (*Consumer, error) { + var s struct { + Consumer *Consumer `json:"consumer"` + } + err := c.ExtractInto(&s) + return s.Consumer, err +} + +// Token contains an OAuth1 token. +type Token struct { + // OAuthToken is the key value for the oauth token that the Identity API returns. + OAuthToken string `q:"oauth_token"` + // OAuthTokenSecret is the secret value associated with the OAuth Token. + OAuthTokenSecret string `q:"oauth_token_secret"` + // OAuthExpiresAt is the date and time when an OAuth token expires. + OAuthExpiresAt *time.Time `q:"-"` +} + +// TokenResult is a struct to handle +// "Content-Type: application/x-www-form-urlencoded" response. +type TokenResult struct { + gophercloud.Result + Body []byte +} + +// Extract interprets any OAuth1 token result as a Token. +func (r TokenResult) Extract() (*Token, error) { + if r.Err != nil { + return nil, r.Err + } + + values, err := url.ParseQuery(string(r.Body)) + if err != nil { + return nil, err + } + + token := &Token{ + OAuthToken: values.Get("oauth_token"), + OAuthTokenSecret: values.Get("oauth_token_secret"), + } + + if v := values.Get("oauth_expires_at"); v != "" { + if t, err := time.Parse(gophercloud.RFC3339Milli, v); err != nil { + return nil, err + } else { + token.OAuthExpiresAt = &t + } + } + + return token, nil +} + +// AuthorizedToken contains an OAuth1 authorized token info. +type AuthorizedToken struct { + // OAuthVerifier is the ID of the token verifier. + OAuthVerifier string `json:"oauth_verifier"` +} + +type AuthorizeTokenResult struct { + gophercloud.Result +} + +// Extract interprets AuthorizeTokenResult result as a AuthorizedToken. +func (r AuthorizeTokenResult) Extract() (*AuthorizedToken, error) { + var s struct { + AuthorizedToken *AuthorizedToken `json:"token"` + } + err := r.ExtractInto(&s) + return s.AuthorizedToken, err +} + +// AccessToken represents an AccessToken response as a struct. +type AccessToken struct { + ID string `json:"id"` + ConsumerID string `json:"consumer_id"` + ProjectID string `json:"project_id"` + AuthorizingUserID string `json:"authorizing_user_id"` + ExpiresAt *time.Time `json:"-"` +} + +func (r *AccessToken) UnmarshalJSON(b []byte) error { + type tmp AccessToken + var s struct { + tmp + ExpiresAt *gophercloud.JSONRFC3339Milli `json:"expires_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = AccessToken(s.tmp) + + if s.ExpiresAt != nil { + t := time.Time(*s.ExpiresAt) + r.ExpiresAt = &t + } + + return nil +} + +type GetAccessTokenResult struct { + gophercloud.Result +} + +// Extract interprets any GetAccessTokenResult result as an AccessToken. +func (r GetAccessTokenResult) Extract() (*AccessToken, error) { + var s struct { + AccessToken *AccessToken `json:"access_token"` + } + err := r.ExtractInto(&s) + return s.AccessToken, err +} + +// RevokeAccessTokenResult is the response from a Delete operation. Call its +// ExtractErr to determine if the request succeeded or failed. +type RevokeAccessTokenResult struct { + gophercloud.ErrResult +} + +// AccessTokensPage is a single page of Access Tokens results. +type AccessTokensPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a an AccessTokensPage contains any results. +func (r AccessTokensPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + accessTokens, err := ExtractAccessTokens(r) + return len(accessTokens) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r AccessTokensPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractAccessTokens returns a slice of AccessTokens contained in a single +// page of results. +func ExtractAccessTokens(r pagination.Page) ([]AccessToken, error) { + var s struct { + AccessTokens []AccessToken `json:"access_tokens"` + } + err := (r.(AccessTokensPage)).ExtractInto(&s) + return s.AccessTokens, err +} + +// AccessTokenRole represents an Access Token Role struct. +type AccessTokenRole struct { + ID string `json:"id"` + Name string `json:"name"` + DomainID string `json:"domain_id"` +} + +// AccessTokenRolesPage is a single page of Access Token roles results. +type AccessTokenRolesPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether or not a an AccessTokensPage contains any results. +func (r AccessTokenRolesPage) IsEmpty() (bool, error) { + if r.StatusCode == 204 { + return true, nil + } + + accessTokenRoles, err := ExtractAccessTokenRoles(r) + return len(accessTokenRoles) == 0, err +} + +// NextPageURL extracts the "next" link from the links section of the result. +func (r AccessTokenRolesPage) NextPageURL() (string, error) { + var s struct { + Links struct { + Next string `json:"next"` + Previous string `json:"previous"` + } `json:"links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return s.Links.Next, err +} + +// ExtractAccessTokenRoles returns a slice of AccessTokenRole contained in a +// single page of results. +func ExtractAccessTokenRoles(r pagination.Page) ([]AccessTokenRole, error) { + var s struct { + AccessTokenRoles []AccessTokenRole `json:"roles"` + } + err := (r.(AccessTokenRolesPage)).ExtractInto(&s) + return s.AccessTokenRoles, err +} + +type GetAccessTokenRoleResult struct { + gophercloud.Result +} + +// Extract interprets any GetAccessTokenRoleResult result as an AccessTokenRole. +func (r GetAccessTokenRoleResult) Extract() (*AccessTokenRole, error) { + var s struct { + AccessTokenRole *AccessTokenRole `json:"role"` + } + err := r.ExtractInto(&s) + return s.AccessTokenRole, err +} + +// OAuth1 is an OAuth1 object, returned in OAuth1 token result. +type OAuth1 struct { + AccessTokenID string `json:"access_token_id"` + ConsumerID string `json:"consumer_id"` +} + +// TokenExt represents an extension of the base token result. +type TokenExt struct { + OAuth1 OAuth1 `json:"OS-OAUTH1"` +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/urls.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/urls.go new file mode 100644 index 0000000..c8dc02e --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1/urls.go @@ -0,0 +1,43 @@ +package oauth1 + +import "github.com/gophercloud/gophercloud/v2" + +func consumersURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("OS-OAUTH1", "consumers") +} + +func consumerURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("OS-OAUTH1", "consumers", id) +} + +func requestTokenURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("OS-OAUTH1", "request_token") +} + +func authorizeTokenURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("OS-OAUTH1", "authorize", id) +} + +func createAccessTokenURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("OS-OAUTH1", "access_token") +} + +func userAccessTokensURL(c *gophercloud.ServiceClient, userID string) string { + return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens") +} + +func userAccessTokenURL(c *gophercloud.ServiceClient, userID string, id string) string { + return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id) +} + +func userAccessTokenRolesURL(c *gophercloud.ServiceClient, userID string, id string) string { + return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles") +} + +func userAccessTokenRoleURL(c *gophercloud.ServiceClient, userID string, id string, roleID string) string { + return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles", roleID) +} + +func authURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("auth", "tokens") +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/doc.go new file mode 100644 index 0000000..c711d0c --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/doc.go @@ -0,0 +1,107 @@ +/* +Package tokens provides information and interaction with the token API +resource for the OpenStack Identity service. + +For more information, see: +http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3 + +Example to Create a Token From a Username and Password + + authOptions := tokens.AuthOptions{ + UserID: "username", + Password: "password", + } + + token, err := tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token From a Username, Password, and Domain + + authOptions := tokens.AuthOptions{ + UserID: "username", + Password: "password", + DomainID: "default", + } + + token, err := tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + + authOptions = tokens.AuthOptions{ + UserID: "username", + Password: "password", + DomainName: "default", + } + + token, err = tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token From a Token + + authOptions := tokens.AuthOptions{ + TokenID: "token_id", + } + + token, err := tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Project ID Scope + + scope := tokens.Scope{ + ProjectID: "0fe36e73809d46aeae6705c39077b1b3", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Domain ID Scope + + scope := tokens.Scope{ + DomainID: "default", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } + +Example to Create a Token from a Username and Password with Project Name Scope + + scope := tokens.Scope{ + ProjectName: "project_name", + DomainID: "default", + } + + authOptions := tokens.AuthOptions{ + Scope: &scope, + UserID: "username", + Password: "password", + } + + token, err = tokens.Create(context.TODO(), identityClient, authOptions).ExtractToken() + if err != nil { + panic(err) + } +*/ +package tokens diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/requests.go new file mode 100644 index 0000000..fa8b925 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/requests.go @@ -0,0 +1,179 @@ +package tokens + +import ( + "context" + + "github.com/gophercloud/gophercloud/v2" +) + +// Scope allows a created token to be limited to a specific domain or project. +type Scope struct { + ProjectID string + ProjectName string + DomainID string + DomainName string + System bool + TrustID string +} + +// AuthOptionsBuilder provides the ability for extensions to add additional +// parameters to AuthOptions. Extensions must satisfy all required methods. +type AuthOptionsBuilder interface { + // ToTokenV3CreateMap assembles the Create request body, returning an error + // if parameters are missing or inconsistent. + ToTokenV3CreateMap(map[string]any) (map[string]any, error) + ToTokenV3HeadersMap(map[string]any) (map[string]string, error) + ToTokenV3ScopeMap() (map[string]any, error) + CanReauth() bool +} + +// AuthOptions represents options for authenticating a user. +type AuthOptions struct { + // IdentityEndpoint specifies the HTTP endpoint that is required to work with + // the Identity API of the appropriate version. While it's ultimately needed + // by all of the identity services, it will often be populated by a + // provider-level function. + IdentityEndpoint string `json:"-"` + + // Username is required if using Identity V2 API. Consult with your provider's + // control panel to discover your account's username. In Identity V3, either + // UserID or a combination of Username and DomainID or DomainName are needed. + Username string `json:"username,omitempty"` + UserID string `json:"id,omitempty"` + + Password string `json:"password,omitempty"` + + // Passcode is used in TOTP authentication method + Passcode string `json:"passcode,omitempty"` + + // At most one of DomainID and DomainName must be provided if using Username + // with Identity V3. Otherwise, either are optional. + DomainID string `json:"-"` + DomainName string `json:"name,omitempty"` + + // AllowReauth should be set to true if you grant permission for Gophercloud + // to cache your credentials in memory, and to allow Gophercloud to attempt + // to re-authenticate automatically if/when your token expires. If you set + // it to false, it will not cache these settings, but re-authentication will + // not be possible. This setting defaults to false. + AllowReauth bool `json:"-"` + + // TokenID allows users to authenticate (possibly as another user) with an + // authentication token ID. + TokenID string `json:"-"` + + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` + + Scope Scope `json:"-"` +} + +// ToTokenV3CreateMap builds a request body from AuthOptions. +func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]any) (map[string]any, error) { + gophercloudAuthOpts := gophercloud.AuthOptions{ + Username: opts.Username, + UserID: opts.UserID, + Password: opts.Password, + Passcode: opts.Passcode, + DomainID: opts.DomainID, + DomainName: opts.DomainName, + AllowReauth: opts.AllowReauth, + TokenID: opts.TokenID, + ApplicationCredentialID: opts.ApplicationCredentialID, + ApplicationCredentialName: opts.ApplicationCredentialName, + ApplicationCredentialSecret: opts.ApplicationCredentialSecret, + } + + return gophercloudAuthOpts.ToTokenV3CreateMap(scope) +} + +// ToTokenV3ScopeMap builds a scope request body from AuthOptions. +func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]any, error) { + scope := gophercloud.AuthScope(opts.Scope) + + gophercloudAuthOpts := gophercloud.AuthOptions{ + Scope: &scope, + DomainID: opts.DomainID, + DomainName: opts.DomainName, + } + + return gophercloudAuthOpts.ToTokenV3ScopeMap() +} + +func (opts *AuthOptions) CanReauth() bool { + if opts.Passcode != "" { + // cannot reauth using TOTP passcode + return false + } + + return opts.AllowReauth +} + +// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder +// interface in the v3 tokens package. +func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]any) (map[string]string, error) { + return nil, nil +} + +func subjectTokenHeaders(subjectToken string) map[string]string { + return map[string]string{ + "X-Subject-Token": subjectToken, + } +} + +// Create authenticates and either generates a new token, or changes the Scope +// of an existing token. +func Create(ctx context.Context, c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) { + scope, err := opts.ToTokenV3ScopeMap() + if err != nil { + r.Err = err + return + } + + b, err := opts.ToTokenV3CreateMap(scope) + if err != nil { + r.Err = err + return + } + + resp, err := c.Post(ctx, tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{ + OmitHeaders: []string{"X-Auth-Token"}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Get validates and retrieves information about another token. +func Get(ctx context.Context, c *gophercloud.ServiceClient, token string) (r GetResult) { + resp, err := c.Get(ctx, tokenURL(c), &r.Body, &gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(token), + OkCodes: []int{200, 203}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// Validate determines if a specified token is valid or not. +func Validate(ctx context.Context, c *gophercloud.ServiceClient, token string) (bool, error) { + resp, err := c.Head(ctx, tokenURL(c), &gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(token), + OkCodes: []int{200, 204, 404}, + }) + if err != nil { + return false, err + } + + return resp.StatusCode == 200 || resp.StatusCode == 204, nil +} + +// Revoke immediately makes specified token invalid. +func Revoke(ctx context.Context, c *gophercloud.ServiceClient, token string) (r RevokeResult) { + resp, err := c.Delete(ctx, tokenURL(c), &gophercloud.RequestOpts{ + MoreHeaders: subjectTokenHeaders(token), + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/results.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/results.go new file mode 100644 index 0000000..bd61a9a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/results.go @@ -0,0 +1,215 @@ +package tokens + +import ( + "time" + + "github.com/gophercloud/gophercloud/v2" +) + +// Endpoint represents a single API endpoint offered by a service. +// It matches either a public, internal or admin URL. +// If supported, it contains a region specifier, again if provided. +// The significance of the Region field will depend upon your provider. +type Endpoint struct { + ID string `json:"id"` + Region string `json:"region"` + RegionID string `json:"region_id"` + Interface string `json:"interface"` + URL string `json:"url"` +} + +// CatalogEntry provides a type-safe interface to an Identity API V3 service +// catalog listing. Each class of service, such as cloud DNS or block storage +// services, could have multiple CatalogEntry representing it (one by interface +// type, e.g public, admin or internal). +// +// Note: when looking for the desired service, try, whenever possible, to key +// off the type field. Otherwise, you'll tie the representation of the service +// to a specific provider. +type CatalogEntry struct { + // Service ID + ID string `json:"id"` + + // Name will contain the provider-specified name for the service. + Name string `json:"name"` + + // Type will contain a type string if OpenStack defines a type for the + // service. Otherwise, for provider-specific services, the provider may + // assign their own type strings. + Type string `json:"type"` + + // Endpoints will let the caller iterate over all the different endpoints that + // may exist for the service. + Endpoints []Endpoint `json:"endpoints"` +} + +// ServiceCatalog provides a view into the service catalog from a previous, +// successful authentication. +type ServiceCatalog struct { + Entries []CatalogEntry `json:"catalog"` +} + +// Domain provides information about the domain to which this token grants +// access. +type Domain struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// User represents a user resource that exists in the Identity Service. +type User struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` +} + +// Role provides information about roles to which User is authorized. +type Role struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// Project provides information about project to which User is authorized. +type Project struct { + Domain Domain `json:"domain"` + ID string `json:"id"` + Name string `json:"name"` +} + +type TrustUser struct { + ID string `json:"id"` +} + +// Trust provides information about trust with which User is authorized. +type Trust struct { + ID string `json:"id"` + Impersonation bool `json:"impersonation"` + TrusteeUserID TrustUser `json:"trustee_user"` + TrustorUserID TrustUser `json:"trustor_user"` +} + +// commonResult is the response from a request. A commonResult has various +// methods which can be used to extract different details about the result. +type commonResult struct { + gophercloud.Result +} + +// Extract is a shortcut for ExtractToken. +// This function is deprecated and still present for backward compatibility. +func (r commonResult) Extract() (*Token, error) { + return r.ExtractToken() +} + +// ExtractToken interprets a commonResult as a Token. +func (r commonResult) ExtractToken() (*Token, error) { + var s Token + err := r.ExtractInto(&s) + if err != nil { + return nil, err + } + + // Parse the token itself from the stored headers. + s.ID = r.Header.Get("X-Subject-Token") + + return &s, err +} + +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r CreateResult) ExtractTokenID() (string, error) { + return r.Header.Get("X-Subject-Token"), r.Err +} + +// ExtractTokenID implements the gophercloud.AuthResult interface. The returned +// string is the same as the ID field of the Token struct returned from +// ExtractToken(). +func (r GetResult) ExtractTokenID() (string, error) { + return r.Header.Get("X-Subject-Token"), r.Err +} + +// ExtractServiceCatalog returns the ServiceCatalog that was generated along +// with the user's Token. +func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) { + var s ServiceCatalog + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractUser returns the User that is the owner of the Token. +func (r commonResult) ExtractUser() (*User, error) { + var s struct { + User *User `json:"user"` + } + err := r.ExtractInto(&s) + return s.User, err +} + +// ExtractRoles returns Roles to which User is authorized. +func (r commonResult) ExtractRoles() ([]Role, error) { + var s struct { + Roles []Role `json:"roles"` + } + err := r.ExtractInto(&s) + return s.Roles, err +} + +// ExtractProject returns Project to which User is authorized. +func (r commonResult) ExtractProject() (*Project, error) { + var s struct { + Project *Project `json:"project"` + } + err := r.ExtractInto(&s) + return s.Project, err +} + +// ExtractDomain returns Domain to which User is authorized. +func (r commonResult) ExtractDomain() (*Domain, error) { + var s struct { + Domain *Domain `json:"domain"` + } + err := r.ExtractInto(&s) + return s.Domain, err +} + +// ExtractTrust returns Trust to which User is authorized. +func (r commonResult) ExtractTrust() (*Trust, error) { + var s struct { + Trust *Trust `json:"OS-TRUST:trust"` + } + err := r.ExtractInto(&s) + return s.Trust, err +} + +// CreateResult is the response from a Create request. Use ExtractToken() +// to interpret it as a Token, or ExtractServiceCatalog() to interpret it +// as a service catalog. +type CreateResult struct { + commonResult +} + +// GetResult is the response from a Get request. Use ExtractToken() +// to interpret it as a Token, or ExtractServiceCatalog() to interpret it +// as a service catalog. +type GetResult struct { + commonResult +} + +// RevokeResult is response from a Revoke request. +type RevokeResult struct { + commonResult +} + +// Token is a string that grants a user access to a controlled set of services +// in an OpenStack provider. Each Token is valid for a set length of time. +type Token struct { + // ID is the issued token. + ID string `json:"id"` + + // ExpiresAt is the timestamp at which this token will no longer be accepted. + ExpiresAt time.Time `json:"expires_at"` +} + +func (r commonResult) ExtractInto(v any) error { + return r.ExtractIntoStructPtr(v, "token") +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/urls.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/urls.go new file mode 100644 index 0000000..2218c10 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens/urls.go @@ -0,0 +1,7 @@ +package tokens + +import "github.com/gophercloud/gophercloud/v2" + +func tokenURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL("auth", "tokens") +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/base_endpoint.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/base_endpoint.go new file mode 100644 index 0000000..40080f7 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/base_endpoint.go @@ -0,0 +1,28 @@ +package utils + +import ( + "net/url" + "regexp" + "strings" +) + +// BaseEndpoint will return a URL without the /vX.Y +// portion of the URL. +func BaseEndpoint(endpoint string) (string, error) { + u, err := url.Parse(endpoint) + if err != nil { + return "", err + } + + u.RawQuery, u.Fragment = "", "" + + path := u.Path + versionRe := regexp.MustCompile("v[0-9.]+/?") + + if version := versionRe.FindString(path); version != "" { + versionIndex := strings.Index(path, version) + u.Path = path[:versionIndex] + } + + return u.String(), nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/choose_version.go b/vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/choose_version.go new file mode 100644 index 0000000..6c720e5 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/openstack/utils/choose_version.go @@ -0,0 +1,236 @@ +package utils + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/gophercloud/gophercloud/v2" +) + +// Version is a supported API version, corresponding to a vN package within the appropriate service. +type Version struct { + ID string + Suffix string + Priority int +} + +var goodStatus = map[string]bool{ + "current": true, + "supported": true, + "stable": true, +} + +// ChooseVersion queries the base endpoint of an API to choose the identity service version. +// It will pick a version among the recognized, taking into account the priority and avoiding +// experimental alternatives from the published versions. However, if the client specifies a full +// endpoint that is among the recognized versions, it will be used regardless of priority. +// It returns the highest-Priority Version, OR exact match with client endpoint, +// among the alternatives that are provided, as well as its corresponding endpoint. +func ChooseVersion(ctx context.Context, client *gophercloud.ProviderClient, recognized []*Version) (*Version, string, error) { + type linkResp struct { + Href string `json:"href"` + Rel string `json:"rel"` + } + + type valueResp struct { + ID string `json:"id"` + Status string `json:"status"` + Links []linkResp `json:"links"` + } + + type versionsResp struct { + Values []valueResp `json:"values"` + } + + type response struct { + Versions versionsResp `json:"versions"` + } + + normalize := func(endpoint string) string { + if !strings.HasSuffix(endpoint, "/") { + return endpoint + "/" + } + return endpoint + } + identityEndpoint := normalize(client.IdentityEndpoint) + + // If a full endpoint is specified, check version suffixes for a match first. + for _, v := range recognized { + if strings.HasSuffix(identityEndpoint, v.Suffix) { + return v, identityEndpoint, nil + } + } + + var resp response + _, err := client.Request(ctx, "GET", client.IdentityBase, &gophercloud.RequestOpts{ + JSONResponse: &resp, + OkCodes: []int{200, 300}, + }) + + if err != nil { + return nil, "", err + } + + var highest *Version + var endpoint string + + for _, value := range resp.Versions.Values { + href := "" + for _, link := range value.Links { + if link.Rel == "self" { + href = normalize(link.Href) + } + } + + for _, version := range recognized { + if strings.Contains(value.ID, version.ID) { + // Prefer a version that exactly matches the provided endpoint. + if href == identityEndpoint { + if href == "" { + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase) + } + return version, href, nil + } + + // Otherwise, find the highest-priority version with a whitelisted status. + if goodStatus[strings.ToLower(value.Status)] { + if highest == nil || version.Priority > highest.Priority { + highest = version + endpoint = href + } + } + } + } + } + + if highest == nil { + return nil, "", fmt.Errorf("No supported version available from endpoint %s", client.IdentityBase) + } + if endpoint == "" { + return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, client.IdentityBase) + } + + return highest, endpoint, nil +} + +type SupportedMicroversions struct { + MaxMajor int + MaxMinor int + MinMajor int + MinMinor int +} + +// GetSupportedMicroversions returns the minimum and maximum microversion that is supported by the ServiceClient Endpoint. +func GetSupportedMicroversions(ctx context.Context, client *gophercloud.ServiceClient) (SupportedMicroversions, error) { + type valueResp struct { + ID string `json:"id"` + Status string `json:"status"` + Version string `json:"version"` + MinVersion string `json:"min_version"` + } + + type response struct { + Version valueResp `json:"version"` + Versions []valueResp `json:"versions"` + } + var minVersion, maxVersion string + var supportedMicroversions SupportedMicroversions + var resp response + _, err := client.Get(ctx, client.Endpoint, &resp, &gophercloud.RequestOpts{ + OkCodes: []int{200, 300}, + }) + + if err != nil { + return supportedMicroversions, err + } + + if len(resp.Versions) > 0 { + // We are dealing with an unversioned endpoint + // We only handle the case when there is exactly one, and assume it is the correct one + if len(resp.Versions) > 1 { + return supportedMicroversions, fmt.Errorf("unversioned endpoint with multiple alternatives not supported") + } + minVersion = resp.Versions[0].MinVersion + maxVersion = resp.Versions[0].Version + } else { + minVersion = resp.Version.MinVersion + maxVersion = resp.Version.Version + } + + // Return early if the endpoint does not support microversions + if minVersion == "" && maxVersion == "" { + return supportedMicroversions, fmt.Errorf("microversions not supported by ServiceClient Endpoint") + } + + supportedMicroversions.MinMajor, supportedMicroversions.MinMinor, err = ParseMicroversion(minVersion) + if err != nil { + return supportedMicroversions, err + } + + supportedMicroversions.MaxMajor, supportedMicroversions.MaxMinor, err = ParseMicroversion(maxVersion) + if err != nil { + return supportedMicroversions, err + } + + return supportedMicroversions, nil +} + +// RequireMicroversion checks that the required microversion is supported and +// returns a ServiceClient with the microversion set. +func RequireMicroversion(ctx context.Context, client gophercloud.ServiceClient, required string) (gophercloud.ServiceClient, error) { + supportedMicroversions, err := GetSupportedMicroversions(ctx, &client) + if err != nil { + return client, fmt.Errorf("unable to determine supported microversions: %w", err) + } + supported, err := supportedMicroversions.IsSupported(required) + if err != nil { + return client, err + } + if !supported { + return client, fmt.Errorf("microversion %s not supported. Supported versions: %v", required, supportedMicroversions) + } + client.Microversion = required + return client, nil +} + +// IsSupported checks if a microversion falls in the supported interval. +// It returns true if the version is within the interval and false otherwise. +func (supported SupportedMicroversions) IsSupported(version string) (bool, error) { + // Parse the version X.Y into X and Y integers that are easier to compare. + vMajor, vMinor, err := ParseMicroversion(version) + if err != nil { + return false, err + } + + // Check that the major version number is supported. + if (vMajor < supported.MinMajor) || (vMajor > supported.MaxMajor) { + return false, nil + } + + // Check that the minor version number is supported + if (vMinor <= supported.MaxMinor) && (vMinor >= supported.MinMinor) { + return true, nil + } + + return false, nil +} + +// ParseMicroversion parses the version major.minor into separate integers major and minor. +// For example, "2.53" becomes 2 and 53. +func ParseMicroversion(version string) (major int, minor int, err error) { + parts := strings.Split(version, ".") + if len(parts) != 2 { + return 0, 0, fmt.Errorf("invalid microversion format: %q", version) + } + major, err = strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, err + } + minor, err = strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, err + } + return major, minor, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/pagination/http.go b/vendor/github.com/gophercloud/gophercloud/v2/pagination/http.go new file mode 100644 index 0000000..cf188b8 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/pagination/http.go @@ -0,0 +1,63 @@ +package pagination + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + "strings" + + "github.com/gophercloud/gophercloud/v2" +) + +// PageResult stores the HTTP response that returned the current page of results. +type PageResult struct { + gophercloud.Result + url.URL +} + +// PageResultFrom parses an HTTP response as JSON and returns a PageResult containing the +// results, interpreting it as JSON if the content type indicates. +func PageResultFrom(resp *http.Response) (PageResult, error) { + var parsedBody any + + defer resp.Body.Close() + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return PageResult{}, err + } + + if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { + err = json.Unmarshal(rawBody, &parsedBody) + if err != nil { + return PageResult{}, err + } + } else { + parsedBody = rawBody + } + + return PageResultFromParsed(resp, parsedBody), err +} + +// PageResultFromParsed constructs a PageResult from an HTTP response that has already had its +// body parsed as JSON (and closed). +func PageResultFromParsed(resp *http.Response, body any) PageResult { + return PageResult{ + Result: gophercloud.Result{ + Body: body, + StatusCode: resp.StatusCode, + Header: resp.Header, + }, + URL: *resp.Request.URL, + } +} + +// Request performs an HTTP request and extracts the http.Response from the result. +func Request(ctx context.Context, client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) { + return client.Get(ctx, url, nil, &gophercloud.RequestOpts{ + MoreHeaders: headers, + OkCodes: []int{200, 204, 300}, + KeepResponseBody: true, + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/pagination/linked.go b/vendor/github.com/gophercloud/gophercloud/v2/pagination/linked.go new file mode 100644 index 0000000..7e4de4f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/pagination/linked.go @@ -0,0 +1,92 @@ +package pagination + +import ( + "fmt" + "reflect" + + "github.com/gophercloud/gophercloud/v2" +) + +// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result. +type LinkedPageBase struct { + PageResult + + // LinkPath lists the keys that should be traversed within a response to arrive at the "next" pointer. + // If any link along the path is missing, an empty URL will be returned. + // If any link results in an unexpected value type, an error will be returned. + // When left as "nil", []string{"links", "next"} will be used as a default. + LinkPath []string +} + +// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present. +// It assumes that the links are available in a "links" element of the top-level response object. +// If this is not the case, override NextPageURL on your result type. +func (current LinkedPageBase) NextPageURL() (string, error) { + var path []string + var key string + + if current.LinkPath == nil { + path = []string{"links", "next"} + } else { + path = current.LinkPath + } + + submap, ok := current.Body.(map[string]any) + if !ok { + err := gophercloud.ErrUnexpectedType{} + err.Expected = "map[string]any" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return "", err + } + + for { + key, path = path[0], path[1:] + + value, ok := submap[key] + if !ok { + return "", nil + } + + if len(path) > 0 { + submap, ok = value.(map[string]any) + if !ok { + err := gophercloud.ErrUnexpectedType{} + err.Expected = "map[string]any" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) + return "", err + } + } else { + if value == nil { + // Actual null element. + return "", nil + } + + url, ok := value.(string) + if !ok { + err := gophercloud.ErrUnexpectedType{} + err.Expected = "string" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) + return "", err + } + + return url, nil + } + } +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (current LinkedPageBase) IsEmpty() (bool, error) { + if b, ok := current.Body.([]any); ok { + return len(b) == 0, nil + } + err := gophercloud.ErrUnexpectedType{} + err.Expected = "[]any" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return true, err +} + +// GetBody returns the linked page's body. This method is needed to satisfy the +// Page interface. +func (current LinkedPageBase) GetBody() any { + return current.Body +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/pagination/marker.go b/vendor/github.com/gophercloud/gophercloud/v2/pagination/marker.go new file mode 100644 index 0000000..1d101fe --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/pagination/marker.go @@ -0,0 +1,58 @@ +package pagination + +import ( + "fmt" + "reflect" + + "github.com/gophercloud/gophercloud/v2" +) + +// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager. +// For convenience, embed the MarkedPageBase struct. +type MarkerPage interface { + Page + + // LastMarker returns the last "marker" value on this page. + LastMarker() (string, error) +} + +// MarkerPageBase is a page in a collection that's paginated by "limit" and "marker" query parameters. +type MarkerPageBase struct { + PageResult + + // Owner is a reference to the embedding struct. + Owner MarkerPage +} + +// NextPageURL generates the URL for the page of results after this one. +func (current MarkerPageBase) NextPageURL() (string, error) { + currentURL := current.URL + + mark, err := current.Owner.LastMarker() + if err != nil { + return "", err + } + + q := currentURL.Query() + q.Set("marker", mark) + currentURL.RawQuery = q.Encode() + + return currentURL.String(), nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (current MarkerPageBase) IsEmpty() (bool, error) { + if b, ok := current.Body.([]any); ok { + return len(b) == 0, nil + } + err := gophercloud.ErrUnexpectedType{} + err.Expected = "[]any" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return true, err +} + +// GetBody returns the linked page's body. This method is needed to satisfy the +// Page interface. +func (current MarkerPageBase) GetBody() any { + return current.Body +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/pagination/pager.go b/vendor/github.com/gophercloud/gophercloud/v2/pagination/pager.go new file mode 100644 index 0000000..3581012 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/pagination/pager.go @@ -0,0 +1,256 @@ +package pagination + +import ( + "context" + "errors" + "fmt" + "net/http" + "reflect" + "strings" + + "github.com/gophercloud/gophercloud/v2" +) + +var ( + // ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist. + ErrPageNotAvailable = errors.New("The requested page does not exist.") +) + +// Page must be satisfied by the result type of any resource collection. +// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated. +// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs, +// instead. +// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type +// will need to implement. +type Page interface { + // NextPageURL generates the URL for the page of data that follows this collection. + // Return "" if no such page exists. + NextPageURL() (string, error) + + // IsEmpty returns true if this Page has no items in it. + IsEmpty() (bool, error) + + // GetBody returns the Page Body. This is used in the `AllPages` method. + GetBody() any +} + +// Pager knows how to advance through a specific resource collection, one page at a time. +type Pager struct { + client *gophercloud.ServiceClient + + initialURL string + + createPage func(r PageResult) Page + + firstPage Page + + Err error + + // Headers supplies additional HTTP headers to populate on each paged request. + Headers map[string]string +} + +// NewPager constructs a manually-configured pager. +// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page. +func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager { + return Pager{ + client: client, + initialURL: initialURL, + createPage: createPage, + } +} + +// WithPageCreator returns a new Pager that substitutes a different page creation function. This is +// useful for overriding List functions in delegation. +func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager { + return Pager{ + client: p.client, + initialURL: p.initialURL, + createPage: createPage, + } +} + +func (p Pager) fetchNextPage(ctx context.Context, url string) (Page, error) { + resp, err := Request(ctx, p.client, p.Headers, url) + if err != nil { + return nil, err + } + + remembered, err := PageResultFrom(resp) + if err != nil { + return nil, err + } + + return p.createPage(remembered), nil +} + +// EachPage iterates over each page returned by a Pager, yielding one at a time +// to a handler function. Return "false" from the handler to prematurely stop +// iterating. +func (p Pager) EachPage(ctx context.Context, handler func(context.Context, Page) (bool, error)) error { + if p.Err != nil { + return p.Err + } + currentURL := p.initialURL + for { + var currentPage Page + + // if first page has already been fetched, no need to fetch it again + if p.firstPage != nil { + currentPage = p.firstPage + p.firstPage = nil + } else { + var err error + currentPage, err = p.fetchNextPage(ctx, currentURL) + if err != nil { + return err + } + } + + empty, err := currentPage.IsEmpty() + if err != nil { + return err + } + if empty { + return nil + } + + ok, err := handler(ctx, currentPage) + if err != nil { + return err + } + if !ok { + return nil + } + + currentURL, err = currentPage.NextPageURL() + if err != nil { + return err + } + if currentURL == "" { + return nil + } + } +} + +// AllPages returns all the pages from a `List` operation in a single page, +// allowing the user to retrieve all the pages at once. +func (p Pager) AllPages(ctx context.Context) (Page, error) { + if p.Err != nil { + return nil, p.Err + } + // pagesSlice holds all the pages until they get converted into as Page Body. + var pagesSlice []any + // body will contain the final concatenated Page body. + var body reflect.Value + + // Grab a first page to ascertain the page body type. + firstPage, err := p.fetchNextPage(ctx, p.initialURL) + if err != nil { + return nil, err + } + // Store the page type so we can use reflection to create a new mega-page of + // that type. + pageType := reflect.TypeOf(firstPage) + + // if it's a single page, just return the firstPage (first page) + if _, found := pageType.FieldByName("SinglePageBase"); found { + return firstPage, nil + } + + // store the first page to avoid getting it twice + p.firstPage = firstPage + + // Switch on the page body type. Recognized types are `map[string]any`, + // `[]byte`, and `[]any`. + switch pb := firstPage.GetBody().(type) { + case map[string]any: + // key is the map key for the page body if the body type is `map[string]any`. + var key string + // Iterate over the pages to concatenate the bodies. + err = p.EachPage(ctx, func(_ context.Context, page Page) (bool, error) { + b := page.GetBody().(map[string]any) + for k, v := range b { + // If it's a linked page, we don't want the `links`, we want the other one. + if !strings.HasSuffix(k, "links") { + // check the field's type. we only want []any (which is really []map[string]any) + switch vt := v.(type) { + case []any: + key = k + pagesSlice = append(pagesSlice, vt...) + } + } + } + return true, nil + }) + if err != nil { + return nil, err + } + // Set body to value of type `map[string]any` + body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice))) + body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice)) + case []byte: + // Iterate over the pages to concatenate the bodies. + err = p.EachPage(ctx, func(_ context.Context, page Page) (bool, error) { + b := page.GetBody().([]byte) + pagesSlice = append(pagesSlice, b) + // seperate pages with a comma + pagesSlice = append(pagesSlice, []byte{10}) + return true, nil + }) + if err != nil { + return nil, err + } + if len(pagesSlice) > 0 { + // Remove the trailing comma. + pagesSlice = pagesSlice[:len(pagesSlice)-1] + } + var b []byte + // Combine the slice of slices in to a single slice. + for _, slice := range pagesSlice { + b = append(b, slice.([]byte)...) + } + // Set body to value of type `bytes`. + body = reflect.New(reflect.TypeOf(b)).Elem() + body.SetBytes(b) + case []any: + // Iterate over the pages to concatenate the bodies. + err = p.EachPage(ctx, func(_ context.Context, page Page) (bool, error) { + b := page.GetBody().([]any) + pagesSlice = append(pagesSlice, b...) + return true, nil + }) + if err != nil { + return nil, err + } + // Set body to value of type `[]any` + body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice)) + for i, s := range pagesSlice { + body.Index(i).Set(reflect.ValueOf(s)) + } + default: + err := gophercloud.ErrUnexpectedType{} + err.Expected = "map[string]any/[]byte/[]any" + err.Actual = fmt.Sprintf("%T", pb) + return nil, err + } + + // Each `Extract*` function is expecting a specific type of page coming back, + // otherwise the type assertion in those functions will fail. pageType is needed + // to create a type in this method that has the same type that the `Extract*` + // function is expecting and set the Body of that object to the concatenated + // pages. + page := reflect.New(pageType) + // Set the page body to be the concatenated pages. + page.Elem().FieldByName("Body").Set(body) + // Set any additional headers that were pass along. The `objectstorage` pacakge, + // for example, passes a Content-Type header. + h := make(http.Header) + for k, v := range p.Headers { + h.Add(k, v) + } + page.Elem().FieldByName("Header").Set(reflect.ValueOf(h)) + // Type assert the page to a Page interface so that the type assertion in the + // `Extract*` methods will work. + return page.Elem().Interface().(Page), err +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/pagination/pkg.go b/vendor/github.com/gophercloud/gophercloud/v2/pagination/pkg.go new file mode 100644 index 0000000..912daea --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/pagination/pkg.go @@ -0,0 +1,4 @@ +/* +Package pagination contains utilities and convenience structs that implement common pagination idioms within OpenStack APIs. +*/ +package pagination diff --git a/vendor/github.com/gophercloud/gophercloud/v2/pagination/single.go b/vendor/github.com/gophercloud/gophercloud/v2/pagination/single.go new file mode 100644 index 0000000..4166211 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/pagination/single.go @@ -0,0 +1,33 @@ +package pagination + +import ( + "fmt" + "reflect" + + "github.com/gophercloud/gophercloud/v2" +) + +// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once. +type SinglePageBase PageResult + +// NextPageURL always returns "" to indicate that there are no more pages to return. +func (current SinglePageBase) NextPageURL() (string, error) { + return "", nil +} + +// IsEmpty satisifies the IsEmpty method of the Page interface +func (current SinglePageBase) IsEmpty() (bool, error) { + if b, ok := current.Body.([]any); ok { + return len(b) == 0, nil + } + err := gophercloud.ErrUnexpectedType{} + err.Expected = "[]any" + err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) + return true, err +} + +// GetBody returns the single page's body. This method is needed to satisfy the +// Page interface. +func (current SinglePageBase) GetBody() any { + return current.Body +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/params.go b/vendor/github.com/gophercloud/gophercloud/v2/params.go new file mode 100644 index 0000000..09b322a --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/params.go @@ -0,0 +1,506 @@ +package gophercloud + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +/* +BuildRequestBody builds a map[string]interface from the given `struct`. If +parent is not an empty string, the final map[string]interface returned will +encapsulate the built one. For example: + + disk := 1 + createOpts := flavors.CreateOpts{ + ID: "1", + Name: "m1.tiny", + Disk: &disk, + RAM: 512, + VCPUs: 1, + RxTxFactor: 1.0, + } + + body, err := gophercloud.BuildRequestBody(createOpts, "flavor") + +The above example can be run as-is, however it is recommended to look at how +BuildRequestBody is used within Gophercloud to more fully understand how it +fits within the request process as a whole rather than use it directly as shown +above. +*/ +func BuildRequestBody(opts any, parent string) (map[string]any, error) { + optsValue := reflect.ValueOf(opts) + if optsValue.Kind() == reflect.Ptr { + optsValue = optsValue.Elem() + } + + optsType := reflect.TypeOf(opts) + if optsType.Kind() == reflect.Ptr { + optsType = optsType.Elem() + } + + optsMap := make(map[string]any) + if optsValue.Kind() == reflect.Struct { + //fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind()) + for i := 0; i < optsValue.NumField(); i++ { + v := optsValue.Field(i) + f := optsType.Field(i) + + if f.Name != strings.Title(f.Name) { + //fmt.Printf("Skipping field: %s...\n", f.Name) + continue + } + + //fmt.Printf("Starting on field: %s...\n", f.Name) + + zero := isZero(v) + //fmt.Printf("v is zero?: %v\n", zero) + + // if the field has a required tag that's set to "true" + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + //fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero) + // if the field's value is zero, return a missing-argument error + if zero { + // if the field has a 'required' tag, it can't have a zero-value + err := ErrMissingInput{} + err.Argument = f.Name + return nil, err + } + } + + if xorTag := f.Tag.Get("xor"); xorTag != "" { + //fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag) + xorField := optsValue.FieldByName(xorTag) + var xorFieldIsZero bool + if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) { + xorFieldIsZero = true + } else { + if xorField.Kind() == reflect.Ptr { + xorField = xorField.Elem() + } + xorFieldIsZero = isZero(xorField) + } + if !(zero != xorFieldIsZero) { + err := ErrMissingInput{} + err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag) + err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag) + return nil, err + } + } + + if orTag := f.Tag.Get("or"); orTag != "" { + //fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag) + //fmt.Printf("field is zero?: %v\n", zero) + if zero { + orField := optsValue.FieldByName(orTag) + var orFieldIsZero bool + if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) { + orFieldIsZero = true + } else { + if orField.Kind() == reflect.Ptr { + orField = orField.Elem() + } + orFieldIsZero = isZero(orField) + } + if orFieldIsZero { + err := ErrMissingInput{} + err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag) + err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag) + return nil, err + } + } + } + + jsonTag := f.Tag.Get("json") + if jsonTag == "-" { + continue + } + + if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) { + sliceValue := v + if sliceValue.Kind() == reflect.Ptr { + sliceValue = sliceValue.Elem() + } + + for i := 0; i < sliceValue.Len(); i++ { + element := sliceValue.Index(i) + if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) { + _, err := BuildRequestBody(element.Interface(), "") + if err != nil { + return nil, err + } + } + } + } + if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) { + if zero { + //fmt.Printf("value before change: %+v\n", optsValue.Field(i)) + if jsonTag != "" { + jsonTagPieces := strings.Split(jsonTag, ",") + if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" { + if v.CanSet() { + if !v.IsNil() { + if v.Kind() == reflect.Ptr { + v.Set(reflect.Zero(v.Type())) + } + } + //fmt.Printf("value after change: %+v\n", optsValue.Field(i)) + } + } + } + continue + } + + //fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name) + _, err := BuildRequestBody(v.Interface(), f.Name) + if err != nil { + return nil, err + } + } + } + + //fmt.Printf("opts: %+v \n", opts) + + b, err := json.Marshal(opts) + if err != nil { + return nil, err + } + + //fmt.Printf("string(b): %s\n", string(b)) + + err = json.Unmarshal(b, &optsMap) + if err != nil { + return nil, err + } + + //fmt.Printf("optsMap: %+v\n", optsMap) + + if parent != "" { + optsMap = map[string]any{parent: optsMap} + } + //fmt.Printf("optsMap after parent added: %+v\n", optsMap) + return optsMap, nil + } + // Return an error if the underlying type of 'opts' isn't a struct. + return nil, fmt.Errorf("Options type is not a struct.") +} + +// EnabledState is a convenience type, mostly used in Create and Update +// operations. Because the zero value of a bool is FALSE, we need to use a +// pointer instead to indicate zero-ness. +type EnabledState *bool + +// Convenience vars for EnabledState values. +var ( + iTrue = true + iFalse = false + + Enabled EnabledState = &iTrue + Disabled EnabledState = &iFalse +) + +// IPVersion is a type for the possible IP address versions. Valid instances +// are IPv4 and IPv6 +type IPVersion int + +const ( + // IPv4 is used for IP version 4 addresses + IPv4 IPVersion = 4 + // IPv6 is used for IP version 6 addresses + IPv6 IPVersion = 6 +) + +// IntToPointer is a function for converting integers into integer pointers. +// This is useful when passing in options to operations. +func IntToPointer(i int) *int { + return &i +} + +/* +MaybeString is an internal function to be used by request methods in individual +resource packages. + +It takes a string that might be a zero value and returns either a pointer to its +address or nil. This is useful for allowing users to conveniently omit values +from an options struct by leaving them zeroed, but still pass nil to the JSON +serializer so they'll be omitted from the request body. +*/ +func MaybeString(original string) *string { + if original != "" { + return &original + } + return nil +} + +/* +MaybeInt is an internal function to be used by request methods in individual +resource packages. + +Like MaybeString, it accepts an int that may or may not be a zero value, and +returns either a pointer to its address or nil. It's intended to hint that the +JSON serializer should omit its field. +*/ +func MaybeInt(original int) *int { + if original != 0 { + return &original + } + return nil +} + +/* +func isUnderlyingStructZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Ptr: + return isUnderlyingStructZero(v.Elem()) + default: + return isZero(v) + } +} +*/ + +var t time.Time + +func isZero(v reflect.Value) bool { + //fmt.Printf("\n\nchecking isZero for value: %+v\n", v) + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + return true + } + return false + case reflect.Func, reflect.Map, reflect.Slice: + return v.IsNil() + case reflect.Array: + z := true + for i := 0; i < v.Len(); i++ { + z = z && isZero(v.Index(i)) + } + return z + case reflect.Struct: + if v.Type() == reflect.TypeOf(t) { + return v.Interface().(time.Time).IsZero() + } + z := true + for i := 0; i < v.NumField(); i++ { + z = z && isZero(v.Field(i)) + } + return z + } + // Compare other types directly: + z := reflect.Zero(v.Type()) + //fmt.Printf("zero type for value: %+v\n\n\n", z) + return v.Interface() == z.Interface() +} + +/* +BuildQueryString is an internal function to be used by request methods in +individual resource packages. + +It accepts a tagged structure and expands it into a URL struct. Field names are +converted into query parameters based on a "q" tag. For example: + + type struct Something { + Bar string `q:"x_bar"` + Baz int `q:"lorem_ipsum"` + } + + instance := Something{ + Bar: "AAA", + Baz: "BBB", + } + +will be converted into "?x_bar=AAA&lorem_ipsum=BBB". + +The struct's fields may be strings, integers, slices, or boolean values. Fields +left at their type's zero value will be omitted from the query. + +Slice are handled in one of two ways: + + type struct Something { + Bar []string `q:"bar"` // E.g. ?bar=1&bar=2 + Baz []int `q:"baz" format="comma-separated"` // E.g. ?baz=1,2 + } +*/ +func BuildQueryString(opts any) (*url.URL, error) { + optsValue := reflect.ValueOf(opts) + if optsValue.Kind() == reflect.Ptr { + optsValue = optsValue.Elem() + } + + optsType := reflect.TypeOf(opts) + if optsType.Kind() == reflect.Ptr { + optsType = optsType.Elem() + } + + params := url.Values{} + + if optsValue.Kind() == reflect.Struct { + for i := 0; i < optsValue.NumField(); i++ { + v := optsValue.Field(i) + f := optsType.Field(i) + qTag := f.Tag.Get("q") + + // if the field has a 'q' tag, it goes in the query string + if qTag != "" { + tags := strings.Split(qTag, ",") + + // if the field is set, add it to the slice of query pieces + if !isZero(v) { + loop: + switch v.Kind() { + case reflect.Ptr: + v = v.Elem() + goto loop + case reflect.String: + params.Add(tags[0], v.String()) + case reflect.Int: + params.Add(tags[0], strconv.FormatInt(v.Int(), 10)) + case reflect.Bool: + params.Add(tags[0], strconv.FormatBool(v.Bool())) + case reflect.Slice: + var values []string + switch v.Type().Elem() { + case reflect.TypeOf(0): + for i := 0; i < v.Len(); i++ { + values = append(values, strconv.FormatInt(v.Index(i).Int(), 10)) + } + default: + for i := 0; i < v.Len(); i++ { + values = append(values, v.Index(i).String()) + } + } + if sliceFormat := f.Tag.Get("format"); sliceFormat == "comma-separated" { + params.Add(tags[0], strings.Join(values, ",")) + } else { + params[tags[0]] = append(params[tags[0]], values...) + } + case reflect.Map: + if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String { + var s []string + for _, k := range v.MapKeys() { + value := v.MapIndex(k).String() + s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value)) + } + params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", "))) + } + } + } else { + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) + } + } + } + } + + return &url.URL{RawQuery: params.Encode()}, nil + } + // Return an error if the underlying type of 'opts' isn't a struct. + return nil, fmt.Errorf("Options type is not a struct.") +} + +/* +BuildHeaders is an internal function to be used by request methods in +individual resource packages. + +It accepts an arbitrary tagged structure and produces a string map that's +suitable for use as the HTTP headers of an outgoing request. Field names are +mapped to header names based in "h" tags. + + type struct Something { + Bar string `h:"x_bar"` + Baz int `h:"lorem_ipsum"` + } + + instance := Something{ + Bar: "AAA", + Baz: "BBB", + } + +will be converted into: + + map[string]string{ + "x_bar": "AAA", + "lorem_ipsum": "BBB", + } + +Untagged fields and fields left at their zero values are skipped. Integers, +booleans and string values are supported. +*/ +func BuildHeaders(opts any) (map[string]string, error) { + optsValue := reflect.ValueOf(opts) + if optsValue.Kind() == reflect.Ptr { + optsValue = optsValue.Elem() + } + + optsType := reflect.TypeOf(opts) + if optsType.Kind() == reflect.Ptr { + optsType = optsType.Elem() + } + + optsMap := make(map[string]string) + if optsValue.Kind() == reflect.Struct { + for i := 0; i < optsValue.NumField(); i++ { + v := optsValue.Field(i) + f := optsType.Field(i) + hTag := f.Tag.Get("h") + + // if the field has a 'h' tag, it goes in the header + if hTag != "" { + tags := strings.Split(hTag, ",") + + // if the field is set, add it to the slice of query pieces + if !isZero(v) { + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + switch v.Kind() { + case reflect.String: + optsMap[tags[0]] = v.String() + case reflect.Int: + optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) + case reflect.Int64: + optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) + case reflect.Bool: + optsMap[tags[0]] = strconv.FormatBool(v.Bool()) + } + } else { + // if the field has a 'required' tag, it can't have a zero-value + if requiredTag := f.Tag.Get("required"); requiredTag == "true" { + return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) + } + } + } + + } + return optsMap, nil + } + // Return an error if the underlying type of 'opts' isn't a struct. + return optsMap, fmt.Errorf("Options type is not a struct.") +} + +// IDSliceToQueryString takes a slice of elements and converts them into a query +// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the +// result would be `?name=20&name=40&name=60' +func IDSliceToQueryString(name string, ids []int) string { + str := "" + for k, v := range ids { + if k == 0 { + str += "?" + } else { + str += "&" + } + str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v)) + } + return str +} + +// IntWithinRange returns TRUE if an integer falls within a defined range, and +// FALSE if not. +func IntWithinRange(val, min, max int) bool { + return val > min && val < max +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/provider_client.go b/vendor/github.com/gophercloud/gophercloud/v2/provider_client.go new file mode 100644 index 0000000..f8c4928 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/provider_client.go @@ -0,0 +1,583 @@ +package gophercloud + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "strings" + "sync" +) + +// DefaultUserAgent is the default User-Agent string set in the request header. +const ( + DefaultUserAgent = "gophercloud/v2.0.0" + DefaultMaxBackoffRetries = 60 +) + +// UserAgent represents a User-Agent header. +type UserAgent struct { + // prepend is the slice of User-Agent strings to prepend to DefaultUserAgent. + // All the strings to prepend are accumulated and prepended in the Join method. + prepend []string +} + +type RetryBackoffFunc func(context.Context, *ErrUnexpectedResponseCode, error, uint) error + +// RetryFunc is a catch-all function for retrying failed API requests. +// If it returns nil, the request will be retried. If it returns an error, +// the request method will exit with that error. failCount is the number of +// times the request has failed (starting at 1). +type RetryFunc func(context context.Context, method, url string, options *RequestOpts, err error, failCount uint) error + +// Prepend prepends a user-defined string to the default User-Agent string. Users +// may pass in one or more strings to prepend. +func (ua *UserAgent) Prepend(s ...string) { + ua.prepend = append(s, ua.prepend...) +} + +// Join concatenates all the user-defined User-Agend strings with the default +// Gophercloud User-Agent string. +func (ua *UserAgent) Join() string { + uaSlice := append(ua.prepend, DefaultUserAgent) + return strings.Join(uaSlice, " ") +} + +// ProviderClient stores details that are required to interact with any +// services within a specific provider's API. +// +// Generally, you acquire a ProviderClient by calling the NewClient method in +// the appropriate provider's child package, providing whatever authentication +// credentials are required. +type ProviderClient struct { + // IdentityBase is the base URL used for a particular provider's identity + // service - it will be used when issuing authenticatation requests. It + // should point to the root resource of the identity service, not a specific + // identity version. + IdentityBase string + + // IdentityEndpoint is the identity endpoint. This may be a specific version + // of the identity service. If this is the case, this endpoint is used rather + // than querying versions first. + IdentityEndpoint string + + // TokenID is the ID of the most recently issued valid token. + // NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application. + // To safely read or write this value, call `Token` or `SetToken`, respectively + TokenID string + + // EndpointLocator describes how this provider discovers the endpoints for + // its constituent services. + EndpointLocator EndpointLocator + + // HTTPClient allows users to interject arbitrary http, https, or other transit behaviors. + HTTPClient http.Client + + // UserAgent represents the User-Agent header in the HTTP request. + UserAgent UserAgent + + // ReauthFunc is the function used to re-authenticate the user if the request + // fails with a 401 HTTP response code. This a needed because there may be multiple + // authentication functions for different Identity service versions. + ReauthFunc func(context.Context) error + + // Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client + // with the token and reauth func zeroed. Such client can be used to perform reauthorization. + Throwaway bool + + // Retry backoff func is called when rate limited. + RetryBackoffFunc RetryBackoffFunc + + // MaxBackoffRetries set the maximum number of backoffs. When not set, defaults to DefaultMaxBackoffRetries + MaxBackoffRetries uint + + // A general failed request handler method - this is always called in the end if a request failed. Leave as nil + // to abort when an error is encountered. + RetryFunc RetryFunc + + // mut is a mutex for the client. It protects read and write access to client attributes such as getting + // and setting the TokenID. + mut *sync.RWMutex + + // reauthmut is a mutex for reauthentication it attempts to ensure that only one reauthentication + // attempt happens at one time. + reauthmut *reauthlock + + authResult AuthResult +} + +// reauthlock represents a set of attributes used to help in the reauthentication process. +type reauthlock struct { + sync.RWMutex + ongoing *reauthFuture +} + +// reauthFuture represents future result of the reauthentication process. +// while done channel is not closed, reauthentication is in progress. +// when done channel is closed, err contains the result of reauthentication. +type reauthFuture struct { + done chan struct{} + err error +} + +func newReauthFuture() *reauthFuture { + return &reauthFuture{ + make(chan struct{}), + nil, + } +} + +func (f *reauthFuture) Set(err error) { + f.err = err + close(f.done) +} + +func (f *reauthFuture) Get() error { + <-f.done + return f.err +} + +// AuthenticatedHeaders returns a map of HTTP headers that are common for all +// authenticated service requests. Blocks if Reauthenticate is in progress. +func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) { + if client.IsThrowaway() { + return + } + if client.reauthmut != nil { + // If a Reauthenticate is in progress, wait for it to complete. + client.reauthmut.Lock() + ongoing := client.reauthmut.ongoing + client.reauthmut.Unlock() + if ongoing != nil { + _ = ongoing.Get() + } + } + t := client.Token() + if t == "" { + return + } + return map[string]string{"X-Auth-Token": t} +} + +// UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token. +// If the application's ProviderClient is not used concurrently, this doesn't need to be called. +func (client *ProviderClient) UseTokenLock() { + client.mut = new(sync.RWMutex) + client.reauthmut = new(reauthlock) +} + +// GetAuthResult returns the result from the request that was used to obtain a +// provider client's Keystone token. +// +// The result is nil when authentication has not yet taken place, when the token +// was set manually with SetToken(), or when a ReauthFunc was used that does not +// record the AuthResult. +func (client *ProviderClient) GetAuthResult() AuthResult { + if client.mut != nil { + client.mut.RLock() + defer client.mut.RUnlock() + } + return client.authResult +} + +// Token safely reads the value of the auth token from the ProviderClient. Applications should +// call this method to access the token instead of the TokenID field +func (client *ProviderClient) Token() string { + if client.mut != nil { + client.mut.RLock() + defer client.mut.RUnlock() + } + return client.TokenID +} + +// SetToken safely sets the value of the auth token in the ProviderClient. Applications may +// use this method in a custom ReauthFunc. +// +// WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead. +func (client *ProviderClient) SetToken(t string) { + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + client.TokenID = t + client.authResult = nil +} + +// SetTokenAndAuthResult safely sets the value of the auth token in the +// ProviderClient and also records the AuthResult that was returned from the +// token creation request. Applications may call this in a custom ReauthFunc. +func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error { + tokenID := "" + var err error + if r != nil { + tokenID, err = r.ExtractTokenID() + if err != nil { + return err + } + } + + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + client.TokenID = tokenID + client.authResult = r + return nil +} + +// CopyTokenFrom safely copies the token from another ProviderClient into the +// this one. +func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) { + if client.mut != nil { + client.mut.Lock() + defer client.mut.Unlock() + } + if other.mut != nil && other.mut != client.mut { + other.mut.RLock() + defer other.mut.RUnlock() + } + client.TokenID = other.TokenID + client.authResult = other.authResult +} + +// IsThrowaway safely reads the value of the client Throwaway field. +func (client *ProviderClient) IsThrowaway() bool { + if client.reauthmut != nil { + client.reauthmut.RLock() + defer client.reauthmut.RUnlock() + } + return client.Throwaway +} + +// SetThrowaway safely sets the value of the client Throwaway field. +func (client *ProviderClient) SetThrowaway(v bool) { + if client.reauthmut != nil { + client.reauthmut.Lock() + defer client.reauthmut.Unlock() + } + client.Throwaway = v +} + +// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is +// called because of a 401 response, the caller may pass the previous token. In +// this case, the reauthentication can be skipped if another thread has already +// reauthenticated in the meantime. If no previous token is known, an empty +// string should be passed instead to force unconditional reauthentication. +func (client *ProviderClient) Reauthenticate(ctx context.Context, previousToken string) error { + if client.ReauthFunc == nil { + return nil + } + + if client.reauthmut == nil { + return client.ReauthFunc(ctx) + } + + future := newReauthFuture() + + // Check if a Reauthenticate is in progress, or start one if not. + client.reauthmut.Lock() + ongoing := client.reauthmut.ongoing + if ongoing == nil { + client.reauthmut.ongoing = future + } + client.reauthmut.Unlock() + + // If Reauthenticate is running elsewhere, wait for its result. + if ongoing != nil { + return ongoing.Get() + } + + // Perform the actual reauthentication. + var err error + if previousToken == "" || client.TokenID == previousToken { + err = client.ReauthFunc(ctx) + } else { + err = nil + } + + // Mark Reauthenticate as finished. + client.reauthmut.Lock() + client.reauthmut.ongoing.Set(err) + client.reauthmut.ongoing = nil + client.reauthmut.Unlock() + + return err +} + +// RequestOpts customizes the behavior of the provider.Request() method. +type RequestOpts struct { + // JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The + // content type of the request will default to "application/json" unless overridden by MoreHeaders. + // It's an error to specify both a JSONBody and a RawBody. + JSONBody any + // RawBody contains an io.Reader that will be consumed by the request directly. No content-type + // will be set unless one is provided explicitly by MoreHeaders. + RawBody io.Reader + // JSONResponse, if provided, will be populated with the contents of the response body parsed as + // JSON. + JSONResponse any + // OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If + // the response has a different code, an error will be returned. + OkCodes []int + // MoreHeaders specifies additional HTTP headers to be provided on the request. + // MoreHeaders will be overridden by OmitHeaders + MoreHeaders map[string]string + // OmitHeaders specifies the HTTP headers which should be omitted. + // OmitHeaders will override MoreHeaders + OmitHeaders []string + // KeepResponseBody specifies whether to keep the HTTP response body. Usually used, when the HTTP + // response body is considered for further use. Valid when JSONResponse is nil. + KeepResponseBody bool +} + +// requestState contains temporary state for a single ProviderClient.Request() call. +type requestState struct { + // This flag indicates if we have reauthenticated during this request because of a 401 response. + // It ensures that we don't reauthenticate multiple times for a single request. If we + // reauthenticate, but keep getting 401 responses with the fresh token, reauthenticating some more + // will just get us into an infinite loop. + hasReauthenticated bool + // Retry-After backoff counter, increments during each backoff call + retries uint +} + +var applicationJSON = "application/json" + +// Request performs an HTTP request using the ProviderClient's +// current HTTPClient. An authentication header will automatically be provided. +func (client *ProviderClient) Request(ctx context.Context, method, url string, options *RequestOpts) (*http.Response, error) { + return client.doRequest(ctx, method, url, options, &requestState{ + hasReauthenticated: false, + }) +} + +func (client *ProviderClient) doRequest(ctx context.Context, method, url string, options *RequestOpts, state *requestState) (*http.Response, error) { + var body io.Reader + var contentType *string + + // Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided + // io.ReadSeeker as-is. Default the content-type to application/json. + if options.JSONBody != nil { + if options.RawBody != nil { + return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()") + } + + rendered, err := json.Marshal(options.JSONBody) + if err != nil { + return nil, err + } + + body = bytes.NewReader(rendered) + contentType = &applicationJSON + } + + // Return an error, when "KeepResponseBody" is true and "JSONResponse" is not nil + if options.KeepResponseBody && options.JSONResponse != nil { + return nil, errors.New("cannot use KeepResponseBody when JSONResponse is not nil") + } + + if options.RawBody != nil { + body = options.RawBody + } + + req, err := http.NewRequestWithContext(ctx, method, url, body) + if err != nil { + return nil, err + } + + // Populate the request headers. + // Apply options.MoreHeaders and options.OmitHeaders, to give the caller the chance to + // modify or omit any header. + if contentType != nil { + req.Header.Set("Content-Type", *contentType) + } + req.Header.Set("Accept", applicationJSON) + + // Set the User-Agent header + req.Header.Set("User-Agent", client.UserAgent.Join()) + + if options.MoreHeaders != nil { + for k, v := range options.MoreHeaders { + req.Header.Set(k, v) + } + } + + for _, v := range options.OmitHeaders { + req.Header.Del(v) + } + + // get latest token from client + for k, v := range client.AuthenticatedHeaders() { + req.Header.Set(k, v) + } + + prereqtok := req.Header.Get("X-Auth-Token") + + // Issue the request. + resp, err := client.HTTPClient.Do(req) + if err != nil { + if client.RetryFunc != nil { + var e error + state.retries = state.retries + 1 + e = client.RetryFunc(ctx, method, url, options, err, state.retries) + if e != nil { + return nil, e + } + + return client.doRequest(ctx, method, url, options, state) + } + return nil, err + } + + // Allow default OkCodes if none explicitly set + okc := options.OkCodes + if okc == nil { + okc = defaultOkCodes(method) + } + + // Validate the HTTP response status. + var ok bool + for _, code := range okc { + if resp.StatusCode == code { + ok = true + break + } + } + + if !ok { + body, _ := io.ReadAll(resp.Body) + resp.Body.Close() + respErr := ErrUnexpectedResponseCode{ + URL: url, + Method: method, + Expected: okc, + Actual: resp.StatusCode, + Body: body, + ResponseHeader: resp.Header, + } + + switch resp.StatusCode { + case http.StatusUnauthorized: + if client.ReauthFunc != nil && !state.hasReauthenticated { + err = client.Reauthenticate(ctx, prereqtok) + if err != nil { + e := &ErrUnableToReauthenticate{} + e.ErrOriginal = respErr + e.ErrReauth = err + return nil, e + } + if options.RawBody != nil { + if seeker, ok := options.RawBody.(io.Seeker); ok { + if _, err := seeker.Seek(0, 0); err != nil { + return nil, err + } + } + } + state.hasReauthenticated = true + resp, err = client.doRequest(ctx, method, url, options, state) + if err != nil { + switch e := err.(type) { + case *ErrUnexpectedResponseCode: + err := &ErrErrorAfterReauthentication{} + err.ErrOriginal = e + return nil, err + default: + err := &ErrErrorAfterReauthentication{} + err.ErrOriginal = e + return nil, err + } + } + return resp, nil + } + case http.StatusTooManyRequests, 498: + maxTries := client.MaxBackoffRetries + if maxTries == 0 { + maxTries = DefaultMaxBackoffRetries + } + + if f := client.RetryBackoffFunc; f != nil && state.retries < maxTries { + var e error + + state.retries = state.retries + 1 + e = f(ctx, &respErr, err, state.retries) + + if e != nil { + return resp, e + } + + return client.doRequest(ctx, method, url, options, state) + } + } + + if err == nil { + err = respErr + } + + if err != nil && client.RetryFunc != nil { + var e error + state.retries = state.retries + 1 + e = client.RetryFunc(ctx, method, url, options, err, state.retries) + if e != nil { + return resp, e + } + + return client.doRequest(ctx, method, url, options, state) + } + + return resp, err + } + + // Parse the response body as JSON, if requested to do so. + if options.JSONResponse != nil { + defer resp.Body.Close() + // Don't decode JSON when there is no content + if resp.StatusCode == http.StatusNoContent { + // read till EOF, otherwise the connection will be closed and cannot be reused + _, err = io.Copy(io.Discard, resp.Body) + return resp, err + } + if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil { + if client.RetryFunc != nil { + var e error + state.retries = state.retries + 1 + e = client.RetryFunc(ctx, method, url, options, err, state.retries) + if e != nil { + return resp, e + } + + return client.doRequest(ctx, method, url, options, state) + } + return nil, err + } + } + + // Close unused body to allow the HTTP connection to be reused + if !options.KeepResponseBody && options.JSONResponse == nil { + defer resp.Body.Close() + // read till EOF, otherwise the connection will be closed and cannot be reused + if _, err := io.Copy(io.Discard, resp.Body); err != nil { + return nil, err + } + } + + return resp, nil +} + +func defaultOkCodes(method string) []int { + switch method { + case "GET", "HEAD": + return []int{200} + case "POST": + return []int{201, 202} + case "PUT": + return []int{201, 202} + case "PATCH": + return []int{200, 202, 204} + case "DELETE": + return []int{202, 204} + } + + return []int{} +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/results.go b/vendor/github.com/gophercloud/gophercloud/v2/results.go new file mode 100644 index 0000000..9e6f630 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/results.go @@ -0,0 +1,465 @@ +package gophercloud + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "reflect" + "strconv" + "time" +) + +/* +Result is an internal type to be used by individual resource packages, but its +methods will be available on a wide variety of user-facing embedding types. + +It acts as a base struct that other Result types, returned from request +functions, can embed for convenience. All Results capture basic information +from the HTTP transaction that was performed, including the response body, +HTTP headers, and any errors that happened. + +Generally, each Result type will have an Extract method that can be used to +further interpret the result's payload in a specific context. Extensions or +providers can then provide additional extraction functions to pull out +provider- or extension-specific information as well. +*/ +type Result struct { + // Body is the payload of the HTTP response from the server. In most cases, + // this will be the deserialized JSON structure. + Body any + + // StatusCode is the HTTP status code of the original response. Will be + // one of the OkCodes defined on the gophercloud.RequestOpts that was + // used in the request. + StatusCode int + + // Header contains the HTTP header structure from the original response. + Header http.Header + + // Err is an error that occurred during the operation. It's deferred until + // extraction to make it easier to chain the Extract call. + Err error +} + +// ExtractInto allows users to provide an object into which `Extract` will extract +// the `Result.Body`. This would be useful for OpenStack providers that have +// different fields in the response object than OpenStack proper. +func (r Result) ExtractInto(to any) error { + if r.Err != nil { + return r.Err + } + + if reader, ok := r.Body.(io.Reader); ok { + if readCloser, ok := reader.(io.Closer); ok { + defer readCloser.Close() + } + return json.NewDecoder(reader).Decode(to) + } + + b, err := json.Marshal(r.Body) + if err != nil { + return err + } + err = json.Unmarshal(b, to) + + return err +} + +func (r Result) extractIntoPtr(to any, label string) error { + if label == "" { + return r.ExtractInto(&to) + } + + var m map[string]any + err := r.ExtractInto(&m) + if err != nil { + return err + } + + b, err := json.Marshal(m[label]) + if err != nil { + return err + } + + toValue := reflect.ValueOf(to) + if toValue.Kind() == reflect.Ptr { + toValue = toValue.Elem() + } + + switch toValue.Kind() { + case reflect.Slice: + typeOfV := toValue.Type().Elem() + if typeOfV.Kind() == reflect.Struct { + if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { + newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) + + if mSlice, ok := m[label].([]any); ok { + for _, v := range mSlice { + // For each iteration of the slice, we create a new struct. + // This is to work around a bug where elements of a slice + // are reused and not overwritten when the same copy of the + // struct is used: + // + // https://github.com/golang/go/issues/21092 + // https://github.com/golang/go/issues/24155 + // https://play.golang.org/p/NHo3ywlPZli + newType := reflect.New(typeOfV).Elem() + + b, err := json.Marshal(v) + if err != nil { + return err + } + + // This is needed for structs with an UnmarshalJSON method. + // Technically this is just unmarshalling the response into + // a struct that is never used, but it's good enough to + // trigger the UnmarshalJSON method. + for i := 0; i < newType.NumField(); i++ { + s := newType.Field(i).Addr().Interface() + + // Unmarshal is used rather than NewDecoder to also work + // around the above-mentioned bug. + err = json.Unmarshal(b, s) + if err != nil { + return err + } + } + + newSlice = reflect.Append(newSlice, newType) + } + } + + // "to" should now be properly modeled to receive the + // JSON response body and unmarshal into all the correct + // fields of the struct or composed extension struct + // at the end of this method. + toValue.Set(newSlice) + + // jtopjian: This was put into place to resolve the issue + // described at + // https://github.com/gophercloud/gophercloud/issues/1963 + // + // This probably isn't the best fix, but it appears to + // be resolving the issue, so I'm going to implement it + // for now. + // + // For future readers, this entire case statement could + // use a review. + return nil + } + } + case reflect.Struct: + typeOfV := toValue.Type() + if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { + for i := 0; i < toValue.NumField(); i++ { + toField := toValue.Field(i) + if toField.Kind() == reflect.Struct { + s := toField.Addr().Interface() + err = json.NewDecoder(bytes.NewReader(b)).Decode(s) + if err != nil { + return err + } + } + } + } + } + + err = json.Unmarshal(b, &to) + return err +} + +// ExtractIntoStructPtr will unmarshal the Result (r) into the provided +// any (to). +// +// NOTE: For internal use only +// +// `to` must be a pointer to an underlying struct type +// +// If provided, `label` will be filtered out of the response +// body prior to `r` being unmarshalled into `to`. +func (r Result) ExtractIntoStructPtr(to any, label string) error { + if r.Err != nil { + return r.Err + } + + t := reflect.TypeOf(to) + if k := t.Kind(); k != reflect.Ptr { + return fmt.Errorf("Expected pointer, got %v", k) + } + switch t.Elem().Kind() { + case reflect.Struct: + return r.extractIntoPtr(to, label) + default: + return fmt.Errorf("Expected pointer to struct, got: %v", t) + } +} + +// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided +// any (to). +// +// NOTE: For internal use only +// +// `to` must be a pointer to an underlying slice type +// +// If provided, `label` will be filtered out of the response +// body prior to `r` being unmarshalled into `to`. +func (r Result) ExtractIntoSlicePtr(to any, label string) error { + if r.Err != nil { + return r.Err + } + + t := reflect.TypeOf(to) + if k := t.Kind(); k != reflect.Ptr { + return fmt.Errorf("Expected pointer, got %v", k) + } + switch t.Elem().Kind() { + case reflect.Slice: + return r.extractIntoPtr(to, label) + default: + return fmt.Errorf("Expected pointer to slice, got: %v", t) + } +} + +// PrettyPrintJSON creates a string containing the full response body as +// pretty-printed JSON. It's useful for capturing test fixtures and for +// debugging extraction bugs. If you include its output in an issue related to +// a buggy extraction function, we will all love you forever. +func (r Result) PrettyPrintJSON() string { + pretty, err := json.MarshalIndent(r.Body, "", " ") + if err != nil { + panic(err.Error()) + } + return string(pretty) +} + +// ErrResult is an internal type to be used by individual resource packages, but +// its methods will be available on a wide variety of user-facing embedding +// types. +// +// It represents results that only contain a potential error and +// nothing else. Usually, if the operation executed successfully, the Err field +// will be nil; otherwise it will be stocked with a relevant error. Use the +// ExtractErr method +// to cleanly pull it out. +type ErrResult struct { + Result +} + +// ExtractErr is a function that extracts error information, or nil, from a result. +func (r ErrResult) ExtractErr() error { + return r.Err +} + +/* +HeaderResult is an internal type to be used by individual resource packages, but +its methods will be available on a wide variety of user-facing embedding types. + +It represents a result that only contains an error (possibly nil) and an +http.Header. This is used, for example, by the objectstorage packages in +openstack, because most of the operations don't return response bodies, but do +have relevant information in headers. +*/ +type HeaderResult struct { + Result +} + +// ExtractInto allows users to provide an object into which `Extract` will +// extract the http.Header headers of the result. +func (r HeaderResult) ExtractInto(to any) error { + if r.Err != nil { + return r.Err + } + + tmpHeaderMap := map[string]string{} + for k, v := range r.Header { + if len(v) > 0 { + tmpHeaderMap[k] = v[0] + } + } + + b, err := json.Marshal(tmpHeaderMap) + if err != nil { + return err + } + err = json.Unmarshal(b, to) + + return err +} + +// RFC3339Milli describes a common time format used by some API responses. +const RFC3339Milli = "2006-01-02T15:04:05.999999Z" + +type JSONRFC3339Milli time.Time + +func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error { + b := bytes.NewBuffer(data) + dec := json.NewDecoder(b) + var s string + if err := dec.Decode(&s); err != nil { + return err + } + t, err := time.Parse(RFC3339Milli, s) + if err != nil { + return err + } + *jt = JSONRFC3339Milli(t) + return nil +} + +const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999" + +type JSONRFC3339MilliNoZ time.Time + +func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339MilliNoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339MilliNoZ(t) + return nil +} + +type JSONRFC1123 time.Time + +func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(time.RFC1123, s) + if err != nil { + return err + } + *jt = JSONRFC1123(t) + return nil +} + +type JSONUnix time.Time + +func (jt *JSONUnix) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + unix, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return err + } + t = time.Unix(unix, 0) + *jt = JSONUnix(t) + return nil +} + +// RFC3339NoZ is the time format used in Heat (Orchestration). +const RFC3339NoZ = "2006-01-02T15:04:05" + +type JSONRFC3339NoZ time.Time + +func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339NoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339NoZ(t) + return nil +} + +// RFC3339ZNoT is the time format used in Zun (Containers Service). +const RFC3339ZNoT = "2006-01-02 15:04:05-07:00" + +type JSONRFC3339ZNoT time.Time + +func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoT, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoT(t) + return nil +} + +// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service). +const RFC3339ZNoTNoZ = "2006-01-02 15:04:05" + +type JSONRFC3339ZNoTNoZ time.Time + +func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoTNoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoTNoZ(t) + return nil +} + +/* +Link is an internal type to be used in packages of collection resources that are +paginated in a certain way. + +It's a response substructure common to many paginated collection results that is +used to point to related pages. Usually, the one we care about is the one with +Rel field set to "next". +*/ +type Link struct { + Href string `json:"href"` + Rel string `json:"rel"` +} + +/* +ExtractNextURL is an internal function useful for packages of collection +resources that are paginated in a certain way. + +It attempts to extract the "next" URL from slice of Link structs, or +"" if no such URL is present. +*/ +func ExtractNextURL(links []Link) (string, error) { + var url string + + for _, l := range links { + if l.Rel == "next" { + url = l.Href + } + } + + if url == "" { + return "", nil + } + + return url, nil +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/service_client.go b/vendor/github.com/gophercloud/gophercloud/v2/service_client.go new file mode 100644 index 0000000..11b8010 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/service_client.go @@ -0,0 +1,164 @@ +package gophercloud + +import ( + "context" + "io" + "net/http" + "strings" +) + +// ServiceClient stores details required to interact with a specific service API implemented by a provider. +// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient. +type ServiceClient struct { + // ProviderClient is a reference to the provider that implements this service. + *ProviderClient + + // Endpoint is the base URL of the service's API, acquired from a service catalog. + // It MUST end with a /. + Endpoint string + + // ResourceBase is the base URL shared by the resources within a service's API. It should include + // the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used + // as-is, instead. + ResourceBase string + + // This is the service client type (e.g. compute, sharev2). + // NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS. + // It is only exported because it gets set in a different package. + Type string + + // The microversion of the service to use. Set this to use a particular microversion. + Microversion string + + // MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way, + // values set in this field will be set on all the HTTP requests the service client sends. + MoreHeaders map[string]string +} + +// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /. +func (client *ServiceClient) ResourceBaseURL() string { + if client.ResourceBase != "" { + return client.ResourceBase + } + return client.Endpoint +} + +// ServiceURL constructs a URL for a resource belonging to this provider. +func (client *ServiceClient) ServiceURL(parts ...string) string { + return client.ResourceBaseURL() + strings.Join(parts, "/") +} + +func (client *ServiceClient) initReqOpts(JSONBody any, JSONResponse any, opts *RequestOpts) { + if v, ok := (JSONBody).(io.Reader); ok { + opts.RawBody = v + } else if JSONBody != nil { + opts.JSONBody = JSONBody + } + + if JSONResponse != nil { + opts.JSONResponse = JSONResponse + } +} + +// Get calls `Request` with the "GET" HTTP verb. +func (client *ServiceClient) Get(ctx context.Context, url string, JSONResponse any, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(nil, JSONResponse, opts) + return client.Request(ctx, "GET", url, opts) +} + +// Post calls `Request` with the "POST" HTTP verb. +func (client *ServiceClient) Post(ctx context.Context, url string, JSONBody any, JSONResponse any, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(JSONBody, JSONResponse, opts) + return client.Request(ctx, "POST", url, opts) +} + +// Put calls `Request` with the "PUT" HTTP verb. +func (client *ServiceClient) Put(ctx context.Context, url string, JSONBody any, JSONResponse any, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(JSONBody, JSONResponse, opts) + return client.Request(ctx, "PUT", url, opts) +} + +// Patch calls `Request` with the "PATCH" HTTP verb. +func (client *ServiceClient) Patch(ctx context.Context, url string, JSONBody any, JSONResponse any, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(JSONBody, JSONResponse, opts) + return client.Request(ctx, "PATCH", url, opts) +} + +// Delete calls `Request` with the "DELETE" HTTP verb. +func (client *ServiceClient) Delete(ctx context.Context, url string, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(nil, nil, opts) + return client.Request(ctx, "DELETE", url, opts) +} + +// Head calls `Request` with the "HEAD" HTTP verb. +func (client *ServiceClient) Head(ctx context.Context, url string, opts *RequestOpts) (*http.Response, error) { + if opts == nil { + opts = new(RequestOpts) + } + client.initReqOpts(nil, nil, opts) + return client.Request(ctx, "HEAD", url, opts) +} + +func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) { + switch client.Type { + case "compute": + opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion + case "sharev2": + opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion + case "volume": + opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion + case "baremetal": + opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion + case "baremetal-introspection": + opts.MoreHeaders["X-OpenStack-Ironic-Inspector-API-Version"] = client.Microversion + } + + if client.Type != "" { + opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion + } +} + +// Request carries out the HTTP operation for the service client +func (client *ServiceClient) Request(ctx context.Context, method, url string, options *RequestOpts) (*http.Response, error) { + if options.MoreHeaders == nil { + options.MoreHeaders = make(map[string]string) + } + + if client.Microversion != "" { + client.setMicroversionHeader(options) + } + + if len(client.MoreHeaders) > 0 { + if options == nil { + options = new(RequestOpts) + } + + for k, v := range client.MoreHeaders { + options.MoreHeaders[k] = v + } + } + return client.ProviderClient.Request(ctx, method, url, options) +} + +// ParseResponse is a helper function to parse http.Response to constituents. +func ParseResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, error) { + if resp != nil { + return resp.Body, resp.Header, err + } + return nil, nil, err +} diff --git a/vendor/github.com/gophercloud/gophercloud/v2/util.go b/vendor/github.com/gophercloud/gophercloud/v2/util.go new file mode 100644 index 0000000..ad8a7df --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/v2/util.go @@ -0,0 +1,109 @@ +package gophercloud + +import ( + "context" + "net/url" + "path/filepath" + "reflect" + "strings" + "time" +) + +// NormalizePathURL is used to convert rawPath to a fqdn, using basePath as +// a reference in the filesystem, if necessary. basePath is assumed to contain +// either '.' when first used, or the file:// type fqdn of the parent resource. +// e.g. myFavScript.yaml => file://opt/lib/myFavScript.yaml +func NormalizePathURL(basePath, rawPath string) (string, error) { + u, err := url.Parse(rawPath) + if err != nil { + return "", err + } + // if a scheme is defined, it must be a fqdn already + if u.Scheme != "" { + return u.String(), nil + } + // if basePath is a url, then child resources are assumed to be relative to it + bu, err := url.Parse(basePath) + if err != nil { + return "", err + } + var basePathSys, absPathSys string + if bu.Scheme != "" { + basePathSys = filepath.FromSlash(bu.Path) + absPathSys = filepath.Join(basePathSys, rawPath) + bu.Path = filepath.ToSlash(absPathSys) + return bu.String(), nil + } + + absPathSys = filepath.Join(basePath, rawPath) + u.Path = filepath.ToSlash(absPathSys) + if err != nil { + return "", err + } + u.Scheme = "file" + return u.String(), nil +} + +// NormalizeURL is an internal function to be used by provider clients. +// +// It ensures that each endpoint URL has a closing `/`, as expected by +// ServiceClient's methods. +func NormalizeURL(url string) string { + if !strings.HasSuffix(url, "/") { + return url + "/" + } + return url +} + +// RemainingKeys will inspect a struct and compare it to a map. Any struct +// field that does not have a JSON tag that matches a key in the map or +// a matching lower-case field in the map will be returned as an extra. +// +// This is useful for determining the extra fields returned in response bodies +// for resources that can contain an arbitrary or dynamic number of fields. +func RemainingKeys(s any, m map[string]any) (extras map[string]any) { + extras = make(map[string]any) + for k, v := range m { + extras[k] = v + } + + valueOf := reflect.ValueOf(s) + typeOf := reflect.TypeOf(s) + for i := 0; i < valueOf.NumField(); i++ { + field := typeOf.Field(i) + + lowerField := strings.ToLower(field.Name) + delete(extras, lowerField) + + if tagValue := field.Tag.Get("json"); tagValue != "" && tagValue != "-" { + delete(extras, tagValue) + } + } + + return +} + +// WaitFor polls a predicate function, once per second, up to a context cancellation. +// This is useful to wait for a resource to transition to a certain state. +// Resource packages will wrap this in a more convenient function that's +// specific to a certain resource, but it can also be useful on its own. +func WaitFor(ctx context.Context, predicate func(context.Context) (bool, error)) error { + if done, err := predicate(ctx); done || err != nil { + return err + } + + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if done, err := predicate(ctx); done || err != nil { + return err + } + + case <-ctx.Done(): + return ctx.Err() + } + } +} diff --git a/vendor/github.com/gophercloud/utils/v2/LICENSE b/vendor/github.com/gophercloud/utils/v2/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/github.com/gophercloud/utils/v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/gophercloud/utils/v2/client/client.go b/vendor/github.com/gophercloud/utils/v2/client/client.go new file mode 100644 index 0000000..e2cd4ce --- /dev/null +++ b/vendor/github.com/gophercloud/utils/v2/client/client.go @@ -0,0 +1,404 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "sort" + "strconv" + "strings" + "time" + + "github.com/gophercloud/gophercloud/v2" +) + +// Logger is an interface representing the Logger struct +type Logger interface { + Printf(format string, args ...interface{}) +} + +// DefaultLogger is a default struct, which satisfies the Logger interface +type DefaultLogger struct{} + +// Printf is a default Printf method +func (DefaultLogger) Printf(format string, args ...interface{}) { + log.Printf("[DEBUG] "+format, args...) +} + +// noopLogger is a default noop logger satisfies the Logger interface +type noopLogger struct{} + +// Printf is a default noop method +func (noopLogger) Printf(format string, args ...interface{}) {} + +// RoundTripper satisfies the http.RoundTripper interface and is used to +// customize the default http client RoundTripper +type RoundTripper struct { + // Default http.RoundTripper + Rt http.RoundTripper + // Additional request headers to be set (not appended) in all client + // requests + headers *http.Header + // A pointer to a map of headers to be masked in logger + maskHeaders *map[string]struct{} + // A custom function to format and mask JSON requests and responses + FormatJSON func([]byte) (string, error) + // How many times HTTP connection should be retried until giving up + MaxRetries int + // If Logger is not nil, then RoundTrip method will debug the JSON + // requests and responses + Logger Logger +} + +// List of headers that contain sensitive data. +var defaultSensitiveHeaders = map[string]struct{}{ + "x-auth-token": {}, + "x-auth-key": {}, + "x-service-token": {}, + "x-storage-token": {}, + "x-account-meta-temp-url-key": {}, + "x-account-meta-temp-url-key-2": {}, + "x-container-meta-temp-url-key": {}, + "x-container-meta-temp-url-key-2": {}, + "set-cookie": {}, + "x-subject-token": {}, + "authorization": {}, +} + +// GetDefaultSensitiveHeaders returns the default list of headers to be masked +func GetDefaultSensitiveHeaders() []string { + headers := make([]string, len(defaultSensitiveHeaders)) + i := 0 + for k := range defaultSensitiveHeaders { + headers[i] = k + i++ + } + + return headers +} + +// SetSensitiveHeaders sets the list of case insensitive headers to be masked in +// debug log +func (rt *RoundTripper) SetSensitiveHeaders(headers []string) { + newHeaders := make(map[string]struct{}, len(headers)) + + for _, h := range headers { + newHeaders[h] = struct{}{} + } + + // this is concurrency safe + rt.maskHeaders = &newHeaders +} + +// SetHeaders sets request headers to be set (not appended) in all client +// requests +func (rt *RoundTripper) SetHeaders(headers http.Header) { + newHeaders := make(http.Header, len(headers)) + for k, v := range headers { + s := make([]string, len(v)) + for i, v := range v { + s[i] = v + } + newHeaders[k] = s + } + + // this is concurrency safe + rt.headers = &newHeaders +} + +func (rt *RoundTripper) hideSensitiveHeadersData(headers http.Header) []string { + result := make([]string, len(headers)) + headerIdx := 0 + + // this is concurrency safe + v := rt.maskHeaders + if v == nil { + v = &defaultSensitiveHeaders + } + maskHeaders := *v + + for header, data := range headers { + v := strings.ToLower(header) + if _, ok := maskHeaders[v]; ok { + result[headerIdx] = fmt.Sprintf("%s: %s", header, "***") + } else { + result[headerIdx] = fmt.Sprintf("%s: %s", header, strings.Join(data, " ")) + } + headerIdx++ + } + + return result +} + +// formatHeaders converts standard http.Header type to a string with separated headers. +// It will hide data of sensitive headers. +func (rt *RoundTripper) formatHeaders(headers http.Header, separator string) string { + redactedHeaders := rt.hideSensitiveHeadersData(headers) + sort.Strings(redactedHeaders) + + return strings.Join(redactedHeaders, separator) +} + +// RoundTrip performs a round-trip HTTP request and logs relevant information about it. +func (rt *RoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { + defer func() { + if request.Body != nil { + request.Body.Close() + } + }() + + // for future reference, this is how to access the Transport struct: + //tlsconfig := rt.Rt.(*http.Transport).TLSClientConfig + + // this is concurrency safe + h := rt.headers + if h != nil { + for k, v := range *h { + // Set additional request headers + request.Header[k] = v + } + } + + var err error + + if rt.Logger != nil { + rt.log().Printf("OpenStack Request URL: %s %s", request.Method, request.URL) + rt.log().Printf("OpenStack Request Headers:\n%s", rt.formatHeaders(request.Header, "\n")) + + if request.Body != nil { + request.Body, err = rt.logRequest(request.Body, request.Header.Get("Content-Type")) + if err != nil { + return nil, err + } + } + } + + // this is concurrency safe + ort := rt.Rt + if ort == nil { + return nil, fmt.Errorf("Rt RoundTripper is nil, aborting") + } + response, err := ort.RoundTrip(request) + + // If the first request didn't return a response, retry up to `max_retries`. + retry := 1 + for response == nil { + if retry > rt.MaxRetries { + if rt.Logger != nil { + rt.log().Printf("OpenStack connection error, retries exhausted. Aborting") + } + err = fmt.Errorf("OpenStack connection error, retries exhausted. Aborting. Last error was: %s", err) + return nil, err + } + + if rt.Logger != nil { + rt.log().Printf("OpenStack connection error, retry number %d: %s", retry, err) + } + response, err = ort.RoundTrip(request) + retry += 1 + } + + if rt.Logger != nil { + rt.log().Printf("OpenStack Response Code: %d", response.StatusCode) + rt.log().Printf("OpenStack Response Headers:\n%s", rt.formatHeaders(response.Header, "\n")) + + response.Body, err = rt.logResponse(response.Body, response.Header.Get("Content-Type")) + } + + return response, err +} + +// logRequest will log the HTTP Request details. +// If the body is JSON, it will attempt to be pretty-formatted. +func (rt *RoundTripper) logRequest(original io.ReadCloser, contentType string) (io.ReadCloser, error) { + // Handle request contentType + if strings.HasPrefix(contentType, "application/json") || (strings.HasPrefix(contentType, "application/") && strings.HasSuffix(contentType, "-json-patch")) { + var bs bytes.Buffer + defer original.Close() + + _, err := io.Copy(&bs, original) + if err != nil { + return nil, err + } + + debugInfo, err := rt.formatJSON()(bs.Bytes()) + if err != nil { + rt.log().Printf("%s", err) + } + rt.log().Printf("OpenStack Request Body: %s", debugInfo) + + return ioutil.NopCloser(strings.NewReader(bs.String())), nil + } + + rt.log().Printf("Not logging because OpenStack request body isn't JSON") + return original, nil +} + +// logResponse will log the HTTP Response details. +// If the body is JSON, it will attempt to be pretty-formatted. +func (rt *RoundTripper) logResponse(original io.ReadCloser, contentType string) (io.ReadCloser, error) { + if strings.HasPrefix(contentType, "application/json") { + var bs bytes.Buffer + defer original.Close() + + _, err := io.Copy(&bs, original) + if err != nil { + return nil, err + } + + debugInfo, err := rt.formatJSON()(bs.Bytes()) + if err != nil { + rt.log().Printf("%s", err) + } + if debugInfo != "" { + rt.log().Printf("OpenStack Response Body: %s", debugInfo) + } + + return ioutil.NopCloser(strings.NewReader(bs.String())), nil + } + + rt.log().Printf("Not logging because OpenStack response body isn't JSON") + return original, nil +} + +func (rt *RoundTripper) formatJSON() func([]byte) (string, error) { + // this is concurrency safe + f := rt.FormatJSON + if f == nil { + return FormatJSON + } + return f +} + +func (rt *RoundTripper) log() Logger { + // this is concurrency safe + l := rt.Logger + if l == nil { + // noop is used, when logger pointer has been set to nil + return &noopLogger{} + } + return l +} + +// FormatJSON is a default function to pretty-format a JSON body. +// It will also mask known fields which contain sensitive information. +func FormatJSON(raw []byte) (string, error) { + var rawData interface{} + + err := json.Unmarshal(raw, &rawData) + if err != nil { + return string(raw), fmt.Errorf("unable to parse OpenStack JSON: %s", err) + } + + data, ok := rawData.(map[string]interface{}) + if !ok { + pretty, err := json.MarshalIndent(rawData, "", " ") + if err != nil { + return string(raw), fmt.Errorf("unable to re-marshal OpenStack JSON: %s", err) + } + + return string(pretty), nil + } + + // Mask known password fields + if v, ok := data["auth"].(map[string]interface{}); ok { + // v2 auth methods + if v, ok := v["passwordCredentials"].(map[string]interface{}); ok { + v["password"] = "***" + } + if v, ok := v["token"].(map[string]interface{}); ok { + v["id"] = "***" + } + // v3 auth methods + if v, ok := v["identity"].(map[string]interface{}); ok { + if v, ok := v["password"].(map[string]interface{}); ok { + if v, ok := v["user"].(map[string]interface{}); ok { + v["password"] = "***" + } + } + if v, ok := v["application_credential"].(map[string]interface{}); ok { + v["secret"] = "***" + } + if v, ok := v["token"].(map[string]interface{}); ok { + v["id"] = "***" + } + } + } + + // Mask EC2 access id and body hash + if v, ok := data["credentials"].(map[string]interface{}); ok { + var access string + if s, ok := v["access"]; ok { + access, _ = s.(string) + v["access"] = "***" + } + if _, ok := v["body_hash"]; ok { + v["body_hash"] = "***" + } + if v, ok := v["headers"].(map[string]interface{}); ok { + if _, ok := v["Authorization"]; ok { + if s, ok := v["Authorization"].(string); ok { + v["Authorization"] = strings.Replace(s, access, "***", -1) + } + } + } + } + + // Ignore the huge catalog output + if v, ok := data["token"].(map[string]interface{}); ok { + if _, ok := v["catalog"]; ok { + v["catalog"] = "***" + } + } + + pretty, err := json.MarshalIndent(data, "", " ") + if err != nil { + return string(raw), fmt.Errorf("unable to re-marshal OpenStack JSON: %s", err) + } + + return string(pretty), nil +} + +func RetryBackoffFunc(logger Logger) gophercloud.RetryBackoffFunc { + return func(ctx context.Context, respErr *gophercloud.ErrUnexpectedResponseCode, e error, retries uint) error { + retryAfter := respErr.ResponseHeader.Get("Retry-After") + if retryAfter == "" { + return e + } + + var sleep time.Duration + + // Parse delay seconds or HTTP date + if v, err := strconv.ParseUint(retryAfter, 10, 32); err == nil { + sleep = time.Duration(v) * time.Second + } else if v, err := time.Parse(http.TimeFormat, retryAfter); err == nil { + sleep = time.Until(v) + } else { + return e + } + + l := logger + if l != nil { + l.Printf("Received StatusTooManyRequests response code sleeping for %s", sleep) + } + if c := ctx; c != nil { + select { + case <-time.After(sleep): + case <-c.Done(): + if l != nil { + l.Printf("Sleeping aborted: %w", c.Err()) + } + return e + } + } else { + time.Sleep(sleep) + } + + return nil + } +} diff --git a/vendor/github.com/gophercloud/utils/v2/client/doc.go b/vendor/github.com/gophercloud/utils/v2/client/doc.go new file mode 100644 index 0000000..7315300 --- /dev/null +++ b/vendor/github.com/gophercloud/utils/v2/client/doc.go @@ -0,0 +1,146 @@ +/* +Package client provides an ability to create a http.RoundTripper OpenStack +client with extended options, including the JSON requests and responses log +capabilities. + +Example usage with the default logger: + + package example + + import ( + "net/http" + "os" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/utils/v2/client" + "github.com/gophercloud/utils/v2/openstack/clientconfig" + ) + + func NewComputeV2Client() (*gophercloud.ServiceClient, error) { + ao, err := clientconfig.AuthOptions(nil) + if err != nil { + return nil, err + } + + provider, err := openstack.NewClient(ao.IdentityEndpoint) + if err != nil { + return nil, err + } + + if os.Getenv("OS_DEBUG") != "" { + provider.HTTPClient = http.Client{ + Transport: &client.RoundTripper{ + Rt: &http.Transport{}, + Logger: &client.DefaultLogger{}, + }, + } + } + + err = openstack.Authenticate(provider, *ao) + if err != nil { + return nil, err + } + + return openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + } + +Example usage with the custom logger: + + package example + + import ( + "net/http" + "os" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/utils/v2/client" + "github.com/gophercloud/utils/v2/openstack/clientconfig" + log "github.com/sirupsen/logrus" + ) + + type myLogger struct { + Prefix string + } + + func (l myLogger) Printf(format string, args ...interface{}) { + log.Debugf("%s [DEBUG] "+format, append([]interface{}{l.Prefix}, args...)...) + } + + func NewComputeV2Client() (*gophercloud.ServiceClient, error) { + ao, err := clientconfig.AuthOptions(nil) + if err != nil { + return nil, err + } + + provider, err := openstack.NewClient(ao.IdentityEndpoint) + if err != nil { + return nil, err + } + + if os.Getenv("OS_DEBUG") != "" { + provider.HTTPClient = http.Client{ + Transport: &client.RoundTripper{ + Rt: &http.Transport{}, + Logger: &myLogger{Prefix: "myApp"}, + }, + } + } + + err = openstack.Authenticate(provider, *ao) + if err != nil { + return nil, err + } + + return openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + } + +Example usage with additinal headers: + + package example + + import ( + "net/http" + "os" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/utils/v2/client" + "github.com/gophercloud/utils/v2/openstack/clientconfig" + ) + + func NewComputeV2Client() (*gophercloud.ServiceClient, error) { + ao, err := clientconfig.AuthOptions(nil) + if err != nil { + return nil, err + } + + provider, err := openstack.NewClient(ao.IdentityEndpoint) + if err != nil { + return nil, err + } + + provider.HTTPClient = http.Client{ + Transport: &client.RoundTripper{ + Rt: &http.Transport{}, + }, + } + + provider.HTTPClient.Transport.(*client.RoundTripper).SetHeaders(map[string][]string{"Cache-Control": {"no-cache"}}}) + + err = openstack.Authenticate(provider, *ao) + if err != nil { + return nil, err + } + + return openstack.NewComputeV2(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + } +*/ +package client diff --git a/vendor/github.com/majewsky/schwift/v2/.gitignore b/vendor/github.com/majewsky/schwift/v2/.gitignore new file mode 100644 index 0000000..df8085e --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/.gitignore @@ -0,0 +1,4 @@ +# test artifacts +cover.out +cover.out.* +cover.html diff --git a/vendor/github.com/majewsky/schwift/v2/.golangci.yaml b/vendor/github.com/majewsky/schwift/v2/.golangci.yaml new file mode 100644 index 0000000..ce734c8 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/.golangci.yaml @@ -0,0 +1,159 @@ +################################################################################ +# This file is AUTOGENERATED with # +# Edit Makefile.maker.yaml instead. # +################################################################################ + +run: + timeout: 3m # 1m by default + modules-download-mode: readonly + +output: + # Do not print lines of code with issue. + print-issued-lines: false + +issues: + exclude: + # It is idiomatic Go to reuse the name 'err' with ':=' for subsequent errors. + # Ref: https://go.dev/doc/effective_go#redeclaration + - 'declaration of "err" shadows declaration at' + exclude-rules: + - path: _test\.go + linters: + - bodyclose + - dupl + # '0' disables the following options. + max-issues-per-linter: 0 + max-same-issues: 0 + +linters-settings: + dupl: + # Tokens count to trigger issue, 150 by default. + threshold: 100 + errcheck: + # Report about assignment of errors to blank identifier. + check-blank: true + # Report about not checking of errors in type assertions. + check-type-assertions: true + forbidigo: + analyze-types: true # required for pkg: + forbid: + # ioutil package has been deprecated: https://github.com/golang/go/issues/42026 + - ^ioutil\..*$ + # Using http.DefaultServeMux is discouraged because it's a global variable that some packages silently and magically add handlers to (esp. net/http/pprof). + # Applications wishing to use http.ServeMux should obtain local instances through http.NewServeMux() instead of using the global default instance. + - ^http\.DefaultServeMux$ + - ^http\.Handle(?:Func)?$ + # Forbid usage of old and archived square/go-jose + - pkg: ^gopkg\.in/square/go-jose\.v2$ + msg: "gopk.in/square/go-jose is arcived and has CVEs. Replace it with gopkg.in/go-jose/go-jose.v2" + - pkg: ^github.com/coreos/go-oidc$ + msg: "github.com/coreos/go-oidc depends on gopkg.in/square/go-jose which has CVEs. Replace it with github.com/coreos/go-oidc/v3" + + - pkg: ^github.com/howeyc/gopass$ + msg: "github.com/howeyc/gopass is archived, use golang.org/x/term instead" + goconst: + ignore-tests: true + min-occurrences: 5 + gocritic: + enabled-checks: + - boolExprSimplify + - builtinShadow + - emptyStringTest + - evalOrder + - httpNoBody + - importShadow + - initClause + - methodExprCall + - paramTypeCombine + - preferFilepathJoin + - ptrToRefParam + - redundantSprint + - returnAfterHttpError + - stringConcatSimplify + - timeExprSimplify + - truncateCmp + - typeAssertChain + - typeUnparen + - unnamedResult + - unnecessaryBlock + - unnecessaryDefer + - weakCond + - yodaStyleExpr + goimports: + # Put local imports after 3rd-party packages. + local-prefixes: github.com/majewsky/schwift + gosec: + excludes: + # gosec wants us to set a short ReadHeaderTimeout to avoid Slowloris attacks, but doing so would expose us to Keep-Alive race conditions (see https://iximiuz.com/en/posts/reverse-proxy-http-keep-alive-and-502s/) + - G112 + # created file permissions are restricted by umask if necessary + - G306 + govet: + enable-all: true + disable: + - fieldalignment + nolintlint: + require-specific: true + stylecheck: + dot-import-whitelist: + - github.com/onsi/ginkgo/v2 + - github.com/onsi/gomega + usestdlibvars: + constant-kind: true + crypto-hash: true + default-rpc-path: true + http-method: true + http-status-code: true + sql-isolation-level: true + time-layout: true + time-month: true + time-weekday: true + tls-signature-scheme: true + whitespace: + # Enforce newlines (or comments) after multi-line function signatures. + multi-func: true + +linters: + # We use 'disable-all' and enable linters explicitly so that a newer version + # does not introduce new linters unexpectedly. + disable-all: true + enable: + - bodyclose + - containedctx + - copyloopvar + - dupl + - dupword + - durationcheck + - errcheck + - errname + - errorlint + - forbidigo + - ginkgolinter + - gocheckcompilerdirectives + - goconst + - gocritic + - gofmt + - goimports + - gosec + - gosimple + - govet + - ineffassign + - intrange + - misspell + - nilerr + - noctx + - nolintlint + - nosprintfhostport + - perfsprint + - predeclared + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tenv + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - whitespace diff --git a/vendor/github.com/majewsky/schwift/v2/CHANGELOG.md b/vendor/github.com/majewsky/schwift/v2/CHANGELOG.md new file mode 100644 index 0000000..4779716 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/CHANGELOG.md @@ -0,0 +1,44 @@ +# v2.0.0 (2024-07-08) + +Breaking changes: + +- All methods that make HTTP requests now take a leading `context.Context` argument. + The `RequestOptions.Context` field has been removed because it is made redundant + by these extra arguments. + +# v1.3.0 (2023-10-25) + +New features: + +- The error message of `type UnexpectedStatusCodeError` now includes the + request method and target object name. This will make those errors more + plausible when returned from complex operations that involve several Swift + API calls. + +# v1.2.0 (2022-10-28) + +New features: + +- Digest signing now uses sha256 and sha512 (preference in that order) if + enabled by Swift. + +Changes: + +- Added golangci-lint to `make test`. All new errors and lints were addressed. + +# v1.1.0 (2022-02-07) + +Bugfixes: + +- Fix request URL when object name is not a well-formed path. For example, an + object name like "a///b" is not wrongly normalized into "a/b" anymore. If + your application relies on object names being normalized paths, consider + passing your object names through `path.Clean()` before giving them to + `Container.Object()`. + +# v1.0.0 (2021-05-28) + +Initial release. The library had been mostly feature-complete since 2018, but I +never got around to actually tagging a 1.0.0 since a few less-used features are +missing in the API (mostly object versioning). The 1.0.0 release was overdue, +though, given that this library was already used in many prod deployments. diff --git a/vendor/github.com/majewsky/schwift/v2/CONTRIBUTING.md b/vendor/github.com/majewsky/schwift/v2/CONTRIBUTING.md new file mode 100644 index 0000000..96ae415 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Coding style + +Before submitting a pull request: + +- Please format your code with `gofmt` or `goimports`. +- Please fix warnings generated by `golint` and `go vet`. +- When changing the API, run `godoc -http=:6060`, point your browser to + http://localhost:6060/pkg/github.com/majewsky/schwift and check how your documentation looks. + +As a general rule of thumb, we prefer idiomatic Go code as described in: + +- [Effective Go](https://golang.org/doc/effective_go.html) +- [Collected Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) + +# Running the unit tests + +The unit tests require a Swift server (duh). + +## Using a Swift cluster + +The preferred option is a regular Swift deployment: Run `go test` with the same authentication variables that you give +to the official `swift` client: + +- for Swift inside an OpenStack cluster: `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, etc. +- for Swift using its builtin authentication: `ST_AUTH`, `ST_USER`, `ST_KEY` + +## Using a Swift All-in-One container + +If you don't have a Swift deployment at your disposal where you're running the unit tests, you can use the scripts in +the [`testing` subdirectory](./testing/) to run a Swift all-in-one (SAIO) [development setup in a Docker +container](https://github.com/bouncestorage/docker-swift). + +1. Run `./testing/start-saio.sh` to start the SAIO container (possibly with `sudo` if your user is not in the `docker` + group). Once that has come up successfully, press Ctrl-C to get back to your terminal prompt; the SAIO container will + continue running. + +2. Run the tests with `./testing/with-saio.sh go test`. The script will find how to access the Swift API inside the + container, and configure the auth environment variables accordingly. You can use this with any command that requires + Swift credentials, e.g. `./testing/with-saio.sh swift stat`. diff --git a/vendor/github.com/majewsky/schwift/v2/LICENSE.txt b/vendor/github.com/majewsky/schwift/v2/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/majewsky/schwift/v2/Makefile b/vendor/github.com/majewsky/schwift/v2/Makefile new file mode 100644 index 0000000..a8f2889 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/Makefile @@ -0,0 +1,99 @@ +################################################################################ +# This file is AUTOGENERATED with # +# Edit Makefile.maker.yaml instead. # +################################################################################ + +MAKEFLAGS=--warn-undefined-variables +# /bin/sh is dash on Debian which does not support all features of ash/bash +# to fix that we use /bin/bash only on Debian to not break Alpine +ifneq (,$(wildcard /etc/os-release)) # check file existence + ifneq ($(shell grep -c debian /etc/os-release),0) + SHELL := /bin/bash + endif +endif + +default: FORCE + @echo 'There is nothing to build, use `make check` for running the test suite or `make help` for a list of available targets.' + +generate: generated.go + +%: %.in | util/render_template.go + @echo ./util/render_template.go < $< > $@ + @./util/render_template.go < $< > $@.new && mv $@.new $@ || (rm $@.new; false) + +prepare-static-check: FORCE + @if ! hash golangci-lint 2>/dev/null; then printf "\e[1;36m>> Installing golangci-lint (this may take a while)...\e[0m\n"; go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; fi + +GO_BUILDFLAGS = +GO_LDFLAGS = +GO_TESTENV = + +# which packages to test with test runner +GO_TESTPKGS := $(shell go list -f '{{if or .TestGoFiles .XTestGoFiles}}{{.ImportPath}}{{end}}' ./...) +ifeq ($(GO_TESTPKGS),) +GO_TESTPKGS := ./... +endif +# which packages to measure coverage for +GO_COVERPKGS := $(shell go list ./... | grep -Ev '/util') +# to get around weird Makefile syntax restrictions, we need variables containing nothing, a space and comma +null := +space := $(null) $(null) +comma := , + +check: FORCE static-check build/cover.html + @printf "\e[1;32m>> All checks successful.\e[0m\n" + +run-golangci-lint: FORCE prepare-static-check + @printf "\e[1;36m>> golangci-lint\e[0m\n" + @golangci-lint run + +build/cover.out: FORCE | build + @printf "\e[1;36m>> Running tests\e[0m\n" + @env $(GO_TESTENV) go test -shuffle=on -p 1 -coverprofile=$@ $(GO_BUILDFLAGS) -ldflags '-s -w $(GO_LDFLAGS)' -covermode=count -coverpkg=$(subst $(space),$(comma),$(GO_COVERPKGS)) $(GO_TESTPKGS) + +build/cover.html: build/cover.out + @printf "\e[1;36m>> go tool cover > build/cover.html\e[0m\n" + @go tool cover -html $< -o $@ + +static-check: FORCE run-golangci-lint + +build: + @mkdir $@ + +tidy-deps: FORCE + go mod tidy + go mod verify + +clean: FORCE + git clean -dxf build + +vars: FORCE + @printf "GO_BUILDFLAGS=$(GO_BUILDFLAGS)\n" + @printf "GO_COVERPKGS=$(GO_COVERPKGS)\n" + @printf "GO_LDFLAGS=$(GO_LDFLAGS)\n" + @printf "GO_TESTENV=$(GO_TESTENV)\n" + @printf "GO_TESTPKGS=$(GO_TESTPKGS)\n" +help: FORCE + @printf "\n" + @printf "\e[1mUsage:\e[0m\n" + @printf " make \e[36m\e[0m\n" + @printf "\n" + @printf "\e[1mGeneral\e[0m\n" + @printf " \e[36mvars\e[0m Display values of relevant Makefile variables.\n" + @printf " \e[36mhelp\e[0m Display this help.\n" + @printf "\n" + @printf "\e[1mPrepare\e[0m\n" + @printf " \e[36mprepare-static-check\e[0m Install any tools required by static-check. This is used in CI before dropping privileges, you should probably install all the tools using your package manager\n" + @printf "\n" + @printf "\e[1mTest\e[0m\n" + @printf " \e[36mcheck\e[0m Run the test suite (unit tests and golangci-lint).\n" + @printf " \e[36mrun-golangci-lint\e[0m Install and run golangci-lint. Installing is used in CI, but you should probably install golangci-lint using your package manager.\n" + @printf " \e[36mbuild/cover.out\e[0m Run tests and generate coverage report.\n" + @printf " \e[36mbuild/cover.html\e[0m Generate an HTML file with source code annotations from the coverage report.\n" + @printf " \e[36mstatic-check\e[0m Run static code checks\n" + @printf "\n" + @printf "\e[1mDevelopment\e[0m\n" + @printf " \e[36mtidy-deps\e[0m Run go mod tidy and go mod verify.\n" + @printf " \e[36mclean\e[0m Run git clean.\n" + +.PHONY: FORCE diff --git a/vendor/github.com/majewsky/schwift/v2/Makefile.maker.yaml b/vendor/github.com/majewsky/schwift/v2/Makefile.maker.yaml new file mode 100644 index 0000000..1875a35 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/Makefile.maker.yaml @@ -0,0 +1,17 @@ +# Configuration file for + +metadata: + url: https://github.com/majewsky/schwift + +coverageTest: + except: '/util' + +golangciLint: + createConfig: true + +verbatim: | + generate: generated.go + + %: %.in | util/render_template.go + @echo ./util/render_template.go < $< > $@ + @./util/render_template.go < $< > $@.new && mv $@.new $@ || (rm $@.new; false) diff --git a/vendor/github.com/majewsky/schwift/v2/README.md b/vendor/github.com/majewsky/schwift/v2/README.md new file mode 100644 index 0000000..d7f9f83 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/README.md @@ -0,0 +1,94 @@ +# Schwift + +[![GoDoc](https://godoc.org/github.com/majewsky/schwift?status.svg)](https://godoc.org/github.com/majewsky/schwift) + +This is a Go client library for [OpenStack Swift](https://github.com/openstack/swift). I made this after growing +frustrated with the inflexible API design of [`ncw/swift`](https://github.com/ncw/swift); see [near the +bottom](#why-another-swift-client-library) for details. + +## Installation + +You can get this with `go get github.com/majewsky/schwift`. When using this in an application, vendoring is recommended. + +## Usage + +This library uses [Gophercloud](https://github.com/gophercloud/gophercloud) to handle authentication, so to use Schwift, you have to first build a `gophercloud.ServiceClient` and then pass that to `gopherschwift.Wrap()` to get a handle on the Swift account. + +For example, to connect to Swift using OpenStack Keystone authentication: + +```go +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack" + "github.com/majewsky/schwift/gopherschwift" +) + +authOptions, err := openstack.AuthOptionsFromEnv() +provider, err := openstack.AuthenticatedClient(authOptions) +client, err := openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{}) + +account, err := gopherschwift.Wrap(client, nil) +``` + +To connect to Swift using Swift's built-in authentication: + +```go +import ( + "github.com/gophercloud/gophercloud/openstack" + "github.com/gophercloud/gophercloud/openstack/objectstore/v1/swauth" + "github.com/majewsky/schwift/gopherschwift" +) + +provider, err := openstack.NewClient("http://swift.example.com:8080") +client, err := swauth.NewObjectStorageV1(provider, swauth.AuthOpts { + User: "project:user", + Key: "password", +}) + +account, err := gopherschwift.Wrap(client, nil) +``` + +From this point, follow the [API documentation](https://godoc.org/github.com/majewsky/schwift) for what you can do with +the `schwift.Account` object. For example, to download an object's contents into a string: + +```go +text, err := account.Container("foo").Object("bar.txt").Download(nil).AsString() +``` + +## Why another Swift client library? + +The most popular Swift client library is [`ncw/swift`](https://github.com/ncw/swift). I have [used +it](https://github.com/docker/distribution/pull/2441) [extensively](https://github.com/sapcc/swift-http-import) and my +main gripe with it is that its API is mostly based on single functions. When your API is a function, you cannot easily +add further arguments to it without breaking backwards compatibility. Whenever someone wants to do something slightly +different, an entirely new function needs to be added. To witness, ncw/swift has five functions for listing objects, +four functions for downloading objects, and three functions for uploading objects. (And that's without considering the +separate API for large objects.) And still, when you try to do something that's not one of the 10 most common things, +you're going to run into dead ends where the API does not allow you do specify that one URL parameter that you need. +Like that one day [when I filed five issues in a row because every function in the API that I tried turned out to be +missing something](https://github.com/ncw/swift/issues?utf8=%E2%9C%93&q=is%3Aissue+author%3Amajewsky+created%3A2017-11). + +Schwift improves on ncw/swift by: + +- allowing the user to set arbitrary headers and URL parameters in every request method, +- including a pointer to `RequestOpts` in every request method, which can later be extended with new members without + breaking backwards compatibility, and +- providing a generic `Request.Do()` method as a last resort for users who need to do a request that absolutely cannot + be made with the existing request methods. + +### What about Gophercloud? + +Schwift uses Gophercloud for authentication. That solves one problem that ncw/swift has, namely that you cannot +use the Keystone token that ncw/swift fetches for talking to other OpenStack services. + +But besides the auth code, Schwift avoids all other parts of Gophercloud. Gophercloud, like many other OpenStack client +libraries, is modeled frankly around the "JSON-in, JSON-out" request-response-based design that all OpenStack APIs +share. All of them, except for Swift. A lot of the infrastructure that Gophercloud provides is not suited for Swift, +mostly on account of it not using JSON bodies anywhere. + +Furthermore, the API of Gophercloud is modeled around individual requests and responses, which means that there will +probably never be support for advanced features like large objects unless you're willing to do all the footwork +yourself. + +Schwift improves on Gophercloud by providing a object-oriented API that respects and embraces Swift's domain model and +API design. diff --git a/vendor/github.com/majewsky/schwift/v2/account.go b/vendor/github.com/majewsky/schwift/v2/account.go new file mode 100644 index 0000000..cfc5b31 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/account.go @@ -0,0 +1,230 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "regexp" + "sync" +) + +// Account represents a Swift account. Instances are usually obtained by +// connecting to a backend (see package-level documentation), or by traversing +// upwards from a container with Container.Account(). +type Account struct { + backend Backend + // URL parts + baseURL string + name string + // cache + headers *AccountHeaders + caps *Capabilities + capsMutex sync.Mutex +} + +// IsEqualTo returns true if both Account instances refer to the same account. +func (a *Account) IsEqualTo(other *Account) bool { + return other.baseURL == a.baseURL && other.name == a.name +} + +var endpointURLRegexp = regexp.MustCompile(`^(.*/)v1/(.*)/$`) + +// InitializeAccount takes something that implements the Backend interface, and +// returns the Account instance corresponding to the account/project that this +// backend is connected to. +func InitializeAccount(backend Backend) (*Account, error) { + match := endpointURLRegexp.FindStringSubmatch(backend.EndpointURL()) + if match == nil { + return nil, fmt.Errorf(`schwift.InitializeAccount(): invalid Swift endpoint URL: cannot find "/v1/" in %q`, backend.EndpointURL()) + } + return &Account{ + backend: backend, + baseURL: match[1], + name: match[2], + }, nil +} + +// SwitchAccount returns a handle to a different account on the same server. Note +// that you need reseller permissions to access accounts other than that where +// you originally authenticated. This method does not check whether the account +// actually exists. +// +// The account name is usually the Keystone project ID with an additional "AUTH_" +// prefix. +func (a *Account) SwitchAccount(accountName string) *Account { + newEndpointURL := a.baseURL + "v1/" + accountName + "/" + return &Account{ + backend: a.backend.Clone(newEndpointURL), + baseURL: a.baseURL, + name: accountName, + } +} + +// Name returns the name of the account (usually the prefix "AUTH_" followed by +// the Keystone project ID). +func (a *Account) Name() string { + return a.name +} + +// Backend returns the backend which is used to make requests against this +// account. +func (a *Account) Backend() Backend { + return a.backend +} + +// Headers returns the AccountHeaders for this account. If the AccountHeaders +// has not been cached yet, a HEAD request is issued on the account. +// +// This operation fails with http.StatusNotFound if the account does not exist. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (a *Account) Headers(ctx context.Context) (AccountHeaders, error) { + if a.headers != nil { + return *a.headers, nil + } + + resp, err := Request{ + Method: "HEAD", + ExpectStatusCodes: []int{204}, + }.Do(ctx, a.backend) + if err != nil { + return AccountHeaders{}, err + } + defer resp.Body.Close() + + headers := AccountHeaders{headersFromHTTP(resp.Header)} + err = headers.Validate() + if err != nil { + return headers, err + } + a.headers = &headers + return *a.headers, nil +} + +// Invalidate clears the internal cache of this Account instance. The next call +// to Headers() on this instance will issue a HEAD request on the account. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (a *Account) Invalidate() { + a.headers = nil +} + +// Update updates the account using a POST request. The headers in the headers +// attribute take precedence over those in opts.Headers. +// +// A successful POST request implies Invalidate() since it may change metadata. +func (a *Account) Update(ctx context.Context, headers AccountHeaders, opts *RequestOptions) error { + resp, err := Request{ + Method: "POST", + Options: cloneRequestOptions(opts, headers.Headers), + ExpectStatusCodes: []int{204}, + }.Do(ctx, a.backend) + if err == nil { + a.Invalidate() + resp.Body.Close() + } + return err +} + +// Create creates the account using a PUT request. This operation is only +// available to reseller admins, not to regular users. +// +// A successful PUT request implies Invalidate() since it may change metadata. +func (a *Account) Create(ctx context.Context, opts *RequestOptions) error { + resp, err := Request{ + Method: "PUT", + Options: opts, + ExpectStatusCodes: []int{201, 202}, + DrainResponseBody: true, + }.Do(ctx, a.backend) + if err == nil { + a.Invalidate() + resp.Body.Close() + } + return err +} + +// Containers returns a ContainerIterator that lists the containers in this +// account. The most common use case is: +// +// containers, err := account.Containers().Collect() +// +// You can extend this by configuring the iterator before collecting the results: +// +// iter := account.Containers() +// iter.Prefix = "test-" +// containers, err := iter.Collect() +// +// Or you can use a different iteration method: +// +// err := account.Containers().ForeachDetailed(func (ci ContainerInfo) error { +// log.Printf("container %s contains %d objects!\n", +// ci.Container.Name(), ci.ObjectCount) +// }) +func (a *Account) Containers() *ContainerIterator { + return &ContainerIterator{Account: a} +} + +// Capabilities queries the GET /info endpoint of the Swift server providing +// this account. Capabilities are cached, so the GET request will only be sent +// once during the first call to this method. +func (a *Account) Capabilities(ctx context.Context) (Capabilities, error) { + a.capsMutex.Lock() + defer a.capsMutex.Unlock() + + if a.caps != nil { + return *a.caps, nil + } + + buf, err := a.RawCapabilities(ctx) + if err != nil { + return Capabilities{}, err + } + + var caps Capabilities + err = json.Unmarshal(buf, &caps) + if err != nil { + return caps, err + } + + a.caps = &caps + return caps, nil +} + +// RawCapabilities queries the GET /info endpoint of the Swift server providing +// this account, and returns the response body. Unlike Account.Capabilities, +// this method does not employ any caching. +func (a *Account) RawCapabilities(ctx context.Context) ([]byte, error) { + // This method is the only one in Schwift that bypasses struct Request since + // the request URL is not below the endpoint URL. + req, err := http.NewRequestWithContext(ctx, http.MethodGet, a.baseURL+"info", http.NoBody) + if err != nil { + return nil, err + } + resp, err := a.backend.Do(req) + if err != nil { + return nil, err + } + return collectResponseBody(resp) +} diff --git a/vendor/github.com/majewsky/schwift/v2/backend.go b/vendor/github.com/majewsky/schwift/v2/backend.go new file mode 100644 index 0000000..e45822f --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/backend.go @@ -0,0 +1,48 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "net/http" +) + +// Backend is the interface between Schwift and the libraries providing +// authentication for it. Each instance of Backend represents a particular Swift +// account. +type Backend interface { + // EndpointURL returns the endpoint URL from the Keystone catalog for the + // Swift account that this backend operates on. It should look like + // `http://domain.tld/v1/AUTH_projectid/`. The trailing slash is required. + EndpointURL() string + // Clone returns a deep clone of this backend with the endpoint URL changed to + // the given URL. This is used by Account.SwitchAccount(). + Clone(newEndpointURL string) Backend + // Do executes the given HTTP request after adding to it the X-Auth-Token + // header containing the backend's current Keystone (or Swift auth) token. If + // the status code returned is 401, it shall attempt to acquire a new auth + // token and restart the request with the new token. + // + // If the user has not supplied their own User-Agent string to the backend, + // the backend should use the schwift.DefaultUserAgent constant instead. + Do(req *http.Request) (*http.Response, error) +} + +// DefaultUserAgent is the User-Agent string that Backend implementations should +// use if the user does not provide their own User-Agent string. +const DefaultUserAgent = "schwift/" + Version diff --git a/vendor/github.com/majewsky/schwift/v2/bulk.go b/vendor/github.com/majewsky/schwift/v2/bulk.go new file mode 100644 index 0000000..0669d31 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/bulk.go @@ -0,0 +1,338 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/majewsky/schwift/v2/capabilities" + "github.com/majewsky/schwift/v2/internal/errext" +) + +// BulkUploadFormat enumerates possible archive formats for Container.BulkUpload(). +type BulkUploadFormat string + +const ( + // BulkUploadTar is a plain tar archive. + BulkUploadTar BulkUploadFormat = "tar" + // BulkUploadTarGzip is a GZip-compressed tar archive. + BulkUploadTarGzip BulkUploadFormat = "tar.gz" + // BulkUploadTarBzip2 is a BZip2-compressed tar archive. + BulkUploadTarBzip2 BulkUploadFormat = "tar.bz2" +) + +// BulkUpload extracts an archive (which may contain multiple files) into a +// Swift account. The path of each file in the archive is appended to the +// uploadPath to form the FullName() of the resulting Object. +// +// For example, when uploading an archive that contains the file "a/b/c": +// +// //This uploads the file into the container "a" as object "b/c". +// account.BulkUpload("", format, contents, nil) +// //This uploads the file into the container "foo" as object "a/b/c". +// account.BulkUpload("foo", format, contents, nil) +// //This uploads the file into the container "foo" as object "bar/baz/a/b/c". +// account.BulkUpload("foo/bar/baz", format, contents, nil) +// +// The first return value indicates the number of files that have been created +// on the server side. This may be lower than the number of files in the archive +// if some files could not be saved individually (e.g. because a quota was +// exceeded in the middle of the archive extraction). +// +// If not nil, the error return value is *usually* an instance of BulkError. +// +// This operation returns (0, ErrNotSupported) if the server does not support +// bulk-uploading. +func (a *Account) BulkUpload(ctx context.Context, uploadPath string, format BulkUploadFormat, contents io.Reader, opts *RequestOptions) (int, error) { + caps, err := a.Capabilities(ctx) + if err != nil { + return 0, err + } + if caps.BulkUpload == nil { + return 0, ErrNotSupported + } + + req := Request{ + Method: "PUT", + Body: contents, + Options: cloneRequestOptions(opts, nil), + ExpectStatusCodes: []int{200}, + } + req.Options.Headers.Set("Accept", "application/json") + req.Options.Values.Set("extract-archive", string(format)) + + fields := strings.SplitN(strings.Trim(uploadPath, "/"), "/", 2) + req.ContainerName = fields[0] + if len(fields) == 2 { + req.ObjectName = fields[1] + } + + resp, err := req.Do(ctx, a.backend) //nolint:bodyclose // parseBulkResponse does the close + if err != nil { + return 0, err + } + + result, err := parseBulkResponse(resp.Body) + return result.NumberFilesCreated, err +} + +func parseResponseStatus(status string) (int, error) { + // `status` looks like "201 Created" + fields := strings.SplitN(status, " ", 2) + return strconv.Atoi(fields[0]) +} + +func makeBulkObjectError(fullName string, statusCode int) BulkObjectError { + nameFields := strings.SplitN(fullName, "/", 2) + for len(nameFields) < 2 { + nameFields = append(nameFields, "") + } + return BulkObjectError{ + ContainerName: nameFields[0], + ObjectName: nameFields[1], + StatusCode: statusCode, + } +} + +// BulkDelete deletes a large number of objects (and containers) at once. +// Containers are queued at the end of the deletion, so a container can be +// deleted in the same call in which all objects in it are deleted. +// +// For example, to delete all objects in a container: +// +// var container *schwift.Container +// +// objects, err := container.Objects().Collect() +// numDeleted, numNotFound, err := container.Account().BulkDelete(objects, nil, nil) +// +// To also delete the container: +// +// var container *schwift.Container +// +// objects, err := container.Objects().Collect() +// numDeleted, numNotFound, err := container.Account().BulkDelete( +// objects, []*schwift.Container{container}, nil) +// +// If the server does not support bulk-deletion, this function falls back to +// deleting each object and container individually, and aggregates the result. +// +// If not nil, the error return value is *usually* an instance of BulkError. +// +// The objects may be located in multiple containers, but they and the +// containers must all be located in the given account. (Otherwise, +// ErrAccountMismatch is returned.) +func (a *Account) BulkDelete(ctx context.Context, objects []*Object, containers []*Container, opts *RequestOptions) (numDeleted, numNotFound int, deleteError error) { + // validate that all given objects are in this account + for _, obj := range objects { + if !a.IsEqualTo(obj.Container().Account()) { + return 0, 0, ErrAccountMismatch + } + } + for _, container := range containers { + if !a.IsEqualTo(container.Account()) { + return 0, 0, ErrAccountMismatch + } + } + + // check capabilities to choose deletion method + caps, err := a.Capabilities(ctx) + if err != nil { + return 0, 0, err + } + if caps.BulkDelete == nil || !capabilities.AllowBulkDelete { + return a.bulkDeleteSingle(ctx, objects, containers, opts) + } + chunkSize := int(caps.BulkDelete.MaximumDeletesPerRequest) + + // collect names of things to delete into one big list + var names []string + for _, object := range objects { + object.Invalidate() // deletion must invalidate objects! + names = append(names, fmt.Sprintf("/%s/%s", + url.PathEscape(object.Container().Name()), + url.PathEscape(object.Name()), + )) + } + for _, container := range containers { + container.Invalidate() // deletion must invalidate objects! + names = append(names, "/"+url.PathEscape(container.Name())) + } + + // split list into chunks according to maximum allowed + // chunk size; aggregate results + for len(names) > 0 { + // this condition holds only in the final iteration + if chunkSize > len(names) { + chunkSize = len(names) + } + chunk := names[0:chunkSize] + names = names[chunkSize:] + + numDeletedNow, numNotFoundNow, err := a.bulkDelete(ctx, chunk, opts) + numDeleted += numDeletedNow + numNotFound += numNotFoundNow + if err != nil { + return numDeleted, numNotFound, err + } + } + + return numDeleted, numNotFound, nil +} + +// Implementation of BulkDelete() for servers that *do not* support bulk +// deletion. +func (a *Account) bulkDeleteSingle(ctx context.Context, objects []*Object, containers []*Container, opts *RequestOptions) (numDeleted, numNotFound int, err error) { + var errs []BulkObjectError + + handleSingleError := func(containerName, objectName string, err error) error { + if err == nil { + numDeleted++ + return nil + } + if Is(err, http.StatusNotFound) { + numNotFound++ + return nil + } + if statusErr, ok := errext.As[UnexpectedStatusCodeError](err); ok { + errs = append(errs, BulkObjectError{ + ContainerName: containerName, + ObjectName: objectName, + StatusCode: statusErr.ActualResponse.StatusCode, + }) + return nil + } + // unexpected error type -> stop early + return err + } + + for _, obj := range objects { + err := obj.Delete(ctx, nil, opts) // this implies Invalidate() + err = handleSingleError(obj.Container().Name(), obj.Name(), err) + if err != nil { + return numDeleted, numNotFound, err + } + } + + for _, container := range containers { + err := container.Delete(ctx, opts) // this implies Invalidate() + err = handleSingleError(container.Name(), "", err) + if err != nil { + return numDeleted, numNotFound, err + } + } + + if len(errs) == 0 { + return numDeleted, numNotFound, nil + } + return numDeleted, numNotFound, BulkError{ + StatusCode: errs[0].StatusCode, + OverallError: http.StatusText(errs[0].StatusCode), + ObjectErrors: errs, + } +} + +// Implementation of BulkDelete() for servers that *do* support bulk deletion. +// This function is called *after* chunking, so `len(names) <= +// account.Capabilities.BulkDelete.MaximumDeletesPerRequest`. +func (a *Account) bulkDelete(ctx context.Context, names []string, opts *RequestOptions) (numDeleted, numNotFound int, err error) { + req := Request{ + Method: "DELETE", + Body: strings.NewReader(strings.Join(names, "\n") + "\n"), + Options: cloneRequestOptions(opts, nil), + ExpectStatusCodes: []int{200}, + } + req.Options.Headers.Set("Accept", "application/json") + req.Options.Headers.Set("Content-Type", "text/plain") + req.Options.Values.Set("bulk-delete", "true") + resp, err := req.Do(ctx, a.backend) //nolint:bodyclose // parseBulkResponse does the close + if err != nil { + return 0, 0, err + } + + result, err := parseBulkResponse(resp.Body) + return result.NumberDeleted, result.NumberNotFound, err +} + +type bulkResponse struct { + // ResponseStatus indicates the overall result as a HTTP status string, e.g. + // "201 Created" or "500 Internal Error". + ResponseStatus string `json:"Response Status"` + // ResponseBody contains an overall error message for errors that are not + // related to a single file in the archive (e.g. "invalid tar file" or "Max + // delete failures exceeded"). + ResponseBody string `json:"Response Body"` + // Errors contains error messages for individual files. Each entry is a + // []string with 2 elements, the object's fullName and the HTTP status for + // this file's upload (e.g. "412 Precondition Failed"). + Errors [][]string `json:"Errors"` + // NumberFilesCreated is included in the BulkUpload result only. + NumberFilesCreated int `json:"Number Files Created"` + // NumberDeleted is included in the BulkDelete result only. + NumberDeleted int `json:"Number Deleted"` + // NumberNotFound is included in the BulkDelete result only. + NumberNotFound int `json:"Number Not Found"` +} + +func parseBulkResponse(body io.ReadCloser) (bulkResponse, error) { + var resp bulkResponse + err := json.NewDecoder(body).Decode(&resp) + closeErr := body.Close() + if err == nil { + err = closeErr + } + if err != nil { + return resp, err + } + + // parse `resp` into type BulkError + bulkErr := BulkError{ + OverallError: resp.ResponseBody, + } + bulkErr.StatusCode, err = parseResponseStatus(resp.ResponseStatus) + if err != nil { + return resp, err + } + for _, suberr := range resp.Errors { + if len(suberr) != 2 { + continue // wtf + } + statusCode, err := parseResponseStatus(suberr[1]) + if err != nil { + return resp, err + } + bulkErr.ObjectErrors = append(bulkErr.ObjectErrors, + makeBulkObjectError(suberr[0], statusCode), + ) + } + + // is BulkError really an error? + if len(bulkErr.ObjectErrors) == 0 && bulkErr.OverallError == "" && bulkErr.StatusCode >= 200 && bulkErr.StatusCode < 300 { + return resp, nil + } + return resp, bulkErr + //NOTE: `resp` is passed back to the caller to read the counters + // (resp.NumberFilesCreated etc.) +} diff --git a/vendor/github.com/majewsky/schwift/v2/capabilities.go b/vendor/github.com/majewsky/schwift/v2/capabilities.go new file mode 100644 index 0000000..26a3532 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/capabilities.go @@ -0,0 +1,91 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +// Capabilities describes a subset of the capabilities that Swift can report +// under its /info endpoint. This struct is obtained through the +// Account.Capabilities() method. To query capabilities not represented in this +// struct, see Account.QueryCapabilities(). +// +// All direct members of struct Capabilities, except for "Swift", are pointers. +// If any of these is nil, it indicates that the middleware corresponding to +// that field is not available on this server. +type Capabilities struct { + BulkDelete *struct { + MaximumDeletesPerRequest uint `json:"max_deletes_per_request"` + MaximumFailedDeletes uint `json:"max_failed_deletes"` + } `json:"bulk_delete"` + BulkUpload *struct { + MaximumContainersPerExtraction uint `json:"max_containers_per_extraction"` + MaximumFailedExtractions uint `json:"max_failed_extractions"` + } `json:"bulk_upload"` + StaticLargeObject *struct { + MaximumManifestSegments uint `json:"max_manifest_segments"` + MaximumManifestSize uint `json:"max_manifest_size"` + MinimumSegmentSize uint `json:"min_segment_size"` + } `json:"slo"` + Swift struct { + AccountAutocreate bool `json:"account_autocreate"` + AccountListingLimit uint `json:"account_listing_limit"` + AllowAccountManagement bool `json:"allow_account_management"` + ContainerListingLimit uint `json:"container_listing_limit"` + ExtraHeaderCount uint `json:"extra_header_count"` + MaximumAccountNameLength uint `json:"max_account_name_length"` + MaximumContainerNameLength uint `json:"max_container_name_length"` + MaximumFileSize uint `json:"max_file_size"` + MaximumHeaderSize uint `json:"max_header_size"` + MaximumMetaCount uint `json:"max_meta_count"` + MaximumMetaNameLength uint `json:"max_meta_name_length"` + MaximumMetaOverallSize uint `json:"max_meta_overall_size"` + MaximumMetaValueLength uint `json:"max_meta_value_length"` + MaximumObjectNameLength uint `json:"max_object_name_length"` + Policies []StoragePolicySpec `json:"policies"` + StrictCORSMode bool `json:"strict_cors_mode"` + Version string `json:"version"` + } `json:"swift"` + Swift3 *struct { + AllowMultipartUploads bool `json:"allow_multipart_uploads"` + MaximumBucketListing uint `json:"max_bucket_listing"` + MaximumMultiDeleteObjects uint `json:"max_multi_delete_objects"` + MaximumPartsListing uint `json:"max_parts_listing"` + MaximumUploadPartNumber uint `json:"max_upload_part_num"` + Version string `json:"version"` + } `json:"swift3"` + Symlink *struct { + MaximumLoopCount uint `json:"symloop_max"` + } `json:"symlink"` + TempAuth *struct { + AccountACLs bool `json:"account_acls"` + } `json:"tempauth"` + TempURL *struct { + AllowedDigests []string `json:"allowed_digests"` + IncomingAllowHeaders []string `json:"incoming_allow_headers"` + IncomingRemoveHeaders []string `json:"incoming_remove_headers"` + Methods []string `json:"methods"` + OutgoingAllowHeaders []string `json:"outgoing_allow_headers"` + OutgoingRemoveHeaders []string `json:"outgoing_remove_headers"` + } `json:"tempurl"` +} + +// StoragePolicySpec is a subtype that appears in struct Capabilities. +type StoragePolicySpec struct { + Name string `json:"name"` + Aliases string `json:"aliases"` + Default bool `json:"default"` +} diff --git a/vendor/github.com/majewsky/schwift/v2/capabilities/package.go b/vendor/github.com/majewsky/schwift/v2/capabilities/package.go new file mode 100644 index 0000000..9ea2921 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/capabilities/package.go @@ -0,0 +1,29 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +// Package capabilities contains feature switches that Schwift's unit tests can +// set to exercise certain fallback code paths in Schwift that they could not +// trigger otherwise. +// +// THIS IS A PRIVATE MODULE. It is not covered by any forwards or backwards +// compatibility and may be gone at a moment's notice. +package capabilities + +// AllowBulkDelete can be set to false to force Schwift to act as if the server +// does not support bulk deletion. +var AllowBulkDelete = true diff --git a/vendor/github.com/majewsky/schwift/v2/container.go b/vendor/github.com/majewsky/schwift/v2/container.go new file mode 100644 index 0000000..7ddd415 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/container.go @@ -0,0 +1,225 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "context" + "net/http" +) + +// Container represents a Swift container. Instances are usually obtained by +// traversing downwards from an account with Account.Container() or +// Account.Containers(), or upwards from an object with Object.Container(). +type Container struct { + a *Account + name string + // cache + headers *ContainerHeaders +} + +// IsEqualTo returns true if both Container instances refer to the same container. +func (c *Container) IsEqualTo(other *Container) bool { + return other.name == c.name && other.a.IsEqualTo(c.a) +} + +// Container returns a handle to the container with the given name within this +// account. This function does not issue any HTTP requests, and therefore cannot +// ensure that the container exists. Use the Exists() function to check for the +// container's existence, or chain this function with the EnsureExists() +// function like so: +// +// container, err := account.Container("documents").EnsureExists() +func (a *Account) Container(name string) *Container { + return &Container{a: a, name: name} +} + +// Account returns a handle to the account this container is stored in. +func (c *Container) Account() *Account { + return c.a +} + +// Name returns the container name. +func (c *Container) Name() string { + return c.name +} + +// Exists checks if this container exists, potentially by issuing a HEAD request +// if no Headers() have been cached yet. +func (c *Container) Exists(ctx context.Context) (bool, error) { + _, err := c.Headers(ctx) + if Is(err, http.StatusNotFound) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +// Headers returns the ContainerHeaders for this container. If the ContainerHeaders +// has not been cached yet, a HEAD request is issued on the container. +// +// This operation fails with http.StatusNotFound if the container does not exist. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (c *Container) Headers(ctx context.Context) (ContainerHeaders, error) { + if c.headers != nil { + return *c.headers, nil + } + + resp, err := Request{ + Method: "HEAD", + ContainerName: c.name, + ExpectStatusCodes: []int{204}, + }.Do(ctx, c.a.backend) + if err != nil { + return ContainerHeaders{}, err + } + defer resp.Body.Close() + + headers := ContainerHeaders{headersFromHTTP(resp.Header)} + err = headers.Validate() + if err != nil { + return headers, err + } + c.headers = &headers + return *c.headers, nil +} + +// Update updates the container using a POST request. To add URL parameters, pass +// a non-nil *RequestOptions. +// +// If you are not sure whether the container exists, use Create() instead. +// +// A successful POST request implies Invalidate() since it may change metadata. +func (c *Container) Update(ctx context.Context, headers ContainerHeaders, opts *RequestOptions) error { + resp, err := Request{ + Method: "POST", + ContainerName: c.name, + Options: cloneRequestOptions(opts, headers.Headers), + ExpectStatusCodes: []int{204}, + }.Do(ctx, c.a.backend) + if err == nil { + c.Invalidate() + resp.Body.Close() + } + return err +} + +// Create creates the container using a PUT request. To add URL parameters, pass +// a non-nil *RequestOptions. +// +// This function can be used regardless of whether the container exists or not. +// +// A successful PUT request implies Invalidate() since it may change metadata. +func (c *Container) Create(ctx context.Context, opts *RequestOptions) error { + resp, err := Request{ + Method: "PUT", + ContainerName: c.name, + Options: opts, + ExpectStatusCodes: []int{201, 202}, + DrainResponseBody: true, + }.Do(ctx, c.a.backend) + if err == nil { + c.Invalidate() + resp.Body.Close() + } + return err +} + +// Delete deletes the container using a DELETE request. To add URL parameters, +// pass a non-nil *RequestOptions. +// +// This operation fails with http.StatusConflict if the container is not empty. +// +// This operation fails with http.StatusNotFound if the container does not exist. +// +// A successful DELETE request implies Invalidate(). +func (c *Container) Delete(ctx context.Context, opts *RequestOptions) error { + resp, err := Request{ + Method: "DELETE", + ContainerName: c.name, + Options: opts, + ExpectStatusCodes: []int{204}, + }.Do(ctx, c.a.backend) + if err == nil { + c.Invalidate() + resp.Body.Close() + } + return err +} + +// Invalidate clears the internal cache of this Container instance. The next call +// to Headers() on this instance will issue a HEAD request on the container. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (c *Container) Invalidate() { + c.headers = nil +} + +// EnsureExists issues a PUT request on this container. +// If the container does not exist yet, it will be created by this call. +// If the container exists already, this call does not change it. +// This function returns the same container again, because its intended use is +// with freshly constructed Container instances like so: +// +// container, err := account.Container("documents").EnsureExists() +func (c *Container) EnsureExists(ctx context.Context) (*Container, error) { + resp, err := Request{ + Method: "PUT", + ContainerName: c.name, + ExpectStatusCodes: []int{201, 202}, + DrainResponseBody: true, + }.Do(ctx, c.a.backend) + if err == nil { + resp.Body.Close() + } + return c, err +} + +// Objects returns an ObjectIterator that lists the objects in this +// container. The most common use case is: +// +// objects, err := container.Objects().Collect() +// +// You can extend this by configuring the iterator before collecting the results: +// +// iter := container.Objects() +// iter.Prefix = "test-" +// objects, err := iter.Collect() +// +// Or you can use a different iteration method: +// +// err := container.Objects().ForeachDetailed(func (info ObjectInfo) error { +// log.Printf("object %s is %d bytes large!\n", +// info.Object.Name(), info.SizeBytes) +// }) +func (c *Container) Objects() *ObjectIterator { + return &ObjectIterator{Container: c} +} + +// URL returns the canonical URL for this container on the server. This is +// particularly useful when the ReadACL on the account or container is set to +// allow anonymous read access. +func (c *Container) URL() (string, error) { + return Request{ + ContainerName: c.name, + }.URL(c.a.backend, nil) +} diff --git a/vendor/github.com/majewsky/schwift/v2/container_iterator.go b/vendor/github.com/majewsky/schwift/v2/container_iterator.go new file mode 100644 index 0000000..9ff9be9 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/container_iterator.go @@ -0,0 +1,208 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "context" + "fmt" + "time" +) + +// ContainerInfo is a result type returned by ContainerIterator for detailed +// container listings. The metadata in this type is a subset of Container.Headers(), +// but since it is returned as part of the detailed container listing, it can be +// obtained without making additional HEAD requests on the container(s). +type ContainerInfo struct { + Container *Container + ObjectCount uint64 + BytesUsed uint64 + LastModified time.Time +} + +// ContainerIterator iterates over the accounts in a container. It is typically +// constructed with the Account.Containers() method. For example: +// +// //either this... +// iter := account.Containers() +// iter.Prefix = "test-" +// containers, err := iter.Collect() +// +// //...or this +// containers, err := schwift.ContainerIterator{ +// Account: account, +// Prefix: "test-", +// }.Collect() +// +// When listing containers via a GET request on the account, you can choose to +// receive container names only (via the methods without the "Detailed" suffix), +// or container names plus some basic metadata fields (via the methods with the +// "Detailed" suffix). See struct ContainerInfo for which metadata is returned. +// +// To obtain any other metadata, you can call Container.Headers() on the result +// container, but this will issue a separate HEAD request for each container. +// +// Use the "Detailed" methods only when you use the extra metadata in struct +// ContainerInfo; detailed GET requests are more expensive than simple ones that +// return only container names. +type ContainerIterator struct { + Account *Account + // When Prefix is set, only containers whose name starts with this string are + // returned. + Prefix string + // Options may contain additional headers and query parameters for the GET request. + Options *RequestOptions + + base *iteratorBase +} + +func (i *ContainerIterator) getBase() *iteratorBase { + if i.base == nil { + i.base = &iteratorBase{i: i} + } + return i.base +} + +// NextPage queries Swift for the next page of container names. If limit is +// >= 0, not more than that many container names will be returned at once. Note +// that the server also has a limit for how many containers to list in one +// request; the lower limit wins. +// +// The end of the container listing is reached when an empty list is returned. +// +// This method offers maximal flexibility, but most users will prefer the +// simpler interfaces offered by Collect() and Foreach(). +func (i *ContainerIterator) NextPage(ctx context.Context, limit int) ([]*Container, error) { + names, err := i.getBase().nextPage(ctx, limit) + if err != nil { + return nil, err + } + + result := make([]*Container, len(names)) + for idx, name := range names { + result[idx] = i.Account.Container(name) + } + return result, nil +} + +// NextPageDetailed is like NextPage, but includes basic metadata. +func (i *ContainerIterator) NextPageDetailed(ctx context.Context, limit int) ([]ContainerInfo, error) { + b := i.getBase() + + var document []struct { + BytesUsed uint64 `json:"bytes"` + ObjectCount uint64 `json:"count"` + LastModifiedStr string `json:"last_modified"` + Name string `json:"name"` + } + err := b.nextPageDetailed(ctx, limit, &document) + if err != nil { + return nil, err + } + if len(document) == 0 { + b.setMarker("") // indicate EOF to iteratorBase + return nil, nil + } + + result := make([]ContainerInfo, len(document)) + for idx, data := range document { + result[idx].Container = i.Account.Container(data.Name) + result[idx].BytesUsed = data.BytesUsed + result[idx].ObjectCount = data.ObjectCount + result[idx].LastModified, err = time.Parse(time.RFC3339Nano, data.LastModifiedStr+"Z") + if err != nil { + // this error is sufficiently obscure that we don't need to expose a type for it + return nil, fmt.Errorf("bad field containers[%d].last_modified: %s", idx, err.Error()) + } + } + + b.setMarker(result[len(result)-1].Container.Name()) + return result, nil +} + +// Foreach lists the container names matching this iterator and calls the +// callback once for every container. Iteration is aborted when a GET request fails, +// or when the callback returns a non-nil error. +func (i *ContainerIterator) Foreach(ctx context.Context, callback func(*Container) error) error { + for { + containers, err := i.NextPage(ctx, -1) + if err != nil { + return err + } + if len(containers) == 0 { + return nil // EOF + } + for _, c := range containers { + err := callback(c) + if err != nil { + return err + } + } + } +} + +// ForeachDetailed is like Foreach, but includes basic metadata. +func (i *ContainerIterator) ForeachDetailed(ctx context.Context, callback func(ContainerInfo) error) error { + for { + infos, err := i.NextPageDetailed(ctx, -1) + if err != nil { + return err + } + if len(infos) == 0 { + return nil // EOF + } + for _, ci := range infos { + err := callback(ci) + if err != nil { + return err + } + } + } +} + +// Collect lists all container names matching this iterator. For large sets of +// containers that cannot be retrieved at once, Collect handles paging behind +// the scenes. The return value is always the complete set of containers. +func (i *ContainerIterator) Collect(ctx context.Context) ([]*Container, error) { + var result []*Container + for { + containers, err := i.NextPage(ctx, -1) + if err != nil { + return nil, err + } + if len(containers) == 0 { + return result, nil // EOF + } + result = append(result, containers...) + } +} + +// CollectDetailed is like Collect, but includes basic metadata. +func (i *ContainerIterator) CollectDetailed(ctx context.Context) ([]ContainerInfo, error) { + var result []ContainerInfo + for { + infos, err := i.NextPageDetailed(ctx, -1) + if err != nil { + return nil, err + } + if len(infos) == 0 { + return result, nil // EOF + } + result = append(result, infos...) + } +} diff --git a/vendor/github.com/majewsky/schwift/v2/doc.go b/vendor/github.com/majewsky/schwift/v2/doc.go new file mode 100644 index 0000000..7b49e04 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/doc.go @@ -0,0 +1,110 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +/* +Package schwift is a client library for OpenStack Swift +(https://github.com/openstack/swift, https://openstack.org). + +# Authentication with Gophercloud + +Schwift does not implement authentication (neither Keystone nor Swift v1), but +can be plugged into any library that does. The most common choice is +Gophercloud (https://github.com/gophercloud/gophercloud/v2). + +When using Gophercloud, you usually start by obtaining a +gophercloud.ServiceClient for Swift like so: + + import ( + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/utils/v2/openstack/clientconfig" + ) + + //option 1: build a gophercloud.AuthOptions instance yourself + provider, err := openstack.AuthenticatedClient(authOptions) + client, err := openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{}) + + //option 2: have Gophercloud read the standard OS_* environment variables + provider, err := clientConfig.AuthenticatedClient(nil) + client, err := openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{}) + + //option 3: if you're using Swift's builtin authentication instead of Keystone + provider, err := openstack.NewClient("http://swift.example.com:8080") + client, err := swauth.NewObjectStorageV1(provider, swauth.AuthOpts { + User: "project:user", + Key: "password", + }) + +Then, in all these cases, you use gopherschwift to convert the +gophercloud.ServiceClient into a schwift.Account instance, from which point you +have access to all of schwift's API: + + import "github.com/majewsky/schwift/v2/gopherschwift" + + account, err := gopherschwift.Wrap(client) + +For example, to download an object's contents into a string: + + text, err := account.Container("foo").Object("bar.txt").Download(nil).AsString() + +# Authentication with a different OpenStack library + +If you use a different Go library to handle Keystone/Swift authentication, take +the client object that it provides and wrap it into something that implements +the schwift.Backend interface. Then use schwift.InitializeAccount() to obtain a +schwift.Account. + +# Caching + +When a GET or HEAD request is sent by an Account, Container or Object instance, +the headers associated with that thing will be stored in that instance and not +retrieved again. + + obj := account.Container("foo").Object("bar") + + hdr, err := obj.Headers() //sends HTTP request "HEAD /foo/bar" + ... + hdr, err = obj.Headers() //returns cached values immediately + +If this behavior is not desired, the Invalidate() method can be used to clear +caches on any Account, Container or Object instance. Some methods that modify +the instance on the server call Invalidate() automatically, e.g. Object.Upload(), +Update() or Delete(). This will be indicated in the method's documentation. + +# Error handling + +When a method on an Account, Container or Object instance makes a HTTP request +to Swift and Swift returns an unexpected status code, a +schwift.UnexpectedStatusCodeError will be returned. Schwift provides the +convenience function Is() to check the status code of these errors to detect +common failure situations: + + obj := account.Container("foo").Object("bar") + err := obj.Upload(bytes.NewReader(data), nil) + + if schwift.Is(err, http.StatusRequestEntityTooLarge) { + log.Print("quota exceeded for container foo!") + } else if err != nil { + log.Fatal("unexpected error: " + err.Error()) + } + +The documentation for a method may indicate certain common error conditions +that can be detected this way by stating that "This method fails with +http.StatusXXX if ...". Because of the wide variety of failure modes in Swift, +this information is not guaranteed to be exhaustive. +*/ +package schwift diff --git a/vendor/github.com/majewsky/schwift/v2/download.go b/vendor/github.com/majewsky/schwift/v2/download.go new file mode 100644 index 0000000..8367f3d --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/download.go @@ -0,0 +1,80 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "io" +) + +// DownloadedObject is returned by Object.Download(). It wraps the io.ReadCloser +// from http.Response.Body with convenience methods for collecting the contents +// into a byte slice or string. +// +// var obj *swift.Object +// +// //Do NOT do this! +// reader, err := obj.Download(nil).AsReadCloser() +// bytes, err := io.ReadAll(reader) +// err := reader.Close() +// str := string(bytes) +// +// //Do this instead: +// str, err := obj.Download(nil).AsString() +// +// Since all methods on DownloadedObject are irreversible, the idiomatic way of +// using DownloadedObject is to call one of its members immediately, without +// storing the DownloadedObject instance in a variable first. +// +// var obj *swift.Object +// +// //Do NOT do this! +// downloaded := obj.Download(nil) +// reader, err := downloaded.AsReadCloser() +// +// //Do this instead: +// reader, err := obj.Download(nil).AsReadCloser() +type DownloadedObject struct { + r io.ReadCloser + err error +} + +// AsReadCloser returns an io.ReadCloser containing the contents of the +// downloaded object. +func (o DownloadedObject) AsReadCloser() (io.ReadCloser, error) { + return o.r, o.err +} + +// AsByteSlice collects the contents of this downloaded object into a byte slice. +func (o DownloadedObject) AsByteSlice() ([]byte, error) { + if o.err != nil { + return nil, o.err + } + slice, err := io.ReadAll(o.r) + closeErr := o.r.Close() + if err == nil { + err = closeErr + } + return slice, err +} + +// AsString collects the contents of this downloaded object into a string. +func (o DownloadedObject) AsString() (string, error) { + slice, err := o.AsByteSlice() + return string(slice), err +} diff --git a/vendor/github.com/majewsky/schwift/v2/errors.go b/vendor/github.com/majewsky/schwift/v2/errors.go new file mode 100644 index 0000000..25fdde0 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/errors.go @@ -0,0 +1,169 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "errors" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/majewsky/schwift/v2/internal/errext" +) + +var ( + // ErrChecksumMismatch is returned by Object.Upload() when the Etag in the + // server response does not match the uploaded data. + ErrChecksumMismatch = errors.New("Etag on uploaded object does not match MD5 checksum of uploaded data") + // ErrNoContainerName is returned by Request.Do() if ObjectName is given, but + // ContainerName is empty. + ErrNoContainerName = errors.New("missing container name") + // ErrMalformedContainerName is returned by Request.Do() if ContainerName + // contains slashes. + ErrMalformedContainerName = errors.New("container name may not contain slashes") + // ErrNotSupported is returned by bulk operations, large object operations, + // etc. if the server does not support the requested operation. + ErrNotSupported = errors.New("operation not supported by this Swift server") + // ErrAccountMismatch is returned by operations on an account that accept + // containers/objects as arguments, if some or all of the provided + // containers/objects are located in a different account. + ErrAccountMismatch = errors.New("some of the given objects are not in this account") + // ErrContainerMismatch is returned by operations on a container that accept + // objects as arguments, if some or all of the provided objects are located in + // a different container. + ErrContainerMismatch = errors.New("some of the given objects are not in this container") + // ErrNotLarge is returned by Object.AsLargeObject() if the object does not + // exist, or if it is not a large object composed out of segments. + ErrNotLarge = errors.New("not a large object") + // ErrSegmentInvalid is returned by LargeObject.AddSegment() if the segment + // provided is malformed or uses features not supported by the LargeObject's + // strategy. See documentation for LargeObject.AddSegment() for details. + ErrSegmentInvalid = errors.New("segment invalid or incompatible with large object strategy") +) + +// UnexpectedStatusCodeError is generated when a request to Swift does not yield +// a response with the expected successful status code. The actual status code +// can be checked with the Is() function; see documentation over there. +type UnexpectedStatusCodeError struct { + Method string // e.g. http.MethodGet + Target string // either "" or "$CONTAINER_NAME" or "$CONTAINER_NAME/$OBJECT_NAME" + ExpectedStatusCodes []int + ActualResponse *http.Response + ResponseBody []byte +} + +// Error implements the builtin/error interface. +func (e UnexpectedStatusCodeError) Error() string { + codeStrs := make([]string, len(e.ExpectedStatusCodes)) + for idx, code := range e.ExpectedStatusCodes { + codeStrs[idx] = strconv.Itoa(code) + } + msg := fmt.Sprintf("expected %s response, got %d instead", + strings.Join(codeStrs, "/"), + e.ActualResponse.StatusCode, + ) + if e.Method != "" && e.Target != "" { + //NOTE: Method and Target were added in a minor version change, + // and may not be filled if `e` was constructed outside the library. + msg = fmt.Sprintf("could not %s %q in Swift: %s", e.Method, e.Target, msg) + } + if len(e.ResponseBody) > 0 { + msg += ": " + string(e.ResponseBody) + } + return msg +} + +// BulkObjectError is the error message for a single object in a bulk operation. +// It is not generated individually, only as part of BulkError. +type BulkObjectError struct { + ContainerName string + ObjectName string + StatusCode int +} + +// Error implements the builtin/error interface. +func (e BulkObjectError) Error() string { + return fmt.Sprintf("%s/%s: %d %s", + e.ContainerName, e.ObjectName, + e.StatusCode, http.StatusText(e.StatusCode), + ) +} + +// BulkError is returned by Account.BulkUpload() when the archive was +// uploaded and unpacked successfully, but some (or all) objects could not be +// saved in Swift; and by Account.BulkDelete() when not all requested objects +// could be deleted. +type BulkError struct { + // StatusCode contains the overall HTTP status code of the operation. + StatusCode int + // OverallError contains the fatal error that aborted the bulk operation, or a + // summary of which recoverable errors were encountered. It may be empty. + OverallError string + // ObjectErrors contains errors that occurred while working on individual + // objects or containers. It may be empty if no such errors occurred. + ObjectErrors []BulkObjectError +} + +// Error implements the builtin/error interface. To fit into one line, it +// condenses the ObjectErrors into a count. +func (e BulkError) Error() string { + result := fmt.Sprintf("%d %s", e.StatusCode, http.StatusText(e.StatusCode)) + if e.OverallError != "" { + result += ": " + e.OverallError + } + if len(e.ObjectErrors) > 0 { + result += fmt.Sprintf(" (+%d object errors)", len(e.ObjectErrors)) + } + return result +} + +// Is checks if the given error is an UnexpectedStatusCodeError for that status +// code. For example: +// +// err := container.Delete(nil) +// if err != nil { +// if schwift.Is(err, http.StatusNotFound) { +// //container does not exist -> just what we wanted +// return nil +// } else { +// //report unexpected error +// return err +// } +// } +// +// It is safe to pass a nil error, in which case Is() always returns false. +func Is(err error, code int) bool { + if e, ok := errext.As[UnexpectedStatusCodeError](err); ok { + return e.ActualResponse.StatusCode == code + } + return false +} + +// MalformedHeaderError is generated when a response from Swift contains a +// malformed header. +type MalformedHeaderError struct { + Key string + ParseError error +} + +// Error implements the builtin/error interface. +func (e MalformedHeaderError) Error() string { + return "Bad header " + e.Key + ": " + e.ParseError.Error() +} diff --git a/vendor/github.com/majewsky/schwift/v2/field_metadata.go b/vendor/github.com/majewsky/schwift/v2/field_metadata.go new file mode 100644 index 0000000..447f293 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/field_metadata.go @@ -0,0 +1,56 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +// FieldMetadata is a helper type that provides safe access to the metadata headers +// in a headers instance. It cannot be directly constructed, but each headers +// type has a method "Metadata" returning this type. For example: +// +// hdr := NewObjectHeaders() +// //the following two statements are equivalent +// hdr["X-Object-Meta-Access"] = "strictly confidential" +// hdr.Metadata().Set("Access", "strictly confidential") +type FieldMetadata struct { + h Headers + k string +} + +// Clear works like Headers.Clear(), but prepends the metadata prefix to the key. +func (m FieldMetadata) Clear(key string) { + m.h.Clear(m.k + key) +} + +// Del works like Headers.Del(), but prepends the metadata prefix to the key. +func (m FieldMetadata) Del(key string) { + m.h.Del(m.k + key) +} + +// Get works like Headers.Get(), but prepends the metadata prefix to the key. +func (m FieldMetadata) Get(key string) string { + return m.h.Get(m.k + key) +} + +// Set works like Headers.Set(), but prepends the metadata prefix to the key. +func (m FieldMetadata) Set(key, value string) { + m.h.Set(m.k+key, value) +} + +func (m FieldMetadata) validate() error { + return nil +} diff --git a/vendor/github.com/majewsky/schwift/v2/field_string.go b/vendor/github.com/majewsky/schwift/v2/field_string.go new file mode 100644 index 0000000..8621a69 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/field_string.go @@ -0,0 +1,64 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +// FieldString is a helper type that provides type-safe access to a Swift header key +// whose value is a string. It cannot be directly constructed, but methods on +// the Headers types return this type. For example: +// +// hdr := NewAccountHeaders() +// //the following two statements are equivalent: +// hdr["X-Container-Read"] = ".r:*,.rlistings" +// hdr.ReadACL().Set(".r:*,.rlistings") +type FieldString struct { + h Headers + k string +} + +// Exists checks whether there is a value for this header. +func (f FieldString) Exists() bool { + return f.h.Get(f.k) != "" +} + +// Get returns the value for this header, or the empty string if there is no value. +func (f FieldString) Get() string { + return f.h.Get(f.k) +} + +// Set writes a new value for this header into the corresponding headers +// instance. +func (f FieldString) Set(value string) { + f.h.Set(f.k, value) +} + +// Del removes this key from the original headers instance, so that the +// key will remain unchanged on the server during Update(). +func (f FieldString) Del() { + f.h.Del(f.k) +} + +// Clear sets this key to an empty string in the original headers +// instance, so that the key will be removed on the server during Update(). +func (f FieldString) Clear() { + f.h.Clear(f.k) +} + +func (f FieldString) validate() error { + return nil +} diff --git a/vendor/github.com/majewsky/schwift/v2/field_time.go b/vendor/github.com/majewsky/schwift/v2/field_time.go new file mode 100644 index 0000000..8c51f6b --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/field_time.go @@ -0,0 +1,170 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "net/http" + "strconv" + "time" +) + +// FieldHTTPTimeReadonly is a helper type that provides type-safe access to a +// readonly Swift header whose value is a HTTP timestamp like this: +// +// Mon, 02 Jan 2006 15:04:05 GMT +// +// It cannot be directly constructed, but methods on the Headers types return +// this type. For example: +// +// //suppose you have: +// hdr, err := obj.Headers() +// +// //you could do this: +// time, err := time.Parse(time.RFC1123, hdr.Get("Last-Modified")) +// +// //or you can just: +// time := hdr.UpdatedAt().Get() +// +// Don't worry about the missing `err` in the last line. When the header fails +// to parse, Object.Headers() already returns the corresponding +// MalformedHeaderError. +type FieldHTTPTimeReadonly struct { + h Headers + k string +} + +// Exists checks whether there is a value for this header. +func (f FieldHTTPTimeReadonly) Exists() bool { + return f.h.Get(f.k) != "" +} + +// Get returns the value for this header, or the zero value if there is no value +// (or if it is not a valid timestamp). +func (f FieldHTTPTimeReadonly) Get() time.Time { + t, err := http.ParseTime(f.h.Get(f.k)) + if err != nil { + return time.Time{} + } + return t +} + +func (f FieldHTTPTimeReadonly) validate() error { + val := f.h.Get(f.k) + if val == "" { + return nil + } + _, err := http.ParseTime(val) + if err == nil { + return nil + } + return MalformedHeaderError{f.k, err} +} + +//////////////////////////////////////////////////////////////////////////////// + +// FieldUnixTime is a helper type that provides type-safe access to a Swift +// header whose value is a UNIX timestamp. It cannot be directly constructed, +// but methods on the Headers types return this type. For example: +// +// //suppose you have: +// hdr, err := obj.Headers() +// +// //you could do all this: +// sec, err := strconv.ParseFloat(hdr.Get("X-Delete-At"), 64) +// time := time.Unix(0, int64(1e9 * sec)) +// +// //or you can just: +// time := hdr.ExpiresAt().Get() +// +// Don't worry about the missing `err` in the last line. When the header fails +// to parse, Object.Headers() already returns the corresponding +// MalformedHeaderError. +type FieldUnixTime struct { + h Headers + k string +} + +// Exists checks whether there is a value for this header. +func (f FieldUnixTime) Exists() bool { + return f.h.Get(f.k) != "" +} + +// Get returns the value for this header, or the zero value if there is no value +// (or if it is not a valid timestamp). +func (f FieldUnixTime) Get() time.Time { + v, err := strconv.ParseFloat(f.h.Get(f.k), 64) + if err != nil { + return time.Time{} + } + return time.Unix(0, int64(1e9*v)) +} + +// Set writes a new value for this header into the corresponding headers +// instance. +func (f FieldUnixTime) Set(value time.Time) { + f.h.Set(f.k, strconv.FormatUint(uint64(value.UnixNano())/1e9, 10)) +} + +// Del removes this key from the original headers instance, so that the key will +// remain unchanged on the server during Update(). +func (f FieldUnixTime) Del() { + f.h.Del(f.k) +} + +// Clear sets this key to an empty string in the original headers instance, so +// that the key will be removed on the server during Update(). +func (f FieldUnixTime) Clear() { + f.h.Clear(f.k) +} + +func (f FieldUnixTime) validate() error { + val := f.h.Get(f.k) + if val == "" { + return nil + } + _, err := strconv.ParseFloat(val, 64) + if err == nil { + return nil + } + return MalformedHeaderError{f.k, err} +} + +//////////////////////////////////////////////////////////////////////////////// + +// FieldUnixTimeReadonly is a readonly variant of FieldUnixTime. It is used for +// fields that cannot be set by the client. +type FieldUnixTimeReadonly struct { + h Headers + k string +} + +// Exists checks whether there is a value for this header. +func (f FieldUnixTimeReadonly) Exists() bool { + return f.h.Get(f.k) != "" +} + +// Get returns the value for this header, or the zero value if there is no value +// (or if it is not a valid timestamp). +func (f FieldUnixTimeReadonly) Get() time.Time { + return FieldUnixTime(f).Get() +} + +func (f FieldUnixTimeReadonly) validate() error { + return FieldUnixTime(f).validate() +} diff --git a/vendor/github.com/majewsky/schwift/v2/field_uint64.go b/vendor/github.com/majewsky/schwift/v2/field_uint64.go new file mode 100644 index 0000000..8e1a6c2 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/field_uint64.go @@ -0,0 +1,105 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "strconv" +) + +// FieldUint64 is a helper type that provides type-safe access to a Swift header +// whose value is an unsigned integer. It cannot be directly constructed, but +// methods on the Headers types return this type. For example: +// +// hdr := NewAccountHeaders() +// //the following two statements are equivalent: +// hdr["X-Account-Meta-Quota-Bytes"] = "1048576" +// hdr.BytesUsedQuota().Set(1 << 20) +type FieldUint64 struct { + h Headers + k string +} + +// Exists checks whether there is a value for this header. +func (f FieldUint64) Exists() bool { + return f.h.Get(f.k) != "" +} + +// Get returns the value for this header, or 0 if there is no value (or if it is +// not a valid uint64). +func (f FieldUint64) Get() uint64 { + v, err := strconv.ParseUint(f.h.Get(f.k), 10, 64) + if err != nil { + return 0 + } + return v +} + +// Set writes a new value for this header into the corresponding headers +// instance. +func (f FieldUint64) Set(value uint64) { + f.h.Set(f.k, strconv.FormatUint(value, 10)) +} + +// Del removes this key from the original headers instance, so that the key will +// remain unchanged on the server during Update(). +func (f FieldUint64) Del() { + f.h.Del(f.k) +} + +// Clear sets this key to an empty string in the original headers instance, so +// that the key will be removed on the server during Update(). +func (f FieldUint64) Clear() { + f.h.Clear(f.k) +} + +func (f FieldUint64) validate() error { + val := f.h.Get(f.k) + if val == "" { + return nil + } + _, err := strconv.ParseUint(val, 10, 64) + if err == nil { + return nil + } + return MalformedHeaderError{f.k, err} +} + +//////////////////////////////////////////////////////////////////////////////// + +// FieldUint64Readonly is a readonly variant of FieldUint64. It is used for +// fields that cannot be set by the client. +type FieldUint64Readonly struct { + h Headers + k string +} + +// Exists checks whether there is a value for this header. +func (f FieldUint64Readonly) Exists() bool { + return f.h.Get(f.k) != "" +} + +// Get returns the value for this header, or 0 if there is no value (or if it is +// not a valid uint64). +func (f FieldUint64Readonly) Get() uint64 { + return FieldUint64(f).Get() +} + +func (f FieldUint64Readonly) validate() error { + return FieldUint64(f).validate() +} diff --git a/vendor/github.com/majewsky/schwift/v2/generated.go b/vendor/github.com/majewsky/schwift/v2/generated.go new file mode 100644 index 0000000..66c672a --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/generated.go @@ -0,0 +1,354 @@ +/******************************************************************************* +* +* THIS FILE IS AUTOGENERATED. +* +* Edit `generated.go.in` instead and run `make generate` to produce this file. +* +*******************************************************************************/ + +package schwift + +// AccountHeaders contains the headers for a schwift.Account instance. +// +// To read and write well-known headers, use the methods on this type. +// To read and write arbitrary headers, use the methods on the Headers supertype. +type AccountHeaders struct { + Headers +} + +// NewAccountHeaders creates a new AccountHeaders instance. The return value +// will have the Headers attribute initialized to a non-nil map. +func NewAccountHeaders() AccountHeaders { + return AccountHeaders{make(Headers)} +} + +// Validate returns MalformedHeaderError if the value of any well-known header +// does not conform to its data type. This is called automatically by Schwift +// when preparing an AccountHeaders instance from a GET/HEAD response, so you +// usually do not need to do it yourself. You will get the validation error from +// the Account method doing the request, e.g. Headers(). +func (h AccountHeaders) Validate() error { + if err := h.BytesUsed().validate(); err != nil { + return err + } + if err := h.ContainerCount().validate(); err != nil { + return err + } + if err := h.Metadata().validate(); err != nil { + return err + } + if err := h.BytesUsedQuota().validate(); err != nil { + return err + } + if err := h.TempURLKey2().validate(); err != nil { + return err + } + if err := h.TempURLKey().validate(); err != nil { + return err + } + if err := h.ObjectCount().validate(); err != nil { + return err + } + if err := h.CreatedAt().validate(); err != nil { + return err + } + return evadeGolintComplaint1() +} + +// BytesUsed provides type-safe access to X-Account-Bytes-Used headers. +func (h AccountHeaders) BytesUsed() FieldUint64Readonly { + return FieldUint64Readonly{h.Headers, "X-Account-Bytes-Used"} +} + +// ContainerCount provides type-safe access to X-Account-Container-Count headers. +func (h AccountHeaders) ContainerCount() FieldUint64Readonly { + return FieldUint64Readonly{h.Headers, "X-Account-Container-Count"} +} + +// Metadata provides type-safe access to X-Account-Meta- headers. +func (h AccountHeaders) Metadata() FieldMetadata { + return FieldMetadata{h.Headers, "X-Account-Meta-"} +} + +// BytesUsedQuota provides type-safe access to X-Account-Meta-Quota-Bytes headers. +func (h AccountHeaders) BytesUsedQuota() FieldUint64 { + return FieldUint64{h.Headers, "X-Account-Meta-Quota-Bytes"} +} + +// TempURLKey2 provides type-safe access to X-Account-Meta-Temp-URL-Key-2 headers. +func (h AccountHeaders) TempURLKey2() FieldString { + return FieldString{h.Headers, "X-Account-Meta-Temp-URL-Key-2"} +} + +// TempURLKey provides type-safe access to X-Account-Meta-Temp-URL-Key headers. +func (h AccountHeaders) TempURLKey() FieldString { + return FieldString{h.Headers, "X-Account-Meta-Temp-URL-Key"} +} + +// ObjectCount provides type-safe access to X-Account-Object-Count headers. +func (h AccountHeaders) ObjectCount() FieldUint64Readonly { + return FieldUint64Readonly{h.Headers, "X-Account-Object-Count"} +} + +// CreatedAt provides type-safe access to X-Timestamp headers. +func (h AccountHeaders) CreatedAt() FieldUnixTimeReadonly { + return FieldUnixTimeReadonly{h.Headers, "X-Timestamp"} +} + +// ContainerHeaders contains the headers for a schwift.Container instance. +// +// To read and write well-known headers, use the methods on this type. +// To read and write arbitrary headers, use the methods on the Headers supertype. +type ContainerHeaders struct { + Headers +} + +// NewContainerHeaders creates a new ContainerHeaders instance. The return value +// will have the Headers attribute initialized to a non-nil map. +func NewContainerHeaders() ContainerHeaders { + return ContainerHeaders{make(Headers)} +} + +// Validate returns MalformedHeaderError if the value of any well-known header +// does not conform to its data type. This is called automatically by Schwift +// when preparing an ContainerHeaders instance from a GET/HEAD response, so you +// usually do not need to do it yourself. You will get the validation error from +// the Container method doing the request, e.g. Headers(). +func (h ContainerHeaders) Validate() error { + if err := h.BytesUsed().validate(); err != nil { + return err + } + if err := h.Metadata().validate(); err != nil { + return err + } + if err := h.BytesUsedQuota().validate(); err != nil { + return err + } + if err := h.ObjectCountQuota().validate(); err != nil { + return err + } + if err := h.TempURLKey2().validate(); err != nil { + return err + } + if err := h.TempURLKey().validate(); err != nil { + return err + } + if err := h.ObjectCount().validate(); err != nil { + return err + } + if err := h.ReadACL().validate(); err != nil { + return err + } + if err := h.SyncKey().validate(); err != nil { + return err + } + if err := h.SyncTo().validate(); err != nil { + return err + } + if err := h.WriteACL().validate(); err != nil { + return err + } + if err := h.HistoryLocation().validate(); err != nil { + return err + } + if err := h.StoragePolicy().validate(); err != nil { + return err + } + if err := h.CreatedAt().validate(); err != nil { + return err + } + if err := h.VersionsLocation().validate(); err != nil { + return err + } + return evadeGolintComplaint1() +} + +// BytesUsed provides type-safe access to X-Container-Bytes-Used headers. +func (h ContainerHeaders) BytesUsed() FieldUint64Readonly { + return FieldUint64Readonly{h.Headers, "X-Container-Bytes-Used"} +} + +// Metadata provides type-safe access to X-Container-Meta- headers. +func (h ContainerHeaders) Metadata() FieldMetadata { + return FieldMetadata{h.Headers, "X-Container-Meta-"} +} + +// BytesUsedQuota provides type-safe access to X-Container-Meta-Quota-Bytes headers. +func (h ContainerHeaders) BytesUsedQuota() FieldUint64 { + return FieldUint64{h.Headers, "X-Container-Meta-Quota-Bytes"} +} + +// ObjectCountQuota provides type-safe access to X-Container-Meta-Quota-Count headers. +func (h ContainerHeaders) ObjectCountQuota() FieldUint64 { + return FieldUint64{h.Headers, "X-Container-Meta-Quota-Count"} +} + +// TempURLKey2 provides type-safe access to X-Container-Meta-Temp-URL-Key-2 headers. +func (h ContainerHeaders) TempURLKey2() FieldString { + return FieldString{h.Headers, "X-Container-Meta-Temp-URL-Key-2"} +} + +// TempURLKey provides type-safe access to X-Container-Meta-Temp-URL-Key headers. +func (h ContainerHeaders) TempURLKey() FieldString { + return FieldString{h.Headers, "X-Container-Meta-Temp-URL-Key"} +} + +// ObjectCount provides type-safe access to X-Container-Object-Count headers. +func (h ContainerHeaders) ObjectCount() FieldUint64Readonly { + return FieldUint64Readonly{h.Headers, "X-Container-Object-Count"} +} + +// ReadACL provides type-safe access to X-Container-Read headers. +func (h ContainerHeaders) ReadACL() FieldString { + return FieldString{h.Headers, "X-Container-Read"} +} + +// SyncKey provides type-safe access to X-Container-Sync-Key headers. +func (h ContainerHeaders) SyncKey() FieldString { + return FieldString{h.Headers, "X-Container-Sync-Key"} +} + +// SyncTo provides type-safe access to X-Container-Sync-To headers. +func (h ContainerHeaders) SyncTo() FieldString { + return FieldString{h.Headers, "X-Container-Sync-To"} +} + +// WriteACL provides type-safe access to X-Container-Write headers. +func (h ContainerHeaders) WriteACL() FieldString { + return FieldString{h.Headers, "X-Container-Write"} +} + +// HistoryLocation provides type-safe access to X-History-Location headers. +func (h ContainerHeaders) HistoryLocation() FieldString { + return FieldString{h.Headers, "X-History-Location"} +} + +// StoragePolicy provides type-safe access to X-Storage-Policy headers. +func (h ContainerHeaders) StoragePolicy() FieldString { + return FieldString{h.Headers, "X-Storage-Policy"} +} + +// CreatedAt provides type-safe access to X-Timestamp headers. +func (h ContainerHeaders) CreatedAt() FieldUnixTimeReadonly { + return FieldUnixTimeReadonly{h.Headers, "X-Timestamp"} +} + +// VersionsLocation provides type-safe access to X-Versions-Location headers. +func (h ContainerHeaders) VersionsLocation() FieldString { + return FieldString{h.Headers, "X-Versions-Location"} +} + +// ObjectHeaders contains the headers for a schwift.Object instance. +// +// To read and write well-known headers, use the methods on this type. +// To read and write arbitrary headers, use the methods on the Headers supertype. +type ObjectHeaders struct { + Headers +} + +// NewObjectHeaders creates a new ObjectHeaders instance. The return value +// will have the Headers attribute initialized to a non-nil map. +func NewObjectHeaders() ObjectHeaders { + return ObjectHeaders{make(Headers)} +} + +// Validate returns MalformedHeaderError if the value of any well-known header +// does not conform to its data type. This is called automatically by Schwift +// when preparing an ObjectHeaders instance from a GET/HEAD response, so you +// usually do not need to do it yourself. You will get the validation error from +// the Object method doing the request, e.g. Headers(). +func (h ObjectHeaders) Validate() error { + if err := h.ContentDisposition().validate(); err != nil { + return err + } + if err := h.ContentEncoding().validate(); err != nil { + return err + } + if err := h.SizeBytes().validate(); err != nil { + return err + } + if err := h.ContentType().validate(); err != nil { + return err + } + if err := h.Etag().validate(); err != nil { + return err + } + if err := h.UpdatedAt().validate(); err != nil { + return err + } + if err := h.ExpiresAt().validate(); err != nil { + return err + } + if err := h.Metadata().validate(); err != nil { + return err + } + if err := h.SymlinkTargetAccount().validate(); err != nil { + return err + } + if err := h.SymlinkTarget().validate(); err != nil { + return err + } + if err := h.CreatedAt().validate(); err != nil { + return err + } + return evadeGolintComplaint1() +} + +// ContentDisposition provides type-safe access to Content-Disposition headers. +func (h ObjectHeaders) ContentDisposition() FieldString { + return FieldString{h.Headers, "Content-Disposition"} +} + +// ContentEncoding provides type-safe access to Content-Encoding headers. +func (h ObjectHeaders) ContentEncoding() FieldString { + return FieldString{h.Headers, "Content-Encoding"} +} + +// SizeBytes provides type-safe access to Content-Length headers. +func (h ObjectHeaders) SizeBytes() FieldUint64 { + return FieldUint64{h.Headers, "Content-Length"} +} + +// ContentType provides type-safe access to Content-Type headers. +func (h ObjectHeaders) ContentType() FieldString { + return FieldString{h.Headers, "Content-Type"} +} + +// Etag provides type-safe access to Etag headers. +func (h ObjectHeaders) Etag() FieldString { + return FieldString{h.Headers, "Etag"} +} + +// UpdatedAt provides type-safe access to Last-Modified headers. +func (h ObjectHeaders) UpdatedAt() FieldHTTPTimeReadonly { + return FieldHTTPTimeReadonly{h.Headers, "Last-Modified"} +} + +// ExpiresAt provides type-safe access to X-Delete-At headers. +func (h ObjectHeaders) ExpiresAt() FieldUnixTime { + return FieldUnixTime{h.Headers, "X-Delete-At"} +} + +// Metadata provides type-safe access to X-Object-Meta- headers. +func (h ObjectHeaders) Metadata() FieldMetadata { + return FieldMetadata{h.Headers, "X-Object-Meta-"} +} + +// SymlinkTargetAccount provides type-safe access to X-Symlink-Target-Account headers. +func (h ObjectHeaders) SymlinkTargetAccount() FieldString { + return FieldString{h.Headers, "X-Symlink-Target-Account"} +} + +// SymlinkTarget provides type-safe access to X-Symlink-Target headers. +func (h ObjectHeaders) SymlinkTarget() FieldString { + return FieldString{h.Headers, "X-Symlink-Target"} +} + +// CreatedAt provides type-safe access to X-Timestamp headers. +func (h ObjectHeaders) CreatedAt() FieldUnixTimeReadonly { + return FieldUnixTimeReadonly{h.Headers, "X-Timestamp"} +} + +func evadeGolintComplaint1() error { + return nil +} diff --git a/vendor/github.com/majewsky/schwift/v2/generated.go.in b/vendor/github.com/majewsky/schwift/v2/generated.go.in new file mode 100644 index 0000000..3f4dcd4 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/generated.go.in @@ -0,0 +1,101 @@ +{ + "Account": { + "Fields": [ + { "Header": "X-Account-Bytes-Used", "Attribute": "BytesUsed", "Type": "Uint64Readonly" }, + { "Header": "X-Account-Container-Count", "Attribute": "ContainerCount", "Type": "Uint64Readonly" }, + { "Header": "X-Account-Meta-", "Attribute": "Metadata", "Type": "Metadata" }, + { "Header": "X-Account-Meta-Quota-Bytes", "Attribute": "BytesUsedQuota", "Type": "Uint64" }, + { "Header": "X-Account-Meta-Temp-URL-Key-2", "Attribute": "TempURLKey2", "Type": "String" }, + { "Header": "X-Account-Meta-Temp-URL-Key", "Attribute": "TempURLKey", "Type": "String" }, + { "Header": "X-Account-Object-Count", "Attribute": "ObjectCount", "Type": "Uint64Readonly" }, + { "Header": "X-Timestamp", "Attribute": "CreatedAt", "Type": "UnixTimeReadonly" } + ] + }, + "Container": { + "Fields": [ + { "Header": "X-Container-Bytes-Used", "Attribute": "BytesUsed", "Type": "Uint64Readonly" }, + { "Header": "X-Container-Meta-", "Attribute": "Metadata", "Type": "Metadata" }, + { "Header": "X-Container-Meta-Quota-Bytes", "Attribute": "BytesUsedQuota", "Type": "Uint64" }, + { "Header": "X-Container-Meta-Quota-Count", "Attribute": "ObjectCountQuota", "Type": "Uint64" }, + { "Header": "X-Container-Meta-Temp-URL-Key-2", "Attribute": "TempURLKey2", "Type": "String" }, + { "Header": "X-Container-Meta-Temp-URL-Key", "Attribute": "TempURLKey", "Type": "String" }, + { "Header": "X-Container-Object-Count", "Attribute": "ObjectCount", "Type": "Uint64Readonly" }, + { "Header": "X-Container-Read", "Attribute": "ReadACL", "Type": "String" }, + { "Header": "X-Container-Sync-Key", "Attribute": "SyncKey", "Type": "String" }, + { "Header": "X-Container-Sync-To", "Attribute": "SyncTo", "Type": "String" }, + { "Header": "X-Container-Write", "Attribute": "WriteACL", "Type": "String" }, + { "Header": "X-History-Location", "Attribute": "HistoryLocation", "Type": "String" }, + { "Header": "X-Storage-Policy", "Attribute": "StoragePolicy", "Type": "String" }, + { "Header": "X-Timestamp", "Attribute": "CreatedAt", "Type": "UnixTimeReadonly" }, + { "Header": "X-Versions-Location", "Attribute": "VersionsLocation", "Type": "String" } + ] + }, + "Object": { + "Fields": [ + { "Header": "Content-Disposition", "Attribute": "ContentDisposition", "Type": "String" }, + { "Header": "Content-Encoding", "Attribute": "ContentEncoding", "Type": "String" }, + { "Header": "Content-Length", "Attribute": "SizeBytes", "Type": "Uint64" }, + { "Header": "Content-Type", "Attribute": "ContentType", "Type": "String" }, + { "Header": "Etag", "Attribute": "Etag", "Type": "String" }, + { "Header": "Last-Modified", "Attribute": "UpdatedAt", "Type": "HTTPTimeReadonly" }, + { "Header": "X-Delete-At", "Attribute": "ExpiresAt", "Type": "UnixTime" }, + { "Header": "X-Object-Meta-", "Attribute": "Metadata", "Type": "Metadata" }, + { "Header": "X-Symlink-Target-Account", "Attribute": "SymlinkTargetAccount", "Type": "String" }, + { "Header": "X-Symlink-Target", "Attribute": "SymlinkTarget", "Type": "String" }, + { "Header": "X-Timestamp", "Attribute": "CreatedAt", "Type": "UnixTimeReadonly" } + ] + } +} +--- +/******************************************************************************* +* +* THIS FILE IS AUTOGENERATED. +* +* Edit `generated.go.in` instead and run `make generate` to produce this file. +* +*******************************************************************************/ + +package schwift + +{{- range $htype, $hmeta := . }} + +// {{$htype}}Headers contains the headers for a schwift.{{$htype}} instance. +// +// To read and write well-known headers, use the methods on this type. +// To read and write arbitrary headers, use the methods on the Headers supertype. +type {{$htype}}Headers struct { + Headers +} + +// New{{$htype}}Headers creates a new {{$htype}}Headers instance. The return value +// will have the Headers attribute initialized to a non-nil map. +func New{{$htype}}Headers() {{$htype}}Headers { + return {{$htype}}Headers{make(Headers)} +} + +// Validate returns MalformedHeaderError if the value of any well-known header +// does not conform to its data type. This is called automatically by Schwift +// when preparing an {{$htype}}Headers instance from a GET/HEAD response, so you +// usually do not need to do it yourself. You will get the validation error from +// the {{$htype}} method doing the request, e.g. Headers(). +func (h {{$htype}}Headers) Validate() error { +{{- range $field := $hmeta.Fields }} + if err := h.{{$field.Attribute}}().validate(); err != nil { + return err + } +{{- end }} + return evadeGolintComplaint1() +} + +{{- range $field := $hmeta.Fields }} + +// {{$field.Attribute}} provides type-safe access to {{$field.Header}} headers. +func (h {{$htype}}Headers) {{$field.Attribute}}() Field{{$field.Type}} { + return Field{{$field.Type}}{h.Headers, "{{$field.Header}}"} +} +{{- end }} +{{- end }} + +func evadeGolintComplaint1() error { + return nil +} diff --git a/vendor/github.com/majewsky/schwift/v2/gopherschwift/package.go b/vendor/github.com/majewsky/schwift/v2/gopherschwift/package.go new file mode 100644 index 0000000..743ac0f --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/gopherschwift/package.go @@ -0,0 +1,140 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +/* +Package gopherschwift contains a Gophercloud backend for Schwift. + +If your application uses Gophercloud (https://github.com/gophercloud/gophercloud/v2), +you can use the Wrap() function in this package as an entrypoint to Schwift. +A schwift.Account created this way will re-use Gophercloud's authentication code, +so you only need to obtain a client token once using Gophercloud. For example: + + import ( + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/utils/v2/openstack/clientconfig" + "github.com/majewsky/schwift/v2/gopherschwift" + ) + + provider, err := clientconfig.AuthenticatedClient(nil) + client, err := openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{}) + account, err := gopherschwift.Wrap(client) + +Using this schwift.Account instance, you have access to all of schwift's API. +Refer to the documentation in the parent package for details. +*/ +package gopherschwift + +import ( + "io" + "net/http" + + "github.com/gophercloud/gophercloud/v2" + + "github.com/majewsky/schwift/v2" +) + +// Options contains additional options that can be passed to Wrap(). +type Options struct { + // If set, this User-Agent will be reported in HTTP requests instead of + // schwift.DefaultUserAgent. + UserAgent string +} + +// Wrap creates a schwift.Account that uses the given service client as its +// backend. The service client must refer to a Swift endpoint, i.e. it should +// have been created by openstack.NewObjectStorageV1(). +func Wrap(client *gophercloud.ServiceClient, opts *Options) (*schwift.Account, error) { + b := &backend{ + c: client, + userAgent: schwift.DefaultUserAgent, + } + if opts != nil && opts.UserAgent != "" { + b.userAgent = opts.UserAgent + } + return schwift.InitializeAccount(b) +} + +type backend struct { + c *gophercloud.ServiceClient + userAgent string +} + +func (g *backend) EndpointURL() string { + return g.c.Endpoint +} + +func (g *backend) Clone(newEndpointURL string) schwift.Backend { + clonedClient := *g.c + clonedClient.Endpoint = newEndpointURL + return &backend{ + c: &clonedClient, + userAgent: g.userAgent, + } +} + +func (g *backend) Do(req *http.Request) (*http.Response, error) { + return g.do(req, false) +} + +func (g *backend) do(req *http.Request, afterReauth bool) (*http.Response, error) { + provider := g.c.ProviderClient + + for key, value := range provider.AuthenticatedHeaders() { + req.Header.Set(key, value) + } + req.Header.Set("User-Agent", g.userAgent) + + resp, err := provider.HTTPClient.Do(req) + if err != nil { + return nil, err + } + + // detect expired token + if resp.StatusCode == http.StatusUnauthorized && !afterReauth { + _, err := io.Copy(io.Discard, resp.Body) + if err != nil { + return nil, err + } + err = resp.Body.Close() + if err != nil { + return nil, err + } + err = provider.Reauthenticate(req.Context(), resp.Request.Header.Get("X-Auth-Token")) + if err != nil { + return nil, err + } + + // Swift is stupid: Even though we send `Expect: 100-continue`, it doesn't + // help. Swift will right away answer `100 Continue` and ONLY THEN actually + // check the token (at least in our prod setup). + // + // To increase the chance that this does not completely break this request, + // reset the reader if it implements Seek(). + if seekableReqBody, ok := req.Body.(io.Seeker); ok { + _, err := seekableReqBody.Seek(0, io.SeekStart) + if err != nil { + return nil, err + } + } + + // restart request with new token + return g.do(req, true) + } + + return resp, nil +} diff --git a/vendor/github.com/majewsky/schwift/v2/headers.go b/vendor/github.com/majewsky/schwift/v2/headers.go new file mode 100644 index 0000000..a4fda78 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/headers.go @@ -0,0 +1,122 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "net/http" + "net/textproto" +) + +// Headers represents a set of request headers or response headers. +// +// Users will typically use one of the subtypes (AccountHeaders, +// ContainerHeaders, ObjectHeaders) instead, which provide type-safe access to +// well-known headers. The http.Header-like interface on this type can be used +// read and write arbitrary headers. For example, the following calls are +// equivalent: +// +// h := make(AccountHeaders) +// h.Headers.Set("X-Account-Meta-Quota-Bytes", "1048576") +// h.BytesUsedQuota().Set(1048576) +type Headers map[string]string + +// Clear sets the value for the specified header to the empty string. When the +// Headers instance is then sent to the server with Update(), the server will +// delete the value for that header; cf. Del(). +func (h Headers) Clear(key string) { + h[textproto.CanonicalMIMEHeaderKey(key)] = "" +} + +// Del deletes a key from the Headers instance. When the Headers instance is +// then sent to the server with Update(), a key which has been deleted with +// Del() will remain unchanged on the server. +// +// For most writable attributes, a key which has been deleted with Del() will +// remain unchanged on the server. To remove the key on the server, use Clear() +// instead. +// +// For object metadata (but not other object attributes), deleting a key will +// cause that key to be deleted on the server. Del() is identical to Clear() in +// this case. +func (h Headers) Del(key string) { + delete(h, textproto.CanonicalMIMEHeaderKey(key)) +} + +// Get returns the value for the specified header. +func (h Headers) Get(key string) string { + return h[textproto.CanonicalMIMEHeaderKey(key)] +} + +// Set sets a new value for the specified header. Any existing value will be +// overwritten. +func (h Headers) Set(key, value string) { + h[textproto.CanonicalMIMEHeaderKey(key)] = value +} + +// ToHTTP converts this Headers instance into the equivalent http.Header +// instance. The return value is guaranteed to be non-nil. +func (h Headers) ToHTTP() http.Header { + dest := make(http.Header, len(h)) + for k, v := range h { + dest.Set(k, v) + } + return dest +} + +// ToOpts wraps this Headers instance into a RequestOpts instance, so that it +// can be passed to Schwift's various request methods. +// +// hdr := NewObjectHeaders() +// hdr.ContentType().Set("image/png") +// hdr.Metadata().Set("color", "blue") +// obj.Upload(content, nil, hdr.ToOpts()) +func (h Headers) ToOpts() *RequestOptions { + return &RequestOptions{Headers: h} +} + +func headersFromHTTP(src http.Header) Headers { + h := make(Headers, len(src)) + for k, v := range src { + if len(v) > 0 { + h.Set(k, v[0]) + } + } + return h +} + +//////////////////////////////////////////////////////////////////////////////// +// specialized accessors on Headers subtypes that are not autogenerated + +// IsDynamicLargeObject returns true if this set of headers belongs to a Dynamic +// Large Object (DLO). +func (h ObjectHeaders) IsDynamicLargeObject() bool { + return h.Headers.Get("X-Object-Manifest") != "" +} + +// IsStaticLargeObject returns true if this set of headers belongs to a Static +// Large Object (SLO). +func (h ObjectHeaders) IsStaticLargeObject() bool { + return h.Headers.Get("X-Static-Large-Object") == "True" +} + +// IsLargeObject returns true if this set of headers belongs to a large object +// (either an SLO or a DLO). +func (h ObjectHeaders) IsLargeObject() bool { + return h.IsDynamicLargeObject() || h.IsStaticLargeObject() +} diff --git a/vendor/github.com/majewsky/schwift/v2/internal/errext/errext.go b/vendor/github.com/majewsky/schwift/v2/internal/errext/errext.go new file mode 100644 index 0000000..3655ba7 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/internal/errext/errext.go @@ -0,0 +1,26 @@ +package errext + +import "errors" + +// vendored from https://github.com/sapcc/go-bits/blob/master/errext/errext.go (also licensed Apache 2.0) to prevent go.mod go bump to 1.21 + +// As is a variant of errors.As() that leverages generics to present a nicer interface. +// +// //this code: +// var perr os.PathError +// if errors.As(err, &perr) { +// handle(perr) +// } +// //can be rewritten as: +// if perr, ok := errext.As[os.PathError](err); ok { +// handle(perr) +// } +// +// This is sometimes more verbose (like in this example), but allows to scope +// the specific error variable to the condition's then-branch, and also looks +// more idiomatic to developers already familiar with type casts. +func As[T error](err error) (T, bool) { + var result T + ok := errors.As(err, &result) + return result, ok +} diff --git a/vendor/github.com/majewsky/schwift/v2/iterator.go b/vendor/github.com/majewsky/schwift/v2/iterator.go new file mode 100644 index 0000000..ba5e6f6 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/iterator.go @@ -0,0 +1,170 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "context" + "encoding/json" + "net/http" + "strconv" + "strings" +) + +// iteratorInterface allows iteratorBase to access public attributes of +// ContainerIterator/ObjectIterator. +type iteratorInterface interface { + getAccount() *Account + getContainerName() string + getDelimiter() string + getPrefix() string + getOptions() *RequestOptions + // putHeader initializes the AccountHeaders/ContainerHeaders field of the + // Account/Container using the response headers from the GET request. + putHeader(http.Header) error +} + +func (i ContainerIterator) getAccount() *Account { return i.Account } +func (i ContainerIterator) getContainerName() string { return "" } +func (i ContainerIterator) getDelimiter() string { return "" } +func (i ContainerIterator) getPrefix() string { return i.Prefix } +func (i ContainerIterator) getOptions() *RequestOptions { return i.Options } + +func (i ContainerIterator) putHeader(hdr http.Header) error { + headers := AccountHeaders{headersFromHTTP(hdr)} + if err := headers.Validate(); err != nil { + return err + } + i.Account.headers = &headers + return nil +} + +func (i ObjectIterator) getAccount() *Account { return i.Container.Account() } +func (i ObjectIterator) getContainerName() string { return i.Container.Name() } +func (i ObjectIterator) getDelimiter() string { return i.Delimiter } +func (i ObjectIterator) getPrefix() string { return i.Prefix } +func (i ObjectIterator) getOptions() *RequestOptions { return i.Options } + +func (i ObjectIterator) putHeader(hdr http.Header) error { + headers := ContainerHeaders{headersFromHTTP(hdr)} + if err := headers.Validate(); err != nil { + return err + } + i.Container.headers = &headers + return nil +} + +// iteratorBase provides shared behavior for ContainerIterator and ObjectIterator. +type iteratorBase struct { + i iteratorInterface + marker string + eof bool +} + +func (b *iteratorBase) request(limit int, detailed bool) Request { + r := Request{ + Method: "GET", + ContainerName: b.i.getContainerName(), + Options: cloneRequestOptions(b.i.getOptions(), nil), + } + + if delimiter := b.i.getDelimiter(); delimiter != "" { + r.Options.Values.Set("delimiter", delimiter) + } + if prefix := b.i.getPrefix(); prefix != "" { + r.Options.Values.Set("prefix", prefix) + } + + if b.marker == "" { + r.Options.Values.Del("marker") + } else { + r.Options.Values.Set("marker", b.marker) + } + + if limit < 0 { + r.Options.Values.Del("limit") + } else { + r.Options.Values.Set("limit", strconv.FormatUint(uint64(limit), 10)) + } + + if detailed { + r.Options.Headers.Set("Accept", "application/json") + r.Options.Values.Set("format", "json") + r.ExpectStatusCodes = []int{200} + } else { + r.Options.Headers.Set("Accept", "text/plain") + r.Options.Values.Set("format", "plain") + r.ExpectStatusCodes = []int{200, 204} + } + + return r +} + +func (b *iteratorBase) nextPage(ctx context.Context, limit int) ([]string, error) { + if b.eof { + return nil, nil + } + resp, err := b.request(limit, false).Do(ctx, b.i.getAccount().backend) + if err != nil { + return nil, err + } + + buf, err := collectResponseBody(resp) + if err != nil { + return nil, err + } + bufStr := strings.TrimSuffix(string(buf), "\n") + var result []string + if bufStr != "" { + result = strings.Split(bufStr, "\n") + } + + if len(result) == 0 { + b.eof = true + b.marker = "" + } else { + b.eof = false + b.marker = result[len(result)-1] + } + return result, b.i.putHeader(resp.Header) +} + +func (b *iteratorBase) nextPageDetailed(ctx context.Context, limit int, data interface{}) error { + if b.eof { + return nil + } + resp, err := b.request(limit, true).Do(ctx, b.i.getAccount().backend) + if err != nil { + return err + } + + err = json.NewDecoder(resp.Body).Decode(&data) + closeErr := resp.Body.Close() + if err == nil { + err = closeErr + } + if err == nil { + err = b.i.putHeader(resp.Header) + } + return err +} + +func (b *iteratorBase) setMarker(marker string) { + b.marker = marker + b.eof = marker == "" +} diff --git a/vendor/github.com/majewsky/schwift/v2/largeobject.go b/vendor/github.com/majewsky/schwift/v2/largeobject.go new file mode 100644 index 0000000..2ce63a8 --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/largeobject.go @@ -0,0 +1,825 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "bytes" + "context" + "crypto/md5" //nolint:gosec // Etag uses md5 + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "hash" + "io" + "math" + "net/http" + "net/url" + "path" + "regexp" + "strconv" + "strings" + "time" + + "github.com/jpillora/longestcommon" +) + +// SegmentInfo describes a segment of a large object. +// +// For .RangeLength == 0, the segment consists of all the bytes in the backing +// object, after skipping the first .RangeOffset bytes. The default +// (.RangeOffset == 0) is to include the entire contents of the backing object. +// +// For .RangeLength > 0, the segment consists of that many bytes from the +// backing object, again after skipping the first .RangeOffset bytes. +// +// However, for .RangeOffset < 0, the segment consists of .RangeLength many bytes +// from the *end* of the backing object. (The concrete value for .RangeOffset is +// disregarded.) .RangeLength must be non-zero in this case. +// +// Sorry that specifying a range is that involved. I was just following orders ^W +// RFC 7233, section 3.1 here. +type SegmentInfo struct { + Object *Object + SizeBytes uint64 + Etag string + RangeLength uint64 + RangeOffset int64 + // Static Large Objects support data segments that are not backed by actual + // objects. For those kinds of segments, only the Data attribute is set and + // all other attributes are set to their default values (esp. .Object == nil). + // + // Data segments can only be used for small chunks of data because the SLO + // manifest (the list of all SegmentInfo encoded as JSON) is severely limited + // in size (usually to 8 MiB). + Data []byte +} + +type sloSegmentInfo struct { + Path string `json:"path,omitempty"` + SizeBytes uint64 `json:"size_bytes,omitempty"` + Etag string `json:"etag,omitempty"` + Range string `json:"range,omitempty"` + DataBase64 string `json:"data,omitempty"` +} + +// LargeObjectStrategy enumerates segmenting strategies supported by Swift. +type LargeObjectStrategy int + +// A value of 0 for LargeObjectStrategy will instruct Schwift to choose a +// strategy itself. Right now, Schwift always chooses StaticLargeObject, but +// this behavior may change in future versions of Schwift, esp. if new +// strategies become available. The choice may also start to depend on the +// capabilities advertised by the server. +const ( + // StaticLargeObject is the default LargeObjectStrategy used by Schwift. + StaticLargeObject LargeObjectStrategy = iota + 1 + // DynamicLargeObject is an older LargeObjectStrategy that is not recommended + // for new applications because of eventual consistency problems and missing + // support for several newer features (e.g. data segments, range specifications). + DynamicLargeObject +) + +// SegmentingOptions describes how an object is segmented. It is passed to +// Object.AsNewLargeObject(). +// +// If Strategy is not set, a reasonable strategy is chosen; see documentation on +// LargeObjectStrategy for details. +// +// SegmentContainer must not be nil. A value of nil will cause Schwift to panic. +// If the SegmentContainer is not in the same account as the large object, +// ErrAccountMismatch will be returned by Schwift. +// +// If SegmentPrefix is empty, a reasonable default will be computed by +// Object.AsNewLargeObject(), using the format +// "//", where strategy is either "slo" or +// "dlo". +type SegmentingOptions struct { + Strategy LargeObjectStrategy + SegmentContainer *Container + SegmentPrefix string +} + +//////////////////////////////////////////////////////////////////////////////// + +// LargeObject is a wrapper for type Object that performs operations specific to +// large objects, i.e. those objects which are uploaded in segments rather than +// all at once. It can be constructed with the Object.AsLargeObject() and +// Object.AsNewLargeObject() methods. +// +// The following example shows how to upload a large file from the filesystem to +// Swift (error handling elided for brevity): +// +// file, err := os.Open(sourcePath) +// segmentContainer, err := account.Container("segments").EnsureExists() +// +// lo, err := o.AsNewLargeObject(schwift.SegmentingOptions { +// SegmentContainer: segmentContainer, +// //use defaults for everything else +// }, &schwift.TruncateOptions { +// //if there's already a large object here, clean it up +// DeleteSegments: true, +// }) +// +// err = lo.Append(contents, 1<<30) // 1<30 bytes = 1 GiB per segment +// err = lo.WriteManifest(nil) +// +// Append() has a more low-level counterpart, AddSegment(). Both methods can be +// freely intermixed. AddSegment() is useful when you want to control the +// segments' metadata or use advanced features like range segments or data +// segments; see documentation over there. +// +// Writing to a large object must always be concluded by a call to +// WriteManifest() to link the new segments to the large object on the server +// side. +type LargeObject struct { + object *Object + segmentContainer *Container + segmentPrefix string + strategy LargeObjectStrategy + segments []SegmentInfo +} + +// Object returns the location of this large object (where its manifest is stored). +func (lo *LargeObject) Object() *Object { + return lo.object +} + +// SegmentContainer returns the container in which this object's segments are +// stored. For static large objects, some segments may also be located in +// different containers. +func (lo *LargeObject) SegmentContainer() *Container { + return lo.segmentContainer +} + +// SegmentPrefix returns the prefix shared by the names of all segments of this +// object. For static large objects, some segments may not be located in this +// prefix. +func (lo *LargeObject) SegmentPrefix() string { + return lo.segmentPrefix +} + +// Strategy returns the LargeObjectStrategy used by this object. +func (lo *LargeObject) Strategy() LargeObjectStrategy { + return lo.strategy +} + +// Segments returns a list of all segments for this object, in order. +func (lo *LargeObject) Segments() ([]SegmentInfo, error) { + //NOTE: This method has an error return value because we might later switch + // to loading segments lazily inside this method. + return lo.segments, nil +} + +// SegmentObjects returns a list of all segment objects referenced by this large +// object. Note that, in general, +// +// len(lo.SegmentObjects()) <= len(lo.Segments()) +// +// since one object may be backing multiple segments, and data segments are not +// backed by any object at all. No guarantee is made about the order in which +// objects appear in this list. +func (lo *LargeObject) SegmentObjects() []*Object { + seen := make(map[string]bool) + result := make([]*Object, 0, len(lo.segments)) + for _, segment := range lo.segments { + if segment.Object == nil { // can happen because of data segments + continue + } + fullName := segment.Object.FullName() + if !seen[fullName] { + result = append(result, segment.Object) + } + seen[fullName] = true + } + return result +} + +// AsLargeObject opens an existing large object. If the given object does not +// exist, or if it is not a large object, ErrNotLarge will be returned. In this +// case, Object.AsNewLargeObject() needs to be used instead. +func (o *Object) AsLargeObject(ctx context.Context) (*LargeObject, error) { + exists, err := o.Exists(ctx) + if err != nil { + return nil, err + } + if !exists { + return nil, ErrNotLarge + } + + h := o.headers + if h.IsDynamicLargeObject() { + return o.asDLO(ctx, h.Get("X-Object-Manifest")) + } + if h.IsStaticLargeObject() { + return o.asSLO(ctx) + } + return nil, ErrNotLarge +} + +func (o *Object) asDLO(ctx context.Context, manifestStr string) (*LargeObject, error) { + manifest := strings.SplitN(manifestStr, "/", 2) + if len(manifest) < 2 { + return nil, ErrNotLarge + } + + lo := &LargeObject{ + object: o, + segmentContainer: o.c.a.Container(manifest[0]), + segmentPrefix: manifest[1], + strategy: DynamicLargeObject, + } + + iter := lo.segmentContainer.Objects() + iter.Prefix = lo.segmentPrefix + segmentInfos, err := iter.CollectDetailed(ctx) + if err != nil { + return nil, err + } + lo.segments = make([]SegmentInfo, 0, len(segmentInfos)) + for _, info := range segmentInfos { + lo.segments = append(lo.segments, SegmentInfo{ + Object: info.Object, + SizeBytes: info.SizeBytes, + Etag: info.Etag, + }) + } + + return lo, nil +} + +func (o *Object) asSLO(ctx context.Context) (*LargeObject, error) { + opts := RequestOptions{ + Values: make(url.Values), + } + opts.Values.Set("multipart-manifest", "get") + opts.Values.Set("format", "raw") + buf, err := o.Download(ctx, &opts).AsByteSlice() + if err != nil { + return nil, err + } + + var data []sloSegmentInfo + err = json.Unmarshal(buf, &data) + if err != nil { + return nil, errors.New("invalid SLO manifest: " + err.Error()) + } + + lo := &LargeObject{ + object: o, + strategy: StaticLargeObject, + } + if len(data) == 0 { + return lo, nil + } + + // read the segments first, then deduce the SegmentContainer/SegmentPrefix from these + lo.segments = make([]SegmentInfo, 0, len(data)) + for _, info := range data { + // option 1: data segment + if info.DataBase64 != "" { + data, err := base64.StdEncoding.DecodeString(info.DataBase64) + if err != nil { + return nil, errors.New("invalid SLO data segment: " + err.Error()) + } + lo.segments = append(lo.segments, SegmentInfo{Data: data}) + continue + } + + // option 2: segment backed by object + pathElements := strings.SplitN(strings.TrimPrefix(info.Path, "/"), "/", 2) + if len(pathElements) != 2 { + return nil, errors.New("invalid SLO segment: malformed path: " + info.Path) + } + s := SegmentInfo{ + Object: o.c.a.Container(pathElements[0]).Object(pathElements[1]), + SizeBytes: info.SizeBytes, + Etag: info.Etag, + } + if info.Range != "" { + var ok bool + s.RangeOffset, s.RangeLength, ok = parseHTTPRange(info.Range) + if !ok { + return nil, errors.New("invalid SLO segment: malformed range: " + info.Range) + } + } + lo.segments = append(lo.segments, s) + } + + // choose the SegmentContainer by majority vote (in the spirit of "be liberal + // in what you accept") + containerNames := make(map[string]uint) + for _, s := range lo.segments { + if s.Object == nil { // can happen for data segments + continue + } + containerNames[s.Object.c.Name()]++ + } + maxName := "" + maxVotes := uint(0) + for name, votes := range containerNames { + if votes > maxVotes { + maxName = name + maxVotes = votes + } + } + lo.segmentContainer = lo.object.c.a.Container(maxName) + + // choose the SegmentPrefix as the longest common prefix of all segments in + // the chosen SegmentContainer... + names := make([]string, 0, len(lo.segments)) + for _, s := range lo.segments { + if s.Object == nil { // can happen for data segments + continue + } + name := s.Object.c.Name() + if name == maxName { + names = append(names, s.Object.Name()) + } + } + lo.segmentPrefix = longestcommon.Prefix(names) + + // ..BUT if the prefix is a path with slashes, do not consider the part after + // the last slash; e.g. if we have segments "foo/bar/0001" and "foo/bar/0002", + // the longest common prefix is "foo/bar/000", but we actually want "foo/bar/" + if strings.Contains(lo.segmentPrefix, "/") { + lo.segmentPrefix = path.Dir(lo.segmentPrefix) + "/" + } + + return lo, nil +} + +func parseHTTPRange(str string) (offsetVal int64, lengthVal uint64, ok bool) { + fields := strings.SplitN(str, "-", 2) + if len(fields) != 2 { + return 0, 0, false + } + + if fields[0] == "" { + // case 1: "-" + if fields[1] == "" { + return 0, 0, true + } + + // case 2: "-N" + numBytes, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return 0, 0, false + } + return -1, numBytes, true + } + + firstByte, err := strconv.ParseUint(fields[0], 10, 63) // not 64; needs to be unsigned, but also fit into int64 + if err != nil { + return 0, 0, false + } + if fields[1] == "" { + // case 3: "N-" + return int64(firstByte), 0, true + } + // case 4: "M-N" + lastByte, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil || lastByte < firstByte { + return 0, 0, false + } + return int64(firstByte), lastByte - firstByte + 1, true +} + +// AsNewLargeObject opens an object as a large object. SegmentingOptions are +// always required, see the documentation on type SegmentingOptions for details. +// +// This function can be used regardless of whether the object exists or not. +// If the object exists and is a large object, this function behaves like +// Object.AsLargeObject() followed by Truncate(), except that segmenting options +// are initialized from the method's SegmentingOptions argument rather than from +// the existing manifest. +func (o *Object) AsNewLargeObject(ctx context.Context, sopts SegmentingOptions, topts *TruncateOptions) (*LargeObject, error) { + // we only need to load the existing large object if we want to do something + // with the old segments + if topts != nil && topts.DeleteSegments { + lo, err := o.AsLargeObject(ctx) + switch { + case err == nil: + err := lo.Truncate(ctx, topts) + if err != nil { + return nil, err + } + case errors.Is(err, ErrNotLarge): + // not an error, continue down below + default: + return nil, err // unexpected error + } + } + + lo := &LargeObject{object: o} + + // validate segment container + lo.segmentContainer = sopts.SegmentContainer + if sopts.SegmentContainer == nil { + panic("missing value for sopts.SegmentingContainer") + } + if !sopts.SegmentContainer.a.IsEqualTo(o.c.a) { + return nil, ErrAccountMismatch + } + + // apply default value for strategy + if sopts.Strategy == 0 { + lo.strategy = StaticLargeObject + } else { + lo.strategy = sopts.Strategy + } + + // apply default value for segmenting prefix + lo.segmentPrefix = sopts.SegmentPrefix + if lo.segmentPrefix == "" { + now := time.Now() + strategyStr := "slo" + if lo.strategy == DynamicLargeObject { + strategyStr = "dlo" + } + + lo.segmentPrefix = fmt.Sprintf("%s/%s/%d.%09d", + o.Name(), strategyStr, now.Unix(), now.Nanosecond(), + ) + } + + return lo, nil +} + +// TruncateOptions contains options that can be passed to LargeObject.Truncate() +// and Object.AsNewLargeObject(). +type TruncateOptions struct { + // When truncating a large object's manifest, delete its segments. + // This will cause Truncate() to call into BulkDelete(), so a BulkError may be + // returned. If this is false, the segments will not be deleted even though + // they may not be referenced by any large object anymore. + DeleteSegments bool +} + +// Truncate removes all segments from a large object's manifest. The manifest is +// not written by this call, so WriteManifest() usually needs to be called +// afterwards. +func (lo *LargeObject) Truncate(ctx context.Context, opts *TruncateOptions) error { + _, _, err := lo.object.c.a.BulkDelete(ctx, lo.SegmentObjects(), nil, nil) + if err == nil { + lo.segments = nil + } + return err +} + +// NextSegmentObject suggests where to upload the next segment. +// +// WARNING: This is a low-level function. Most callers will want to use +// Append(). You will only need to upload segments manually when you want to +// control the segments' metadata. +// +// If the name of the current final segment ends with a counter, that counter is +// incremented, otherwise a counter is appended to its name. When looking for a +// counter in an existing segment name, the regex /[0-9]+$/ is used. For example, +// given: +// +// segments := lo.segments() +// lastSegmentName := segments[len(segments)-1].Name() +// nextSegmentName := lo.NextSegmentObject().Name() +// +// If lastSegmentName is "segments/archive/segment0001", then nextSegmentName is +// "segments/archive/segment0002". If lastSegmentName is +// "segments/archive/first", then nextSegmentName is +// "segments/archive/first0000000000000001". +// +// However, the last segment's name will only be considered if it lies within +// lo.segmentContainer below lo.segmentPrefix. If that is not the case, the name +// of the last segment that does will be used instead. +// +// If there are no segments yet, or if all segments are located outside the +// lo.segmentContainer and lo.segmentPrefix, the first segment name is chosen as +// lo.segmentPrefix + "0000000000000001". +func (lo *LargeObject) NextSegmentObject() *Object { + // find the name of the last-most segment that is within the designated + // segment container and prefix + var prevSegmentName string + for _, s := range lo.segments { + o := s.Object + if o == nil { // can happen for data segments + continue + } + if lo.segmentContainer.IsEqualTo(o.c) && strings.HasPrefix(o.Name(), lo.segmentPrefix) { + prevSegmentName = s.Object.Name() + // keep going, we want to find the last such segment + } + } + + // choose the next segment name based on the previous one + var segmentName string + if prevSegmentName == "" { + segmentName = lo.segmentPrefix + initialIndex + } else { + segmentName = nextSegmentName(prevSegmentName) + } + + return lo.segmentContainer.Object(segmentName) +} + +var splitSegmentIndexRx = regexp.MustCompile(`^(.*?)([0-9]+$)`) +var initialIndex = "0000000000000001" + +// Given the object name of a previous large object segment, compute a suitable +// name for the next segment. See doc for LargeObject.NextSegmentObject() +// for how this works. +func nextSegmentName(segmentName string) string { + match := splitSegmentIndexRx.FindStringSubmatch(segmentName) + if match == nil { + return segmentName + initialIndex + } + base, idxStr := match[1], match[2] + + idx, err := strconv.ParseUint(idxStr, 10, 64) + if err != nil || idx == math.MaxUint64 { // overflow + // start from one again, but separate with a dash to ensure that the new + // index can be parsed properly in the next call to this function + return segmentName + "-" + initialIndex + } + + // print next index with same number of digits as previous index, + // e.g. "00001" -> "00002" (except if overflow, e.g. "9999" -> "10000") + formatStr := fmt.Sprintf("%%0%dd", len(idxStr)) + return base + fmt.Sprintf(formatStr, idx+1) +} + +// AddSegment appends a segment to this object. The segment must already have +// been uploaded. +// +// WARNING: This is a low-level function. Most callers will want to use +// Append(). You will only need to add segments manually when you want to +// control the segments' metadata, or when using advanced features such as +// range-limited segments or data segments. +// +// This method returns ErrAccountMismatch if the segment is not located in a +// container in the same account. +// +// For dynamic large objects, this method returns ErrContainerMismatch if the +// segment is not located in the correct container below the correct prefix. +// +// This method returns ErrSegmentInvalid if: +// +// - a range is specified in the SegmentInfo, but it is invalid or the +// LargeObject is a dynamic large object (DLOs do not support ranges), or +// +// - the SegmentInfo's Data attribute is set and any other attribute is also +// set (segments cannot be backed by objects and be data segments at the same +// time), or +// +// - the SegmentInfo's Data attribute is set, but the LargeObject is a dynamic +// large objects (DLOs do not support data segments). +func (lo *LargeObject) AddSegment(segment SegmentInfo) error { + if len(segment.Data) == 0 { + // validate segments backed by objects + o := segment.Object + if o == nil { + // required attributes + return ErrSegmentInvalid + } + if !o.c.a.IsEqualTo(lo.segmentContainer.a) { + return ErrAccountMismatch + } + + switch lo.strategy { + case DynamicLargeObject: + if segment.RangeLength != 0 || segment.RangeOffset != 0 { + // not supported for DLO + return ErrSegmentInvalid + } + + if !o.c.IsEqualTo(lo.segmentContainer) { + return ErrContainerMismatch + } + if !strings.HasPrefix(o.name, lo.segmentPrefix) { + return ErrContainerMismatch + } + + case StaticLargeObject: + if segment.RangeLength == 0 && segment.RangeOffset < 0 { + // malformed range + return ErrSegmentInvalid + } + } + } else { + // validate plain-data segments + if lo.strategy != StaticLargeObject { + // not supported for DLO + return ErrSegmentInvalid + } + if segment.Object != nil || segment.SizeBytes != 0 || segment.Etag != "" || segment.RangeLength != 0 || segment.RangeOffset != 0 { + // all other attributes must be unset + return ErrSegmentInvalid + } + } + + lo.segments = append(lo.segments, segment) + return nil +} + +// Append uploads the contents of the given io.Reader as segment objects of the +// given segment size. (The last segment will be shorter than the segment size +// unless the reader yields an exact multiple of the segment size.) The reader +// is consumed until EOF, or until an error occurs. +// +// If you do not have an io.Reader, but you have a []byte or string instance +// containing the data, wrap it in a *bytes.Reader instance like so: +// +// var buffer []byte +// lo.Append(bytes.NewReader(buffer), segmentSizeBytes) +// +// //or... +// var buffer string +// lo.Append(bytes.NewReader([]byte(buffer)), segmentSizeBytes) +// +// If segmentSizeBytes is zero, Append() defaults to the maximum file size +// reported by Account.Capabilities(). +// +// Calls to Append() and its low-level counterpart, AddSegment(), can be freely +// intermixed. AddSegment() is useful when you want to control the segments' +// metadata or use advanced features like range segments or data segments; see +// documentation over there. +// +// This function uploads segment objects, so it may return any error that +// Object.Upload() returns, see documentation over there. +func (lo *LargeObject) Append(ctx context.Context, contents io.Reader, segmentSizeBytes int64, opts *RequestOptions) error { + if segmentSizeBytes < 0 { + panic("segmentSizeBytes may not be negative") + } + if segmentSizeBytes == 0 { + // apply default value for segmenting size + caps, err := lo.object.c.a.Capabilities(ctx) + if err != nil { + return err + } + segmentSizeBytes = int64(caps.Swift.MaximumFileSize) + if segmentSizeBytes <= 0 { + return errors.New("cannot infer SegmentSizeBytes from Swift /info") + } + } + + sr := segmentingReader{contents, segmentSizeBytes} + for { + segment := sr.NextSegment() + if segment == nil { + break + } + + tracker := lengthAndEtagTrackingReader{ + Reader: segment, + Hasher: md5.New(), //nolint:gosec // Etag uses md5 + } + + obj := lo.NextSegmentObject() + err := obj.Upload(ctx, &tracker, nil, opts) + if err != nil { + return err + } + err = lo.AddSegment(SegmentInfo{ + Object: obj, + SizeBytes: tracker.BytesRead, + Etag: hex.EncodeToString(tracker.Hasher.Sum(nil)), + }) + if err != nil { + return err + } + } + + return nil +} + +type segmentingReader struct { + Reader io.Reader + SegmentSizeBytes int64 // must be >0 +} + +func (sr *segmentingReader) NextSegment() io.Reader { + // peek if there is more content in the backing reader + buf := make([]byte, 1) + var ( + n int + err error + ) + for n == 0 { + n, err = sr.Reader.Read(buf) + if err == io.EOF { + if n == 0 { + // EOF encountered + return nil + } + // that was the last byte - return only that (next NextSegment() will return nil) + return bytes.NewReader(buf) + } + } + + // looks like there is more stuff in the backing reader + return io.MultiReader( + bytes.NewReader(buf), + io.LimitReader(sr.Reader, sr.SegmentSizeBytes-1), // 1 == len(buf) + ) +} + +type lengthAndEtagTrackingReader struct { + Reader io.Reader + BytesRead uint64 + Hasher hash.Hash +} + +func (r *lengthAndEtagTrackingReader) Read(buf []byte) (int, error) { + n, err := r.Reader.Read(buf) + r.BytesRead += uint64(n) + r.Hasher.Write(buf[:n]) + return n, err +} + +// WriteManifest creates this large object by writing a manifest to its +// location using a PUT request. +// +// For dynamic large objects, this method does not generate a PUT request +// if the object already exists and has the correct manifest (i.e. +// SegmentContainer and SegmentPrefix have not been changed). +func (lo *LargeObject) WriteManifest(ctx context.Context, opts *RequestOptions) error { + switch lo.strategy { + case StaticLargeObject: + return lo.writeSLOManifest(ctx, opts) + case DynamicLargeObject: + return lo.writeDLOManifest(ctx, opts) + default: + panic("no such strategy") + } +} + +func (lo *LargeObject) writeDLOManifest(ctx context.Context, opts *RequestOptions) error { + manifest := lo.segmentContainer.Name() + "/" + lo.segmentPrefix + + // check if the manifest is already set correctly + headers, err := lo.object.Headers(ctx) + if err != nil && !Is(err, http.StatusNotFound) { + return err + } + if headers.Get("X-Object-Manifest") == manifest { + return nil + } + + // write manifest; make sure that this is a DLO + opts = cloneRequestOptions(opts, nil) + opts.Headers.Set("X-Object-Manifest", manifest) + return lo.object.Upload(ctx, nil, nil, opts) +} + +func (lo *LargeObject) writeSLOManifest(ctx context.Context, opts *RequestOptions) error { + sloSegments := make([]sloSegmentInfo, len(lo.segments)) + for idx, s := range lo.segments { + if len(s.Data) > 0 { + sloSegments[idx] = sloSegmentInfo{ + DataBase64: base64.StdEncoding.EncodeToString(s.Data), + } + } else { + si := sloSegmentInfo{ + Path: "/" + s.Object.FullName(), + SizeBytes: s.SizeBytes, + Etag: s.Etag, + } + + if s.RangeOffset < 0 { + si.Range = "-" + strconv.FormatUint(s.RangeLength, 10) + } else { + firstByteStr := strconv.FormatUint(uint64(s.RangeOffset), 10) + lastByteStr := strconv.FormatUint(uint64(s.RangeOffset)+s.RangeLength-1, 10) + si.Range = firstByteStr + "-" + lastByteStr + } + + sloSegments[idx] = si + } + } + + manifest, err := json.Marshal(sloSegments) + if err != nil { + // failing json.Marshal() on such a trivial data structure is alarming + panic(err.Error()) + } + + opts = cloneRequestOptions(opts, nil) + opts.Headers.Del("X-Object-Manifest") // ensure sanity :) + opts.Values.Set("multipart-manifest", "put") + return lo.object.Upload(ctx, bytes.NewReader(manifest), nil, opts) +} diff --git a/vendor/github.com/majewsky/schwift/v2/object.go b/vendor/github.com/majewsky/schwift/v2/object.go new file mode 100644 index 0000000..28c33da --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/object.go @@ -0,0 +1,689 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/md5" //nolint:gosec // Etag uses md5 + "crypto/sha1" //nolint:gosec // Used by swift + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "hash" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +// Object represents a Swift object. Instances are usually obtained by +// traversing downwards from a container with Container.Object() or +// Container.Objects(). +type Object struct { + c *Container + name string + // cache + headers *ObjectHeaders // from HEAD/GET without ?symlink=get + symlinkHeaders *ObjectHeaders // from HEAD/GET with ?symlink=get +} + +// IsEqualTo returns true if both Object instances refer to the same object. +func (o *Object) IsEqualTo(other *Object) bool { + return other.name == o.name && other.c.IsEqualTo(o.c) +} + +// Object returns a handle to the object with the given name within this +// container. This function does not issue any HTTP requests, and therefore cannot +// ensure that the object exists. Use the Exists() function to check for the +// object's existence. +func (c *Container) Object(name string) *Object { + return &Object{c: c, name: name} +} + +// Container returns a handle to the container this object is stored in. +func (o *Object) Container() *Container { + return o.c +} + +// Name returns the object name. This does not parse the name in any way; if you +// want only the basename portion of the object name, use package path from the +// standard library in conjunction with this function. For example: +// +// obj := account.Container("docs").Object("2018-02-10/invoice.pdf") +// obj.Name() //returns "2018-02-10/invoice.pdf" +// path.Base(obj.Name()) //returns "invoice.pdf" +func (o *Object) Name() string { + return o.name +} + +// FullName returns the container name and object name joined together with a +// slash. This identifier is used by Swift in several places (large object +// manifests, symlink targets, etc.) to refer to an object within an account. +// For example: +// +// obj := account.Container("docs").Object("2018-02-10/invoice.pdf") +// obj.Name() //returns "2018-02-10/invoice.pdf" +// obj.FullName() //returns "docs/2018-02-10/invoice.pdf" +func (o *Object) FullName() string { + return o.c.name + "/" + o.name +} + +// Exists checks if this object exists, potentially by issuing a HEAD request +// if no Headers() have been cached yet. +func (o *Object) Exists(ctx context.Context) (bool, error) { + _, err := o.Headers(ctx) + if Is(err, http.StatusNotFound) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +// Headers returns the ObjectHeaders for this object. If the ObjectHeaders +// has not been cached yet, a HEAD request is issued on the object. +// +// For symlinks, this operation returns the metadata for the target object. Use +// Object.SymlinkHeaders() to obtain the metadata for the symlink instead. +// +// This operation fails with http.StatusNotFound if the object does not exist. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (o *Object) Headers(ctx context.Context) (ObjectHeaders, error) { + if o.headers != nil { + return *o.headers, nil + } + + hdr, err := o.fetchHeaders(ctx, nil) + if err != nil { + return ObjectHeaders{}, err + } + o.headers = hdr + return *hdr, nil +} + +func (o *Object) fetchHeaders(ctx context.Context, opts *RequestOptions) (*ObjectHeaders, error) { + resp, err := Request{ + Method: "HEAD", + ContainerName: o.c.name, + ObjectName: o.name, + Options: opts, + // since Openstack LOVES to be inconsistent with everything (incl. itself), + // this returns 200 instead of 204 + ExpectStatusCodes: []int{http.StatusOK}, + DrainResponseBody: true, + }.Do(ctx, o.c.a.backend) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + headers := ObjectHeaders{headersFromHTTP(resp.Header)} + return &headers, headers.Validate() +} + +// Update updates the object's headers using a POST request. To add URL +// parameters, pass a non-nil *RequestOptions. +// +// This operation fails with http.StatusNotFound if the object does not exist. +// +// A successful POST request implies Invalidate() since it may change metadata. +func (o *Object) Update(ctx context.Context, headers ObjectHeaders, opts *RequestOptions) error { + resp, err := Request{ + Method: "POST", + ContainerName: o.c.name, + ObjectName: o.name, + Options: cloneRequestOptions(opts, headers.Headers), + ExpectStatusCodes: []int{http.StatusAccepted}, + }.Do(ctx, o.c.a.backend) + if err == nil { + o.Invalidate() + resp.Body.Close() + } + return err +} + +// UploadOptions invokes advanced behavior in the Object.Upload() method. +type UploadOptions struct { + // When overwriting a large object, delete its segments. This will cause + // Upload() to call into BulkDelete(), so a BulkError may be returned. + DeleteSegments bool +} + +// Upload creates the object using a PUT request. +// +// If you do not have an io.Reader, but you have a []byte or string instance +// containing the object, wrap it in a *bytes.Reader instance like so: +// +// var buffer []byte +// o.Upload(bytes.NewReader(buffer), opts) +// +// //or... +// var buffer string +// o.Upload(bytes.NewReader([]byte(buffer)), opts) +// +// If you have neither an io.Reader nor a []byte or string, but you have a +// function that generates the object's content into an io.Writer, use +// UploadFromWriter instead. +// +// If the object is very large and you want to upload it in segments, use +// LargeObject.Append() instead. See documentation on type LargeObject for +// details. +// +// If content is a *bytes.Reader or a *bytes.Buffer instance, the Content-Length +// and Etag request headers will be computed automatically. Otherwise, it is +// highly recommended that the caller set these headers (if possible) to allow +// the server to check the integrity of the uploaded file. +// +// If Etag and/or Content-Length is supplied and the content does not match +// these parameters, http.StatusUnprocessableEntity is returned. If Etag is not +// supplied and cannot be computed in advance, Upload() will compute the Etag as +// data is read from the io.Reader, and compare the result to the Etag returned +// by Swift, returning ErrChecksumMismatch in case of mismatch. The object will +// have been uploaded at that point, so you will usually want to Delete() it. +// +// This function can be used regardless of whether the object exists or not. +// +// A successful PUT request implies Invalidate() since it may change metadata. +func (o *Object) Upload(ctx context.Context, content io.Reader, opts *UploadOptions, ropts *RequestOptions) error { + if opts == nil { + opts = &UploadOptions{} + } + + ropts = cloneRequestOptions(ropts, nil) + hdr := ObjectHeaders{ropts.Headers} + + if !hdr.SizeBytes().Exists() { + value := tryComputeContentLength(content) + if value != nil { + hdr.SizeBytes().Set(*value) + } + } + + // do not attempt to add the Etag header when we're writing a large object + // manifest; the header refers to the content, but we would be computing the + // manifest's hash instead + isManifestUpload := ropts.Values.Get("multipart-manifest") == "put" || hdr.IsDynamicLargeObject() + + var hasher hash.Hash + if !isManifestUpload { + err := tryComputeEtag(content, hdr) + if err != nil { + return err + } + + // could not compute Etag in advance -> need to check on the fly + if !hdr.Etag().Exists() { + hasher = md5.New() //nolint:gosec // Etag uses md5 + if content != nil { + content = io.TeeReader(content, hasher) + } + } + } + + var lo *LargeObject + if opts.DeleteSegments { + // enumerate segments in large object before overwriting it, but only delete + // the segments after successfully uploading the new object to decrease the + // chance of an inconsistent state following an upload error + var err error + lo, err = o.AsLargeObject(ctx) + switch { + case err == nil: + // okay, delete segments at the end + case errors.Is(err, ErrNotLarge): + // okay, do not try to delete segments + lo = nil + default: + // unexpected error + return err + } + } + + resp, err := Request{ + Method: "PUT", + ContainerName: o.c.name, + ObjectName: o.name, + Options: ropts, + Body: content, + ExpectStatusCodes: []int{201}, + DrainResponseBody: true, + }.Do(ctx, o.c.a.backend) + if err != nil { + return err + } + o.Invalidate() + defer resp.Body.Close() + + if hasher != nil { + expectedEtag := hex.EncodeToString(hasher.Sum(nil)) + if expectedEtag != resp.Header.Get("Etag") { + return ErrChecksumMismatch + } + } + + if opts.DeleteSegments && lo != nil { + _, _, err := lo.object.c.a.BulkDelete(ctx, lo.SegmentObjects(), nil, nil) + if err != nil { + return err + } + } + + return nil +} + +type readerWithLen interface { + // Returns the number of bytes in the unread portion of the buffer. + // Implemented by bytes.Reader, bytes.Buffer and strings.Reader. + Len() int +} + +func tryComputeContentLength(content io.Reader) *uint64 { + if content == nil { + val := uint64(0) + return &val + } else if r, ok := content.(readerWithLen); ok { + val := uint64(r.Len()) + return &val + } + return nil +} + +//nolint:gosec // Etag uses md5 +func tryComputeEtag(content io.Reader, headers ObjectHeaders) error { + h := headers.Etag() + if h.Exists() { + return nil + } + switch r := content.(type) { + case nil: + sum := md5.Sum(nil) + h.Set(hex.EncodeToString(sum[:])) + case *bytes.Buffer: + // bytes.Buffer has a method that returns the unread portion of the buffer, + // so this one is easy + sum := md5.Sum(r.Bytes()) + h.Set(hex.EncodeToString(sum[:])) + case io.ReadSeeker: + // bytes.Reader does not have such a method, but it is an io.Seeker, so we + // can read the entire thing and then seek back to where we started + md5Hash := md5.New() + n, err := io.Copy(md5Hash, r) + if err != nil { + return err + } + _, err = r.Seek(-n, io.SeekCurrent) + if err != nil { + return err + } + h.Set(hex.EncodeToString(md5Hash.Sum(nil))) + } + + return nil +} + +// UploadFromWriter is a variant of Upload that can be used when the object's +// content is generated by some function or package that takes an io.Writer +// instead of supplying an io.Reader. For example: +// +// func greeting(target io.Writer, name string) error { +// _, err := fmt.Fprintf(target, "Hello %s!\n", name) +// return err +// } +// +// obj := container.Object("greeting-for-susan-and-jeffrey") +// err := obj.UploadFromWriter(nil, func(w io.Writer) error { +// err := greeting(w, "Susan") +// if err == nil { +// err = greeting(w, "Jeffrey") +// } +// return err +// }) +// +// If you do not need an io.Writer, always use Upload instead. +func (o *Object) UploadFromWriter(ctx context.Context, opts *UploadOptions, ropts *RequestOptions, callback func(io.Writer) error) error { + reader, writer := io.Pipe() + errChan := make(chan error) + go func() { + err := o.Upload(ctx, reader, opts, ropts) + reader.CloseWithError(err) // stop the writer if it is still writing + errChan <- err + }() + writer.CloseWithError(callback(writer)) // stop the reader if it is still reading + return <-errChan +} + +// DeleteOptions invokes advanced behavior in the Object.Delete() method. +type DeleteOptions struct { + // When deleting a large object, also delete its segments. This will cause + // Delete() to call into BulkDelete(), so a BulkError may be returned. + DeleteSegments bool +} + +// Delete deletes the object using a DELETE request. To add URL parameters, +// pass a non-nil *RequestOptions. +// +// This operation fails with http.StatusNotFound if the object does not exist. +// +// A successful DELETE request implies Invalidate(). +func (o *Object) Delete(ctx context.Context, opts *DeleteOptions, ropts *RequestOptions) error { + if opts == nil { + opts = &DeleteOptions{} + } + if opts.DeleteSegments { + exists, err := o.Exists(ctx) + if err != nil { + return err + } + if exists { + lo, err := o.AsLargeObject(ctx) + switch { + case err == nil: + // is large object - delete segments and the object itself in one step + _, _, err := o.c.a.BulkDelete(ctx, append(lo.SegmentObjects(), o), nil, nil) + o.Invalidate() + return err + case errors.Is(err, ErrNotLarge): + // not a large object - use regular DELETE request + default: + // unexpected error + return err + } + } + } + + resp, err := Request{ + Method: "DELETE", + ContainerName: o.c.name, + ObjectName: o.name, + Options: ropts, + ExpectStatusCodes: []int{http.StatusNoContent}, + }.Do(ctx, o.c.a.backend) + if err == nil { + o.Invalidate() + resp.Body.Close() + } + return err +} + +// Invalidate clears the internal cache of this Object instance. The next call +// to Headers() on this instance will issue a HEAD request on the object. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (o *Object) Invalidate() { + o.headers = nil + o.symlinkHeaders = nil +} + +// Download retrieves the object's contents using a GET request. This returns a +// helper object which allows you to select whether you want an io.ReadCloser +// for reading the object contents progressively, or whether you want the object +// contents collected into a byte slice or string. +// +// reader, err := object.Download(nil).AsReadCloser() +// +// buf, err := object.Download(nil).AsByteSlice() +// +// str, err := object.Download(nil).AsString() +// +// See documentation on type DownloadedObject for details. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (o *Object) Download(ctx context.Context, opts *RequestOptions) DownloadedObject { + resp, err := Request{ + Method: "GET", + ContainerName: o.c.name, + ObjectName: o.name, + Options: opts, + ExpectStatusCodes: []int{http.StatusOK}, + }.Do(ctx, o.c.a.backend) //nolint:bodyclose // body is returned and must be closed by the user + var body io.ReadCloser + if err == nil { + newHeaders := ObjectHeaders{headersFromHTTP(resp.Header)} + err = newHeaders.Validate() + if err == nil { + if opts != nil && opts.Values != nil && opts.Values.Get("symlink") == "get" { + o.symlinkHeaders = &newHeaders + } else { + o.headers = &newHeaders + } + } + body = resp.Body + } + return DownloadedObject{body, err} +} + +// CopyOptions invokes advanced behavior in the Object.Copy() method. +type CopyOptions struct { + // Copy only the object's content, not its metadata. New metadata can always + // be supplied in the RequestOptions argument of Object.CopyTo(). + FreshMetadata bool + // When the source is a symlink, copy the symlink instead of the target object. + ShallowCopySymlinks bool +} + +// CopyTo copies the object on the server side using a COPY request. +// +// A successful COPY implies target.Invalidate() since it may change the +// target's metadata. +func (o *Object) CopyTo(ctx context.Context, target *Object, opts *CopyOptions, ropts *RequestOptions) error { + ropts = cloneRequestOptions(ropts, nil) + ropts.Headers.Set("Destination", target.FullName()) + if o.c.a.name != target.c.a.name { + ropts.Headers.Set("Destination-Account", target.c.a.name) + } + if opts != nil { + if opts.FreshMetadata { + ropts.Headers.Set("X-Fresh-Metadata", "true") + } + if opts.ShallowCopySymlinks { + ropts.Values.Set("symlink", "get") + } + } + + resp, err := Request{ + Method: "COPY", + ContainerName: o.c.name, + ObjectName: o.name, + Options: ropts, + ExpectStatusCodes: []int{http.StatusCreated}, + DrainResponseBody: true, + }.Do(ctx, o.c.a.backend) + if err == nil { + target.Invalidate() + resp.Body.Close() + } + return err +} + +// SymlinkOptions invokes advanced behavior in the Object.SymlinkTo() method. +type SymlinkOptions struct { + // When overwriting a large object, delete its segments. This will cause + // SymlinkTo() to call into BulkDelete(), so a BulkError may be returned. + DeleteSegments bool +} + +// SymlinkTo creates the object as a symbolic link to another object using a PUT +// request. Like Object.Upload(), this method works regardless of whether the +// object already exists or not. Existing object contents will be overwritten by +// this operation. +// +// A successful PUT request implies Invalidate() since it may change metadata. +func (o *Object) SymlinkTo(ctx context.Context, target *Object, opts *SymlinkOptions, ropts *RequestOptions) error { + ropts = cloneRequestOptions(ropts, nil) + ropts.Headers.Set("X-Symlink-Target", target.FullName()) + if !target.c.a.IsEqualTo(o.c.a) { + ropts.Headers.Set("X-Symlink-Target-Account", target.c.a.Name()) + } + if ropts.Headers.Get("Content-Type") == "" { + // recommended Content-Type for symlinks as per + // + ropts.Headers.Set("Content-Type", "application/symlink") + } + + var uopts *UploadOptions + if opts != nil { + uopts = &UploadOptions{ + DeleteSegments: opts.DeleteSegments, + } + } + + return o.Upload(ctx, nil, uopts, ropts) +} + +// SymlinkHeaders is similar to Headers, but if the object is a symlink, it +// returns the metadata of the symlink rather than the metadata of the target. +// It also returns a reference to the target object. +// +// If this object is not a symlink, Object.SymlinkHeaders() returns the same +// ObjectHeaders as Object.Headers(), and a nil target object. +// +// In a nutshell, if Object.Headers() is like os.Stat(), then +// Object.SymlinkHeaders() is like os.Lstat(). +// +// If you do not know whether a given object is a symlink or not, it's a good +// idea to call Object.SymlinkHeaders() first: If the object turns out not to be +// a symlink, the cache for Object.Headers() has already been populated. +// +// This operation fails with http.StatusNotFound if the object does not exist. +// +// WARNING: This method is not thread-safe. Calling it concurrently on the same +// object results in undefined behavior. +func (o *Object) SymlinkHeaders(ctx context.Context) (headers ObjectHeaders, target *Object, err error) { + if o.symlinkHeaders == nil { + o.symlinkHeaders, err = o.fetchHeaders(ctx, &RequestOptions{ + Values: url.Values{"symlink": []string{"get"}}, + }) + if err != nil { + return ObjectHeaders{}, nil, err + } + } + + // is this a symlink? + targetFullName := o.symlinkHeaders.Get("X-Symlink-Target") + if targetFullName == "" { + // not a symlink - the o.symlinkHeaders are just the regular headers + o.headers = o.symlinkHeaders + return *o.headers, nil, nil + } + fields := strings.SplitN(targetFullName, "/", 2) + if len(fields) < 2 { + return ObjectHeaders{}, nil, MalformedHeaderError{ + Key: "X-Symlink-Target", + ParseError: fmt.Errorf("expected \"container/object\", got \"%s\"", targetFullName), + } + } + + // cross-account symlink? + accountName := o.symlinkHeaders.Get("X-Symlink-Target-Account") + targetAccount := o.c.a + if accountName != "" && accountName != targetAccount.Name() { + targetAccount = targetAccount.SwitchAccount(accountName) + } + target = targetAccount.Container(fields[0]).Object(fields[1]) + return *o.symlinkHeaders, target, nil +} + +// URL returns the canonical URL for the object on the server. This is +// particularly useful when the ReadACL on the account or container is set to +// allow anonymous read access. +func (o *Object) URL() (string, error) { + return Request{ + ContainerName: o.c.name, + ObjectName: o.name, + }.URL(o.c.a.backend, nil) +} + +// Returns true if string is contained in slice +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +// TempURL is like Object.URL, but includes a token with a limited lifetime (as +// specified by the `expires` argument) that permits anonymous access to this +// object using the given HTTP method. This works only when the tempurl +// middleware is set up on the server, and if the given `key` matches one of the +// tempurl keys for this object's container or account. +// +// For example, if the ReadACL both on the account and container do not permit +// anonymous read access (which is the default behavior): +// +// var o *schwift.Object +// ... +// resp, err := http.Get(o.URL()) +// //After this, resp.StatusCode == 401 (Unauthorized) +// //because anonymous access is forbidden. +// +// //But if the container or account has a tempurl key... +// key := "supersecretkey" +// hdr := NewContainerHeaders() +// hdr.TempURLKey().Set(key) +// c := o.Container() +// err := c.Update(ctx, hdr, nil) +// +// //...we can use it to generate temporary URLs. +// url := o.TempURL(ctx, key, "GET", time.Now().Add(10 * time.Minute)) +// resp, err := http.Get(url) +// //This time, resp.StatusCode == 200 because the URL includes a token. +func (o *Object) TempURL(ctx context.Context, key, method string, expires time.Time) (string, error) { + urlStr, err := o.URL() + if err != nil { + return "", err + } + u, err := url.Parse(urlStr) + if err != nil { + return "", err + } + + capabilities, err := o.c.a.Capabilities(ctx) + if err != nil { + return "", err + } + allowedDigest := capabilities.TempURL.AllowedDigests + + var mac hash.Hash + switch { + case contains(allowedDigest, "sha256"): + mac = hmac.New(sha256.New, []byte(key)) + case contains(allowedDigest, "sha1"): + mac = hmac.New(sha1.New, []byte(key)) + default: + return "", fmt.Errorf("schwift supports sha1 and sha256 digests but the Swift server only supports: %s", strings.Join(allowedDigest, ", ")) + } + + payload := fmt.Sprintf("%s\n%d\n%s", method, expires.Unix(), u.Path) + mac.Write([]byte(payload)) + signature := hex.EncodeToString(mac.Sum(nil)) + + u.RawQuery = fmt.Sprintf("temp_url_sig=%s&temp_url_expires=%d", + signature, expires.Unix()) + return u.String(), nil +} diff --git a/vendor/github.com/majewsky/schwift/v2/object_iterator.go b/vendor/github.com/majewsky/schwift/v2/object_iterator.go new file mode 100644 index 0000000..314b9bf --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/object_iterator.go @@ -0,0 +1,253 @@ +/******************************************************************************* +* +* Copyright 2018 Stefan Majewsky +* +* This program is free software: you can redistribute it and/or modify it under +* the terms of the GNU General Public License as published by the Free Software +* Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +* A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +*******************************************************************************/ + +package schwift + +import ( + "context" + "fmt" + "regexp" + "time" +) + +// ObjectInfo is a result type returned by ObjectIterator for detailed +// object listings. The metadata in this type is a subset of Object.Headers(), +// but since it is returned as part of the detailed object listing, it can be +// obtained without making additional HEAD requests on the object(s). +type ObjectInfo struct { + Object *Object + SizeBytes uint64 + ContentType string + Etag string + LastModified time.Time + // SymlinkTarget is only set for symlinks. + SymlinkTarget *Object + // If the ObjectInfo refers to an actual object, then SubDirectory is empty. + // If the ObjectInfo refers to a pseudo-directory, then SubDirectory contains + // the path of the pseudo-directory and all other fields are nil/zero/empty. + // Pseudo-directories will only be reported for ObjectIterator.Delimiter != "". + SubDirectory string +} + +// ObjectIterator iterates over the objects in a container. It is typically +// constructed with the Container.Objects() method. For example: +// +// //either this... +// iter := container.Objects() +// iter.Prefix = "test-" +// objects, err := iter.Collect() +// +// //...or this +// objects, err := schwift.ObjectIterator{ +// Container: container, +// Prefix: "test-", +// }.Collect() +// +// When listing objects via a GET request on the container, you can choose to +// receive object names only (via the methods without the "Detailed" suffix), +// or object names plus some basic metadata fields (via the methods with the +// "Detailed" suffix). See struct ObjectInfo for which metadata is returned. +// +// To obtain any other metadata, you can call Object.Headers() on the result +// object, but this will issue a separate HEAD request for each object. +// +// Use the "Detailed" methods only when you use the extra metadata in struct +// ObjectInfo; detailed GET requests are more expensive than simple ones that +// return only object names. +// +// Note that, when Delimiter is set, instances of *Object that you receive from +// the iterator may refer to a pseudo-directory instead of an actual object, in +// which case Exists() will return false. +type ObjectIterator struct { + Container *Container + // When Prefix is set, only objects whose name starts with this string are + // returned. + Prefix string + // When Delimiter is set, objects whose name contains this string (after the + // prefix, if any) will be condensed into pseudo-directories in the result. + // See documentation for Swift for details. + Delimiter string + // Options may contain additional headers and query parameters for the GET request. + Options *RequestOptions + + base *iteratorBase +} + +func (i *ObjectIterator) getBase() *iteratorBase { + if i.base == nil { + i.base = &iteratorBase{i: i} + } + return i.base +} + +// NextPage queries Swift for the next page of object names. If limit is +// >= 0, not more than that many object names will be returned at once. Note +// that the server also has a limit for how many objects to list in one +// request; the lower limit wins. +// +// The end of the object listing is reached when an empty list is returned. +// +// This method offers maximal flexibility, but most users will prefer the +// simpler interfaces offered by Collect() and Foreach(). +func (i *ObjectIterator) NextPage(ctx context.Context, limit int) ([]*Object, error) { + names, err := i.getBase().nextPage(ctx, limit) + if err != nil { + return nil, err + } + + result := make([]*Object, len(names)) + for idx, name := range names { + result[idx] = i.Container.Object(name) + } + return result, nil +} + +// The symlink_path attribute looks like "/v1/AUTH_foo/containername/obje/ctna/me". +var symlinkPathRx = regexp.MustCompile(`^/v1/([^/]+)/([^/]+)/(.+)$`) + +// NextPageDetailed is like NextPage, but includes basic metadata. +func (i *ObjectIterator) NextPageDetailed(ctx context.Context, limit int) ([]ObjectInfo, error) { + b := i.getBase() + + var document []struct { + // either all of this: + SizeBytes uint64 `json:"bytes"` + ContentType string `json:"content_type"` + Etag string `json:"hash"` + LastModifiedStr string `json:"last_modified"` + Name string `json:"name"` + SymlinkPath string `json:"symlink_path"` + // or just this: + Subdir string `json:"subdir"` + } + err := b.nextPageDetailed(ctx, limit, &document) + if err != nil { + return nil, err + } + if len(document) == 0 { + b.setMarker("") // indicate EOF to iteratorBase + return nil, nil + } + + result := make([]ObjectInfo, len(document)) + marker := "" + for idx, data := range document { + if data.Subdir == "" { + marker = data.Name + result[idx].Object = i.Container.Object(data.Name) + result[idx].ContentType = data.ContentType + result[idx].Etag = data.Etag + result[idx].SizeBytes = data.SizeBytes + result[idx].LastModified, err = time.Parse(time.RFC3339Nano, data.LastModifiedStr+"Z") + if err != nil { + // this error is sufficiently obscure that we don't need to expose a type for it + return nil, fmt.Errorf("bad field objects[%d].last_modified: %s", idx, err.Error()) + } + if data.SymlinkPath != "" { + match := symlinkPathRx.FindStringSubmatch(data.SymlinkPath) + if match == nil { + // like above + return nil, fmt.Errorf("bad field objects[%d].symlink_path: %q", idx, data.SymlinkPath) + } + a := i.Container.a + if a.Name() != match[1] { + a = a.SwitchAccount(match[1]) + } + result[idx].SymlinkTarget = a.Container(match[2]).Object(match[3]) + } + } else { + marker = data.Subdir + result[idx].SubDirectory = data.Subdir + } + } + + b.setMarker(marker) + return result, nil +} + +// Foreach lists the object names matching this iterator and calls the +// callback once for every object. Iteration is aborted when a GET request fails, +// or when the callback returns a non-nil error. +func (i *ObjectIterator) Foreach(ctx context.Context, callback func(*Object) error) error { + for { + objects, err := i.NextPage(ctx, -1) + if err != nil { + return err + } + if len(objects) == 0 { + return nil // EOF + } + for _, o := range objects { + err := callback(o) + if err != nil { + return err + } + } + } +} + +// ForeachDetailed is like Foreach, but includes basic metadata. +func (i *ObjectIterator) ForeachDetailed(ctx context.Context, callback func(ObjectInfo) error) error { + for { + infos, err := i.NextPageDetailed(ctx, -1) + if err != nil { + return err + } + if len(infos) == 0 { + return nil // EOF + } + for _, ci := range infos { + err := callback(ci) + if err != nil { + return err + } + } + } +} + +// Collect lists all object names matching this iterator. For large sets of +// objects that cannot be retrieved at once, Collect handles paging behind +// the scenes. The return value is always the complete set of objects. +func (i *ObjectIterator) Collect(ctx context.Context) ([]*Object, error) { + var result []*Object + for { + objects, err := i.NextPage(ctx, -1) + if err != nil { + return nil, err + } + if len(objects) == 0 { + return result, nil // EOF + } + result = append(result, objects...) + } +} + +// CollectDetailed is like Collect, but includes basic metadata. +func (i *ObjectIterator) CollectDetailed(ctx context.Context) ([]ObjectInfo, error) { + var result []ObjectInfo + for { + infos, err := i.NextPageDetailed(ctx, -1) + if err != nil { + return nil, err + } + if len(infos) == 0 { + return result, nil // EOF + } + result = append(result, infos...) + } +} diff --git a/vendor/github.com/majewsky/schwift/v2/request.go b/vendor/github.com/majewsky/schwift/v2/request.go new file mode 100644 index 0000000..ce9e8ac --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/request.go @@ -0,0 +1,193 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +import ( + "context" + "io" + "net/http" + "net/url" + "strings" +) + +// RequestOptions is used to pass additional headers and values to a request. +// +// When preparing a RequestOptions instance with additional headers, the +// preferred way is to create an AccountHeaders, ContainerHeaders and +// ObjectHeaders instance and use the type-safe API on these types. Then use the +// ToOpts() method on that instance. For example: +// +// hdr := NewObjectHeaders() +// hdr.ContentType().Set("image/png") +// hdr.Metadata().Set("color", "blue") +// opts := hdr.ToOpts() //type *schwift.RequestOptions +type RequestOptions struct { + Headers Headers + Values url.Values +} + +func cloneRequestOptions(orig *RequestOptions, additional Headers) *RequestOptions { + result := RequestOptions{ + Headers: make(Headers), + Values: make(url.Values), + } + if orig != nil { + for k, v := range orig.Headers { + result.Headers[k] = v + } + for k, v := range orig.Values { + result.Values[k] = v + } + } + for k, v := range additional { + result.Headers[k] = v + } + return &result +} + +// Request contains the parameters that can be set in a request to the Swift API. +type Request struct { + Method string // "GET", "HEAD", "PUT", "POST" or "DELETE" + ContainerName string // empty for requests on accounts + ObjectName string // empty for requests on accounts/containers + Options *RequestOptions + Body io.Reader + // ExpectStatusCodes can be left empty to disable this check, otherwise + // schwift.UnexpectedStatusCodeError may be returned. + ExpectStatusCodes []int + // DrainResponseBody can be set if the caller is not interested in the + // response body. This is implied for Response.StatusCode == 204. + DrainResponseBody bool +} + +// URL returns the full URL for this request. +func (r Request) URL(backend Backend, values url.Values) (string, error) { + uri, err := url.Parse(backend.EndpointURL()) + if err != nil { + return "", err + } + if !strings.HasSuffix(uri.Path, "/") { + uri.Path += "/" + } + + if r.ContainerName == "" { + if r.ObjectName != "" { + return "", ErrNoContainerName + } + } else { + if strings.Contains(r.ContainerName, "/") { + return "", ErrMalformedContainerName + } + // Encode path so that double slashes are encoded and handled correct by backend server + uri.RawPath = uri.Path + r.ContainerName + "/" + url.PathEscape(r.ObjectName) + uri.Path = uri.Path + r.ContainerName + "/" + r.ObjectName + } + + uri.RawQuery = values.Encode() + return uri.String(), nil +} + +// Do executes this request on the given Backend. +func (r Request) Do(ctx context.Context, backend Backend) (*http.Response, error) { + // build URL + var values url.Values + if r.Options != nil { + values = r.Options.Values + } + uri, err := r.URL(backend, values) + if err != nil { + return nil, err + } + + // build request + req, err := http.NewRequestWithContext(ctx, r.Method, uri, r.Body) + if err != nil { + return nil, err + } + + if r.Options != nil { + for k, v := range r.Options.Headers { + req.Header[k] = []string{v} + } + } + if r.Body != nil { + req.Header.Set("Expect", "100-continue") + } + + resp, err := backend.Do(req) + if err != nil { + return nil, err + } + + // return success if error code matches expectation + if len(r.ExpectStatusCodes) == 0 { + // check disabled -> return response unaltered + return resp, nil + } + for _, code := range r.ExpectStatusCodes { + if code == resp.StatusCode { + var err error + if r.DrainResponseBody || resp.StatusCode == http.StatusNoContent { + err = drainResponseBody(resp) + } + return resp, err + } + } + + // unexpected status code -> generate error + buf, err := collectResponseBody(resp) + if err != nil { + return nil, err + } + return nil, UnexpectedStatusCodeError{ + Method: r.Method, + Target: describeTarget(r.ContainerName, r.ObjectName), + ExpectedStatusCodes: r.ExpectStatusCodes, + ActualResponse: resp, + ResponseBody: buf, + } +} + +// Builds a value for the UnexpectedStatusCodeError.Target attribute. +func describeTarget(containerName, objectName string) string { + switch { + case containerName == "": + return "" + case objectName == "": + return containerName + default: + return containerName + "/" + objectName + } +} + +func drainResponseBody(r *http.Response) error { + _, err := io.Copy(io.Discard, r.Body) + if err != nil { + return err + } + return r.Body.Close() +} + +func collectResponseBody(r *http.Response) ([]byte, error) { + buf, err := io.ReadAll(r.Body) + if err != nil { + return nil, err + } + return buf, r.Body.Close() +} diff --git a/vendor/github.com/majewsky/schwift/v2/version.go b/vendor/github.com/majewsky/schwift/v2/version.go new file mode 100644 index 0000000..82b29ca --- /dev/null +++ b/vendor/github.com/majewsky/schwift/v2/version.go @@ -0,0 +1,22 @@ +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package schwift + +// Version contains the version number of Schwift. +const Version = "1.0.0" diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/actors/cleaner.go b/vendor/github.com/sapcc/swift-http-import/pkg/actors/cleaner.go index 2d2fe2a..e3db6d4 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/actors/cleaner.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/actors/cleaner.go @@ -23,7 +23,7 @@ import ( "context" "sort" - "github.com/majewsky/schwift" + "github.com/majewsky/schwift/v2" "github.com/sapcc/go-bits/errext" "github.com/sapcc/go-bits/logg" @@ -97,12 +97,12 @@ func (c *Cleaner) Run(ctx context.Context) { return } if !isJobFailed[job] { - c.performCleanup(job, transferred) + c.performCleanup(ctx, job, transferred) } } } -func (c *Cleaner) performCleanup(job *objects.Job, isFileTransferred map[string]bool) { +func (c *Cleaner) performCleanup(ctx context.Context, job *objects.Job, isFileTransferred map[string]bool) { // collect objects to cleanup var objs []*schwift.Object for objectName := range job.Target.FileExists { @@ -127,7 +127,7 @@ func (c *Cleaner) performCleanup(job *objects.Job, isFileTransferred map[string] } case objects.DeleteUnknownFiles: - numDeleted, _, err := job.Target.Container.Account().BulkDelete(objs, nil, nil) + numDeleted, _, err := job.Target.Container.Account().BulkDelete(ctx, objs, nil, nil) c.Report <- ReportEvent{IsCleanup: true, CleanedUpObjectCount: int64(numDeleted)} if err != nil { logg.Error("cleanup of %d objects on target side failed: %s", (len(objs) - numDeleted), err.Error()) diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/objects/config.go b/vendor/github.com/sapcc/swift-http-import/pkg/objects/config.go index 5111758..536d2c3 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/objects/config.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/objects/config.go @@ -20,12 +20,13 @@ package objects import ( + "context" "fmt" "os" "regexp" "time" - "github.com/majewsky/schwift" + "github.com/majewsky/schwift/v2" yaml "gopkg.in/yaml.v2" "github.com/sapcc/go-bits/secrets" @@ -46,7 +47,7 @@ type Configuration struct { } // ReadConfiguration reads the configuration file. -func ReadConfiguration(path string) (*Configuration, []error) { +func ReadConfiguration(ctx context.Context, path string) (*Configuration, []error) { configBytes, err := os.ReadFile(path) if err != nil { return nil, []error{err} @@ -80,21 +81,18 @@ func ReadConfiguration(path string) (*Configuration, []error) { sl := cfg.Swift sl.ContainerName = secrets.FromEnv(cntrName) sl.ObjectNamePrefix = "" - err := sl.Connect(cntrName) + err := sl.Connect(ctx, cntrName) if err == nil { gpgCacheContainer = sl.Container } else { errors = append(errors, err) } } - gpgKeyRing := util.NewGPGKeyRing(gpgCacheContainer, cfg.GPG.KeyserverURLPatterns) + gpgKeyRing := util.NewGPGKeyRing(ctx, gpgCacheContainer, cfg.GPG.KeyserverURLPatterns) for idx, jobConfig := range cfg.JobConfigs { jobConfig.gpgKeyRing = gpgKeyRing - job, jobErrors := jobConfig.Compile( - fmt.Sprintf("swift.jobs[%d]", idx), - cfg.Swift, - ) + job, jobErrors := jobConfig.Compile(ctx, fmt.Sprintf("swift.jobs[%d]", idx), cfg.Swift) cfg.Jobs = append(cfg.Jobs, job) errors = append(errors, jobErrors...) } @@ -223,7 +221,7 @@ type Job struct { } // Compile validates the given JobConfiguration, then creates and prepares a Job from it. -func (cfg JobConfiguration) Compile(name string, swift SwiftLocation) (job *Job, errors []error) { +func (cfg JobConfiguration) Compile(ctx context.Context, name string, swift SwiftLocation) (job *Job, errors []error) { jobSrc := cfg.Source.Source if jobSrc == nil { errors = append(errors, fmt.Errorf("missing value for %s.from", name)) @@ -334,22 +332,22 @@ func (cfg JobConfiguration) Compile(name string, swift SwiftLocation) (job *Job, } // ensure that connection to Swift exists and that target container(s) is/are available - err := job.Source.Connect(name + ".from") + err := job.Source.Connect(ctx, name+".from") if err != nil { errors = append(errors, err) } - err = job.Target.Connect(name + ".to") + err = job.Target.Connect(ctx, name+".to") if err != nil { errors = append(errors, err) } if job.Target.Account != nil && job.Segmenting != nil { - job.Segmenting.Container, err = job.Target.Account.Container(job.Segmenting.ContainerName).EnsureExists() + job.Segmenting.Container, err = job.Target.Account.Container(job.Segmenting.ContainerName).EnsureExists(ctx) if err != nil { errors = append(errors, err) } } - err = job.Target.DiscoverExistingFiles(job.Matcher) + err = job.Target.DiscoverExistingFiles(ctx, job.Matcher) if err != nil { errors = append(errors, err) } diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/objects/debian.go b/vendor/github.com/sapcc/swift-http-import/pkg/objects/debian.go index f17959a..ef1d9bb 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/objects/debian.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/objects/debian.go @@ -27,7 +27,7 @@ import ( "regexp" "strings" - "github.com/majewsky/schwift" + "github.com/majewsky/schwift/v2" "github.com/sapcc/go-bits/logg" "pault.ag/go/debian/control" @@ -79,8 +79,8 @@ func (s *DebianSource) Validate(name string) []error { } // Connect implements the Source interface. -func (s *DebianSource) Connect(name string) error { - return s.urlSource.Connect(name) +func (s *DebianSource) Connect(ctx context.Context, name string) error { + return s.urlSource.Connect(ctx, name) } // ListEntries implements the Source interface. diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/objects/file.go b/vendor/github.com/sapcc/swift-http-import/pkg/objects/file.go index 45303da..01b0959 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/objects/file.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/objects/file.go @@ -26,7 +26,7 @@ import ( "net/http" "time" - "github.com/majewsky/schwift" + "github.com/majewsky/schwift/v2" "github.com/sapcc/go-bits/logg" "github.com/sapcc/swift-http-import/pkg/util" @@ -91,7 +91,7 @@ func (f File) PerformTransfer(ctx context.Context) (transferResult TransferResul } // can only transfer as a symlink if the target server supports it - capabilities, err := f.Job.Target.Container.Account().Capabilities() + capabilities, err := f.Job.Target.Container.Account().Capabilities(ctx) if err != nil { logg.Fatal("query /info on target failed: %s", err.Error()) } @@ -115,7 +115,7 @@ func (f File) PerformTransfer(ctx context.Context) (transferResult TransferResul logg.Debug("considering transfer of %s", object.FullName()) // query the file metadata at the target - hdr, currentSymlinkTarget, err := object.SymlinkHeaders() + hdr, currentSymlinkTarget, err := object.SymlinkHeaders(ctx) if err != nil { if schwift.Is(err, http.StatusNotFound) { hdr = schwift.NewObjectHeaders() @@ -134,7 +134,7 @@ func (f File) PerformTransfer(ctx context.Context) (transferResult TransferResul // if we want to upload a symlink, we can skip the whole Last-Modified/Etag // shebang and straight-up compare the symlink target if f.Spec.SymlinkTargetPath != "" { - return f.uploadSymlink(currentSymlinkTarget, hdr.IsLargeObject()), 0 + return f.uploadSymlink(ctx, currentSymlinkTarget, hdr.IsLargeObject()), 0 } // retrieve object from source, taking advantage of Etag and Last-Modified where possible @@ -201,9 +201,9 @@ func (f File) PerformTransfer(ctx context.Context) (transferResult TransferResul var ok bool size := sourceState.SizeBytes if f.Job.Segmenting != nil && size > 0 && uint64(size) >= f.Job.Segmenting.MinObjectSize { - ok = f.uploadLargeObject(body, uploadHeaders, hdr.IsLargeObject()) + ok = f.uploadLargeObject(ctx, body, uploadHeaders, hdr.IsLargeObject()) } else { - ok = f.uploadNormalObject(body, uploadHeaders, hdr.IsLargeObject()) + ok = f.uploadNormalObject(ctx, body, uploadHeaders, hdr.IsLargeObject()) } if ok { @@ -212,7 +212,7 @@ func (f File) PerformTransfer(ctx context.Context) (transferResult TransferResul return TransferFailed, 0 } -func (f File) uploadSymlink(previousTarget *schwift.Object, cleanupOldSegments bool) TransferResult { +func (f File) uploadSymlink(ctx context.Context, previousTarget *schwift.Object, cleanupOldSegments bool) TransferResult { object := f.TargetObject() newTarget := f.Job.Target.ObjectAtPath(f.Spec.SymlinkTargetPath) @@ -221,14 +221,14 @@ func (f File) uploadSymlink(previousTarget *schwift.Object, cleanupOldSegments b return TransferSkipped } - err := object.SymlinkTo(newTarget, &schwift.SymlinkOptions{ + err := object.SymlinkTo(ctx, newTarget, &schwift.SymlinkOptions{ DeleteSegments: cleanupOldSegments, }, nil) if err == nil { return TransferSuccess } - cleanupFailedUpload(object) + cleanupFailedUpload(ctx, object) return TransferFailed } @@ -268,9 +268,9 @@ func (s FileSpec) toTransferFormat(requestHeaders schwift.ObjectHeaders) (io.Rea // indicate Too Many Requests. const StatusSwiftRateLimit = 498 -func (f File) uploadNormalObject(body io.Reader, hdr schwift.ObjectHeaders, cleanupOldSegments bool) (ok bool) { +func (f File) uploadNormalObject(ctx context.Context, body io.Reader, hdr schwift.ObjectHeaders, cleanupOldSegments bool) (ok bool) { object := f.TargetObject() - err := object.Upload(body, &schwift.UploadOptions{ + err := object.Upload(ctx, body, &schwift.UploadOptions{ DeleteSegments: cleanupOldSegments, }, hdr.ToOpts()) if err == nil { @@ -285,14 +285,14 @@ func (f File) uploadNormalObject(body io.Reader, hdr schwift.ObjectHeaders, clea return false } - cleanupFailedUpload(object) + cleanupFailedUpload(ctx, object) return false } -func (f File) uploadLargeObject(body io.Reader, hdr schwift.ObjectHeaders, cleanupOldSegments bool) (ok bool) { +func (f File) uploadLargeObject(ctx context.Context, body io.Reader, hdr schwift.ObjectHeaders, cleanupOldSegments bool) bool { object := f.TargetObject() - lo, err := object.AsNewLargeObject(schwift.SegmentingOptions{ + lo, err := object.AsNewLargeObject(ctx, schwift.SegmentingOptions{ SegmentContainer: f.Job.Segmenting.Container, Strategy: schwift.StaticLargeObject, }, &schwift.TruncateOptions{ @@ -303,10 +303,10 @@ func (f File) uploadLargeObject(body io.Reader, hdr schwift.ObjectHeaders, clean if hdr.ExpiresAt().Exists() { XDeleteAtHeader.ExpiresAt().Set(hdr.ExpiresAt().Get()) } - err = lo.Append(body, int64(f.Job.Segmenting.SegmentSize), XDeleteAtHeader.ToOpts()) + err = lo.Append(ctx, body, int64(f.Job.Segmenting.SegmentSize), XDeleteAtHeader.ToOpts()) } if err == nil { - err = lo.WriteManifest(hdr.ToOpts()) + err = lo.WriteManifest(ctx, hdr.ToOpts()) } if err == nil { logg.Info("PUT %s has created a Static Large Object with segments in %s/%s/", @@ -318,13 +318,13 @@ func (f File) uploadLargeObject(body io.Reader, hdr schwift.ObjectHeaders, clean logg.Error("PUT %s as Static Large Object failed: %s", object.FullName(), err.Error()) // file was not transferred correctly - cleanup manifest and segments - cleanupFailedUpload(object) + cleanupFailedUpload(ctx, object) return false } -func cleanupFailedUpload(object *schwift.Object) { +func cleanupFailedUpload(ctx context.Context, object *schwift.Object) { // file was not transferred correctly - cleanup manifest and segments - err := object.Delete(&schwift.DeleteOptions{ + err := object.Delete(ctx, &schwift.DeleteOptions{ DeleteSegments: true, }, nil) if err != nil && !schwift.Is(err, http.StatusNotFound) { diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/objects/github.go b/vendor/github.com/sapcc/swift-http-import/pkg/objects/github.go index 744f0a2..693a75c 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/objects/github.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/objects/github.go @@ -29,8 +29,8 @@ import ( "strconv" "time" - "github.com/google/go-github/v62/github" - "github.com/majewsky/schwift" + "github.com/google/go-github/v63/github" + "github.com/majewsky/schwift/v2" "github.com/sapcc/go-api-declarations/bininfo" "github.com/sapcc/go-bits/secrets" "golang.org/x/oauth2" @@ -103,11 +103,11 @@ func (s *GithubReleaseSource) Validate(name string) []error { } // Connect implements the Source interface. -func (s *GithubReleaseSource) Connect(name string) error { +func (s *GithubReleaseSource) Connect(ctx context.Context, name string) error { c := http.DefaultClient if s.Token != "" { src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(s.Token)}) - c = oauth2.NewClient(context.Background(), src) + c = oauth2.NewClient(ctx, src) } if s.url.Hostname() != "github.com" { // baseURL is s.url without the Path (//). @@ -132,8 +132,8 @@ func (s *GithubReleaseSource) ListEntries(_ context.Context, directoryPath strin } // ListAllFiles implements the Source interface. -func (s *GithubReleaseSource) ListAllFiles(_ context.Context, out chan<- FileSpec) *ListEntriesError { - releases, err := s.getReleases() +func (s *GithubReleaseSource) ListAllFiles(ctx context.Context, out chan<- FileSpec) *ListEntriesError { + releases, err := s.getReleases(ctx) if err != nil { return &ListEntriesError{ Location: s.url.String(), @@ -217,7 +217,7 @@ func (s *GithubReleaseSource) GetFile(_ context.Context, path string, requestHea }, nil } -func (s *GithubReleaseSource) getReleases() ([]*github.RepositoryRelease, error) { +func (s *GithubReleaseSource) getReleases(ctx context.Context) ([]*github.RepositoryRelease, error) { var result []*github.RepositoryRelease // Set higher value than default (30) for results per page to avoid exceeding API rate limit. @@ -229,7 +229,7 @@ func (s *GithubReleaseSource) getReleases() ([]*github.RepositoryRelease, error) err error ) listOpts.Page = resp.NextPage - rL, resp, err = s.client.Repositories.ListReleases(context.Background(), s.owner, s.repo, listOpts) + rL, resp, err = s.client.Repositories.ListReleases(ctx, s.owner, s.repo, listOpts) if err != nil { return nil, err } diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/objects/source.go b/vendor/github.com/sapcc/swift-http-import/pkg/objects/source.go index 75af5e9..1507765 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/objects/source.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/objects/source.go @@ -37,7 +37,7 @@ import ( "golang.org/x/net/html" "golang.org/x/net/html/atom" - "github.com/majewsky/schwift" + "github.com/majewsky/schwift/v2" "github.com/sapcc/go-api-declarations/bininfo" "github.com/sapcc/go-bits/logg" @@ -49,7 +49,7 @@ type Source interface { // Validate reports errors if this source is malspecified. Validate(name string) []error // Connect performs source-specific one-time setup. - Connect(name string) error + Connect(ctx context.Context, name string) error // ListAllFiles returns all files in the source (as paths relative to the // source's root). If this returns ErrListAllFilesNotSupported, ListEntries // must be used instead. @@ -183,7 +183,7 @@ func (u *URLSource) Validate(name string) (result []error) { } // Connect implements the Source interface. -func (u *URLSource) Connect(name string) error { +func (u *URLSource) Connect(_ context.Context, name string) error { tlsConfig := &tls.Config{} //nolint:gosec // only used in HTTP client, where stdlib auto-chooses strong TLS versions if u.ClientCertificatePath != "" { @@ -360,7 +360,7 @@ func (u URLSource) GetFile(ctx context.Context, filePath string, requestHeaders return nil, FileState{}, fmt.Errorf("skipping %s: GET failed: %s", uri, err.Error()) } - if response.StatusCode != 200 && response.StatusCode != 304 { + if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotModified { return nil, FileState{}, fmt.Errorf( "skipping %s: GET returned unexpected status code: expected 200 or 304, but got %d", uri, response.StatusCode, @@ -372,7 +372,7 @@ func (u URLSource) GetFile(ctx context.Context, filePath string, requestHeaders LastModified: response.Header.Get("Last-Modified"), SizeBytes: response.ContentLength, ExpiryTime: nil, // no way to get this information via HTTP only - SkipTransfer: response.StatusCode == 304, + SkipTransfer: response.StatusCode == http.StatusNotModified, ContentType: response.Header.Get("Content-Type"), }, nil } diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/objects/swift.go b/vendor/github.com/sapcc/swift-http-import/pkg/objects/swift.go index a0cccac..b5b471a 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/objects/swift.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/objects/swift.go @@ -28,11 +28,11 @@ import ( "strings" "time" - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack" - "github.com/gophercloud/utils/client" - "github.com/majewsky/schwift" - "github.com/majewsky/schwift/gopherschwift" + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/utils/v2/client" + "github.com/majewsky/schwift/v2" + "github.com/majewsky/schwift/v2/gopherschwift" "github.com/sapcc/go-api-declarations/bininfo" "github.com/sapcc/go-bits/logg" "github.com/sapcc/go-bits/secrets" @@ -157,7 +157,7 @@ func (l logger) Printf(format string, args ...interface{}) { } // Connect implements the Source interface. It establishes the connection to Swift. -func (s *SwiftLocation) Connect(name string) error { +func (s *SwiftLocation) Connect(ctx context.Context, name string) error { if s.Account != nil { return nil } @@ -206,7 +206,7 @@ func (s *SwiftLocation) Connect(name string) error { } } - err = openstack.Authenticate(provider, authOptions) + err = openstack.Authenticate(ctx, provider, authOptions) if err != nil { if authOptions.ApplicationCredentialSecret != "" { return fmt.Errorf("cannot authenticate to %s using application credential: %s", @@ -246,7 +246,7 @@ func (s *SwiftLocation) Connect(name string) error { return nil } var err error - s.Container, err = s.Account.Container(string(s.ContainerName)).EnsureExists() + s.Container, err = s.Account.Container(string(s.ContainerName)).EnsureExists(ctx) return err } @@ -258,7 +258,7 @@ func (s *SwiftLocation) ObjectAtPath(path string) *schwift.Object { } // ListAllFiles implements the Source interface. -func (s *SwiftLocation) ListAllFiles(_ context.Context, out chan<- FileSpec) *ListEntriesError { +func (s *SwiftLocation) ListAllFiles(ctx context.Context, out chan<- FileSpec) *ListEntriesError { objectPath := string(s.ObjectNamePrefix) if objectPath != "" && !strings.HasSuffix(objectPath, "/") { objectPath += "/" @@ -267,7 +267,7 @@ func (s *SwiftLocation) ListAllFiles(_ context.Context, out chan<- FileSpec) *Li iter := s.Container.Objects() iter.Prefix = objectPath - err := iter.ForeachDetailed(func(info schwift.ObjectInfo) error { + err := iter.ForeachDetailed(ctx, func(info schwift.ObjectInfo) error { out <- s.getFileSpec(info) return nil }) @@ -309,10 +309,10 @@ func (s *SwiftLocation) getFileSpec(info schwift.ObjectInfo) FileSpec { } // GetFile implements the Source interface. -func (s *SwiftLocation) GetFile(_ context.Context, path string, requestHeaders schwift.ObjectHeaders) (io.ReadCloser, FileState, error) { +func (s *SwiftLocation) GetFile(ctx context.Context, path string, requestHeaders schwift.ObjectHeaders) (io.ReadCloser, FileState, error) { object := s.ObjectAtPath(path) - body, err := object.Download(requestHeaders.ToOpts()).AsReadCloser() + body, err := object.Download(ctx, requestHeaders.ToOpts()).AsReadCloser() if schwift.Is(err, http.StatusNotModified) { return nil, FileState{SkipTransfer: true}, nil } @@ -321,7 +321,7 @@ func (s *SwiftLocation) GetFile(_ context.Context, path string, requestHeaders s } //NOTE: Download() uses a GET request, so object metadata has already been // received and cached, so Headers() is cheap now and will never fail. - hdr, err := object.Headers() + hdr, err := object.Headers(ctx) if err != nil { body.Close() return nil, FileState{}, err @@ -348,7 +348,7 @@ func (s *SwiftLocation) GetFile(_ context.Context, path string, requestHeaders s // // The given Matcher is used to find out which files are to be considered as // belonging to the transfer job in question. -func (s *SwiftLocation) DiscoverExistingFiles(matcher Matcher) error { +func (s *SwiftLocation) DiscoverExistingFiles(ctx context.Context, matcher Matcher) error { prefix := string(s.ObjectNamePrefix) if prefix != "" && !strings.HasSuffix(prefix, "/") { prefix += "/" @@ -364,7 +364,7 @@ func (s *SwiftLocation) DiscoverExistingFiles(matcher Matcher) error { iter := s.Container.Objects() iter.Prefix = prefix s.FileExists = make(map[string]bool) - err := iter.Foreach(func(object *schwift.Object) error { + err := iter.Foreach(ctx, func(object *schwift.Object) error { s.FileExists[object.Name()] = true return nil }) diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/objects/yum.go b/vendor/github.com/sapcc/swift-http-import/pkg/objects/yum.go index 3fbbeac..6de3814 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/objects/yum.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/objects/yum.go @@ -27,7 +27,7 @@ import ( "path/filepath" "strings" - "github.com/majewsky/schwift" + "github.com/majewsky/schwift/v2" "github.com/sapcc/go-bits/logg" "github.com/sapcc/swift-http-import/pkg/util" @@ -67,8 +67,8 @@ func (s *YumSource) Validate(name string) []error { } // Connect implements the Source interface. -func (s *YumSource) Connect(name string) error { - return s.urlSource.Connect(name) +func (s *YumSource) Connect(ctx context.Context, name string) error { + return s.urlSource.Connect(ctx, name) } // ListEntries implements the Source interface. diff --git a/vendor/github.com/sapcc/swift-http-import/pkg/util/gpg.go b/vendor/github.com/sapcc/swift-http-import/pkg/util/gpg.go index 902071f..e408674 100644 --- a/vendor/github.com/sapcc/swift-http-import/pkg/util/gpg.go +++ b/vendor/github.com/sapcc/swift-http-import/pkg/util/gpg.go @@ -33,7 +33,7 @@ import ( "strings" "sync" - "github.com/majewsky/schwift" + "github.com/majewsky/schwift/v2" "github.com/sapcc/go-bits/logg" //nolint:staticcheck // We cannot switch to the Protonmail fork because we need support for old v3 signatures as found in SLES12 packages. @@ -58,7 +58,7 @@ type GPGKeyRing struct { } // NewGPGKeyRing creates a new GPGKeyRing instance. -func NewGPGKeyRing(cntr *schwift.Container, keyserverURLPatterns []string) *GPGKeyRing { +func NewGPGKeyRing(ctx context.Context, cntr *schwift.Container, keyserverURLPatterns []string) *GPGKeyRing { ksURLPatterns := keyserverURLPatterns if len(ksURLPatterns) == 0 { ksURLPatterns = append(ksURLPatterns, @@ -70,8 +70,8 @@ func NewGPGKeyRing(cntr *schwift.Container, keyserverURLPatterns []string) *GPGK var entityList openpgp.EntityList if cntr != nil { logg.Info("restoring GPG public keys from %s", cntr.Name()) - err := cntr.Objects().Foreach(func(obj *schwift.Object) error { - r, err := obj.Download(nil).AsReadCloser() + err := cntr.Objects().Foreach(ctx, func(obj *schwift.Object) error { + r, err := obj.Download(ctx, nil).AsReadCloser() if err != nil { return err } @@ -191,7 +191,7 @@ func (k *GPGKeyRing) getPublicKey(ctx context.Context, id string) ([]byte, error url := strings.ReplaceAll(v, "{keyid}", id) buf, err := getPublicKeyFromServer(ctx, url) if err == nil { - return uploadPublicKey(k.SwiftContainer, buf) + return uploadPublicKey(ctx, k.SwiftContainer, buf) } if i == len(k.KeyserverURLPatterns)-1 { @@ -231,13 +231,13 @@ func getPublicKeyFromServer(ctx context.Context, uri string) ([]byte, error) { return b, nil } -func uploadPublicKey(cntr *schwift.Container, b []byte) ([]byte, error) { +func uploadPublicKey(ctx context.Context, cntr *schwift.Container, b []byte) ([]byte, error) { if cntr == nil { return b, nil } n := fmt.Sprintf("%x.asc", sha256.Sum256(b)) obj := cntr.Object(n) - err := obj.Upload(bytes.NewReader(b), nil, nil) + err := obj.Upload(ctx, bytes.NewReader(b), nil, nil) if err == nil && LogIndividualTransfers { logg.Info("transferring GPG key to cache at %s", obj.FullName()) } diff --git a/vendor/golang.org/x/crypto/cast5/cast5.go b/vendor/golang.org/x/crypto/cast5/cast5.go index 425e8ee..016e902 100644 --- a/vendor/golang.org/x/crypto/cast5/cast5.go +++ b/vendor/golang.org/x/crypto/cast5/cast5.go @@ -11,7 +11,7 @@ // Deprecated: any new system should use AES (from crypto/aes, if necessary in // an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from // golang.org/x/crypto/chacha20poly1305). -package cast5 // import "golang.org/x/crypto/cast5" +package cast5 import ( "errors" diff --git a/vendor/golang.org/x/crypto/openpgp/armor/armor.go b/vendor/golang.org/x/crypto/openpgp/armor/armor.go index 8907183..e664d12 100644 --- a/vendor/golang.org/x/crypto/openpgp/armor/armor.go +++ b/vendor/golang.org/x/crypto/openpgp/armor/armor.go @@ -10,14 +10,15 @@ // for their specific task. If you are required to interoperate with OpenPGP // systems and need a maintained package, consider a community fork. // See https://golang.org/issue/44226. -package armor // import "golang.org/x/crypto/openpgp/armor" +package armor import ( "bufio" "bytes" "encoding/base64" - "golang.org/x/crypto/openpgp/errors" "io" + + "golang.org/x/crypto/openpgp/errors" ) // A Block represents an OpenPGP armored structure. diff --git a/vendor/golang.org/x/crypto/openpgp/clearsign/clearsign.go b/vendor/golang.org/x/crypto/openpgp/clearsign/clearsign.go index 644b2e0..cea48ef 100644 --- a/vendor/golang.org/x/crypto/openpgp/clearsign/clearsign.go +++ b/vendor/golang.org/x/crypto/openpgp/clearsign/clearsign.go @@ -13,7 +13,7 @@ // for their specific task. If you are required to interoperate with OpenPGP // systems and need a maintained package, consider a community fork. // See https://golang.org/issue/44226. -package clearsign // import "golang.org/x/crypto/openpgp/clearsign" +package clearsign import ( "bufio" diff --git a/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go index 743b35a..f922bdb 100644 --- a/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go +++ b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go @@ -16,7 +16,7 @@ // https://golang.org/issue/44226), and ElGamal in the OpenPGP ecosystem has // compatibility and security issues (see https://eprint.iacr.org/2021/923). // Moreover, this package doesn't protect against side-channel attacks. -package elgamal // import "golang.org/x/crypto/openpgp/elgamal" +package elgamal import ( "crypto/rand" diff --git a/vendor/golang.org/x/crypto/openpgp/errors/errors.go b/vendor/golang.org/x/crypto/openpgp/errors/errors.go index 1d7a0ea..a328749 100644 --- a/vendor/golang.org/x/crypto/openpgp/errors/errors.go +++ b/vendor/golang.org/x/crypto/openpgp/errors/errors.go @@ -9,7 +9,7 @@ // for their specific task. If you are required to interoperate with OpenPGP // systems and need a maintained package, consider a community fork. // See https://golang.org/issue/44226. -package errors // import "golang.org/x/crypto/openpgp/errors" +package errors import ( "strconv" diff --git a/vendor/golang.org/x/crypto/openpgp/packet/packet.go b/vendor/golang.org/x/crypto/openpgp/packet/packet.go index 0a19794..a84a1a2 100644 --- a/vendor/golang.org/x/crypto/openpgp/packet/packet.go +++ b/vendor/golang.org/x/crypto/openpgp/packet/packet.go @@ -10,7 +10,7 @@ // for their specific task. If you are required to interoperate with OpenPGP // systems and need a maintained package, consider a community fork. // See https://golang.org/issue/44226. -package packet // import "golang.org/x/crypto/openpgp/packet" +package packet import ( "bufio" diff --git a/vendor/golang.org/x/crypto/openpgp/read.go b/vendor/golang.org/x/crypto/openpgp/read.go index 48a8931..cff3db9 100644 --- a/vendor/golang.org/x/crypto/openpgp/read.go +++ b/vendor/golang.org/x/crypto/openpgp/read.go @@ -9,7 +9,7 @@ // for their specific task. If you are required to interoperate with OpenPGP // systems and need a maintained package, consider a community fork. // See https://golang.org/issue/44226. -package openpgp // import "golang.org/x/crypto/openpgp" +package openpgp import ( "crypto" diff --git a/vendor/golang.org/x/crypto/openpgp/s2k/s2k.go b/vendor/golang.org/x/crypto/openpgp/s2k/s2k.go index f53244a..fa1a919 100644 --- a/vendor/golang.org/x/crypto/openpgp/s2k/s2k.go +++ b/vendor/golang.org/x/crypto/openpgp/s2k/s2k.go @@ -10,7 +10,7 @@ // for their specific task. If you are required to interoperate with OpenPGP // systems and need a maintained package, consider a community fork. // See https://golang.org/issue/44226. -package s2k // import "golang.org/x/crypto/openpgp/s2k" +package s2k import ( "crypto" diff --git a/vendor/golang.org/x/sys/unix/mremap.go b/vendor/golang.org/x/sys/unix/mremap.go index fd45fe5..3a5e776 100644 --- a/vendor/golang.org/x/sys/unix/mremap.go +++ b/vendor/golang.org/x/sys/unix/mremap.go @@ -50,3 +50,8 @@ func (m *mremapMmapper) Mremap(oldData []byte, newLength int, flags int) (data [ func Mremap(oldData []byte, newLength int, flags int) (data []byte, err error) { return mapper.Mremap(oldData, newLength, flags) } + +func MremapPtr(oldAddr unsafe.Pointer, oldSize uintptr, newAddr unsafe.Pointer, newSize uintptr, flags int) (ret unsafe.Pointer, err error) { + xaddr, err := mapper.mremap(uintptr(oldAddr), oldSize, newSize, flags, uintptr(newAddr)) + return unsafe.Pointer(xaddr), err +} diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index 59542a8..4cc7b00 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -542,6 +542,18 @@ func SysctlKinfoProcSlice(name string, args ...int) ([]KinfoProc, error) { } } +//sys pthread_chdir_np(path string) (err error) + +func PthreadChdir(path string) (err error) { + return pthread_chdir_np(path) +} + +//sys pthread_fchdir_np(fd int) (err error) + +func PthreadFchdir(fd int) (err error) { + return pthread_fchdir_np(fd) +} + //sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) //sys shmat(id int, addr uintptr, flag int) (ret uintptr, err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_unix.go b/vendor/golang.org/x/sys/unix/syscall_unix.go index 77081de..4e92e5a 100644 --- a/vendor/golang.org/x/sys/unix/syscall_unix.go +++ b/vendor/golang.org/x/sys/unix/syscall_unix.go @@ -154,6 +154,15 @@ func Munmap(b []byte) (err error) { return mapper.Munmap(b) } +func MmapPtr(fd int, offset int64, addr unsafe.Pointer, length uintptr, prot int, flags int) (ret unsafe.Pointer, err error) { + xaddr, err := mapper.mmap(uintptr(addr), length, prot, flags, fd, offset) + return unsafe.Pointer(xaddr), err +} + +func MunmapPtr(addr unsafe.Pointer, length uintptr) (err error) { + return mapper.munmap(uintptr(addr), length) +} + func Read(fd int, p []byte) (n int, err error) { n, err = read(fd, p) if raceenabled { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go index ccb02f2..07642c3 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go @@ -760,6 +760,39 @@ var libc_sysctl_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func pthread_chdir_np(path string) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := syscall_syscall(libc_pthread_chdir_np_trampoline_addr, uintptr(unsafe.Pointer(_p0)), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_pthread_chdir_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_pthread_chdir_np pthread_chdir_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func pthread_fchdir_np(fd int) (err error) { + _, _, e1 := syscall_syscall(libc_pthread_fchdir_np_trampoline_addr, uintptr(fd), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_pthread_fchdir_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_pthread_fchdir_np pthread_fchdir_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall6(libc_sendfile_trampoline_addr, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s index 8b8bb28..923e08c 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s @@ -228,6 +228,16 @@ TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB) +TEXT libc_pthread_chdir_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_pthread_chdir_np(SB) +GLOBL ·libc_pthread_chdir_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_pthread_chdir_np_trampoline_addr(SB)/8, $libc_pthread_chdir_np_trampoline<>(SB) + +TEXT libc_pthread_fchdir_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_pthread_fchdir_np(SB) +GLOBL ·libc_pthread_fchdir_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_pthread_fchdir_np_trampoline_addr(SB)/8, $libc_pthread_fchdir_np_trampoline<>(SB) + TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendfile(SB) GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go index 1b40b99..7d73dda 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go @@ -760,6 +760,39 @@ var libc_sysctl_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func pthread_chdir_np(path string) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := syscall_syscall(libc_pthread_chdir_np_trampoline_addr, uintptr(unsafe.Pointer(_p0)), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_pthread_chdir_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_pthread_chdir_np pthread_chdir_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func pthread_fchdir_np(fd int) (err error) { + _, _, e1 := syscall_syscall(libc_pthread_fchdir_np_trampoline_addr, uintptr(fd), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_pthread_fchdir_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_pthread_fchdir_np pthread_fchdir_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall6(libc_sendfile_trampoline_addr, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s index 08362c1..0577001 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s @@ -228,6 +228,16 @@ TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB) +TEXT libc_pthread_chdir_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_pthread_chdir_np(SB) +GLOBL ·libc_pthread_chdir_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_pthread_chdir_np_trampoline_addr(SB)/8, $libc_pthread_chdir_np_trampoline<>(SB) + +TEXT libc_pthread_fchdir_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_pthread_fchdir_np(SB) +GLOBL ·libc_pthread_fchdir_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_pthread_fchdir_np_trampoline_addr(SB)/8, $libc_pthread_fchdir_np_trampoline<>(SB) + TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendfile(SB) GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go index 6f7d2ac..97651b5 100644 --- a/vendor/golang.org/x/sys/windows/security_windows.go +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -894,7 +894,7 @@ type ACL struct { aclRevision byte sbz1 byte aclSize uint16 - aceCount uint16 + AceCount uint16 sbz2 uint16 } @@ -1087,6 +1087,27 @@ type EXPLICIT_ACCESS struct { Trustee TRUSTEE } +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header +type ACE_HEADER struct { + AceType uint8 + AceFlags uint8 + AceSize uint16 +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace +type ACCESS_ALLOWED_ACE struct { + Header ACE_HEADER + Mask ACCESS_MASK + SidStart uint32 +} + +const ( + // Constants for AceType + // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header + ACCESS_ALLOWED_ACE_TYPE = 0 + ACCESS_DENIED_ACE_TYPE = 1 +) + // This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions. type TrusteeValue uintptr @@ -1158,6 +1179,7 @@ type OBJECTS_AND_NAME struct { //sys makeSelfRelativeSD(absoluteSD *SECURITY_DESCRIPTOR, selfRelativeSD *SECURITY_DESCRIPTOR, selfRelativeSDSize *uint32) (err error) = advapi32.MakeSelfRelativeSD //sys setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCESS, oldACL *ACL, newACL **ACL) (ret error) = advapi32.SetEntriesInAclW +//sys GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) = advapi32.GetAce // Control returns the security descriptor control bits. func (sd *SECURITY_DESCRIPTOR) Control() (control SECURITY_DESCRIPTOR_CONTROL, revision uint32, err error) { diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 9f73df7..eba7610 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -91,6 +91,7 @@ var ( procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW") procEqualSid = modadvapi32.NewProc("EqualSid") procFreeSid = modadvapi32.NewProc("FreeSid") + procGetAce = modadvapi32.NewProc("GetAce") procGetLengthSid = modadvapi32.NewProc("GetLengthSid") procGetNamedSecurityInfoW = modadvapi32.NewProc("GetNamedSecurityInfoW") procGetSecurityDescriptorControl = modadvapi32.NewProc("GetSecurityDescriptorControl") @@ -1224,6 +1225,14 @@ func setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCE return } +func GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) { + r0, _, _ := syscall.Syscall(procGetAce.Addr(), 3, uintptr(unsafe.Pointer(acl)), uintptr(aceIndex), uintptr(unsafe.Pointer(pAce))) + if r0 == 0 { + ret = GetLastError() + } + return +} + func SetKernelObjectSecurity(handle Handle, securityInformation SECURITY_INFORMATION, securityDescriptor *SECURITY_DESCRIPTOR) (err error) { r1, _, e1 := syscall.Syscall(procSetKernelObjectSecurity.Addr(), 3, uintptr(handle), uintptr(securityInformation), uintptr(unsafe.Pointer(securityDescriptor))) if r1 == 0 { diff --git a/vendor/modules.txt b/vendor/modules.txt index 2d3274a..b3d2b3b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,7 +8,9 @@ github.com/fsnotify/fsnotify ## explicit; go 1.21 # github.com/google/go-github/v62 v62.0.0 ## explicit; go 1.21 -github.com/google/go-github/v62/github +# github.com/google/go-github/v63 v63.0.0 +## explicit; go 1.21 +github.com/google/go-github/v63/github # github.com/google/go-querystring v1.1.0 ## explicit; go 1.10 github.com/google/go-querystring/query @@ -63,6 +65,17 @@ github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/shares github.com/gophercloud/gophercloud/openstack/sharedfilesystems/v2/snapshots github.com/gophercloud/gophercloud/openstack/utils github.com/gophercloud/gophercloud/pagination +# github.com/gophercloud/gophercloud/v2 v2.0.0 +## explicit; go 1.22 +github.com/gophercloud/gophercloud/v2 +github.com/gophercloud/gophercloud/v2/openstack +github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tenants +github.com/gophercloud/gophercloud/v2/openstack/identity/v2/tokens +github.com/gophercloud/gophercloud/v2/openstack/identity/v3/ec2tokens +github.com/gophercloud/gophercloud/v2/openstack/identity/v3/oauth1 +github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens +github.com/gophercloud/gophercloud/v2/openstack/utils +github.com/gophercloud/gophercloud/v2/pagination # github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 ## explicit; go 1.15 github.com/gophercloud/utils/client @@ -77,6 +90,9 @@ github.com/gophercloud/utils/openstack/imageservice/v2/images github.com/gophercloud/utils/openstack/networking/v2/networks github.com/gophercloud/utils/openstack/networking/v2/subnets github.com/gophercloud/utils/openstack/sharedfilesystems/v2/shares +# github.com/gophercloud/utils/v2 v2.0.0-20240705071316-780b64d153e3 +## explicit; go 1.22 +github.com/gophercloud/utils/v2/client # github.com/hashicorp/go-uuid v1.0.3 ## explicit github.com/hashicorp/go-uuid @@ -114,6 +130,12 @@ github.com/majewsky/schwift github.com/majewsky/schwift/capabilities github.com/majewsky/schwift/gopherschwift github.com/majewsky/schwift/internal/errext +# github.com/majewsky/schwift/v2 v2.0.0 +## explicit; go 1.22 +github.com/majewsky/schwift/v2 +github.com/majewsky/schwift/v2/capabilities +github.com/majewsky/schwift/v2/gopherschwift +github.com/majewsky/schwift/v2/internal/errext # github.com/matryer/is v1.4.1 ## explicit; go 1.14 # github.com/mitchellh/go-homedir v1.1.0 @@ -135,16 +157,16 @@ github.com/sagikazarmark/locafero # github.com/sagikazarmark/slog-shim v0.1.0 ## explicit; go 1.20 github.com/sagikazarmark/slog-shim -# github.com/sapcc/go-api-declarations v1.11.2 +# github.com/sapcc/go-api-declarations v1.11.3 ## explicit; go 1.21 github.com/sapcc/go-api-declarations/bininfo -# github.com/sapcc/go-bits v0.0.0-20240530080859-f4579fb3a074 +# github.com/sapcc/go-bits v0.0.0-20240709125621-b1e90ef040ad ## explicit; go 1.22 github.com/sapcc/go-bits/errext github.com/sapcc/go-bits/logg github.com/sapcc/go-bits/osext github.com/sapcc/go-bits/secrets -# github.com/sapcc/swift-http-import v0.0.0-20240605095849-0db239238e71 +# github.com/sapcc/swift-http-import v0.0.0-20240712085221-f33ec7ac8e34 ## explicit; go 1.22 github.com/sapcc/swift-http-import/pkg/actors github.com/sapcc/swift-http-import/pkg/objects @@ -199,8 +221,8 @@ go.uber.org/atomic # go.uber.org/multierr v1.9.0 ## explicit; go 1.19 go.uber.org/multierr -# golang.org/x/crypto v0.24.0 -## explicit; go 1.18 +# golang.org/x/crypto v0.25.0 +## explicit; go 1.20 golang.org/x/crypto/cast5 golang.org/x/crypto/openpgp golang.org/x/crypto/openpgp/armor @@ -216,7 +238,7 @@ golang.org/x/exp/slices golang.org/x/exp/slog golang.org/x/exp/slog/internal golang.org/x/exp/slog/internal/buffer -# golang.org/x/net v0.26.0 +# golang.org/x/net v0.27.0 ## explicit; go 1.18 golang.org/x/net/html golang.org/x/net/html/atom @@ -224,12 +246,12 @@ golang.org/x/net/html/atom ## explicit; go 1.18 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sys v0.21.0 +# golang.org/x/sys v0.22.0 ## explicit; go 1.18 golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/term v0.21.0 +# golang.org/x/term v0.22.0 ## explicit; go 1.18 golang.org/x/term # golang.org/x/text v0.16.0