mirror of
https://github.com/minio/minio.git
synced 2025-11-07 12:52:58 -05:00
Add Support for Manta Object Storage as a Gateway (#5025)
Manta is an Object Storage by [Joyent](https://www.joyent.com/) This PR adds initial support for Manta. It is intended as non-production ready so that feedback can be obtained.
This commit is contained in:
committed by
Nitish Tiwari
parent
1f77708a30
commit
7d75d61621
40
vendor/github.com/joyent/triton-go/CHANGELOG.md
generated
vendored
Normal file
40
vendor/github.com/joyent/triton-go/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
## Unreleased
|
||||
|
||||
## 0.4.1 (December 15)
|
||||
|
||||
- Clean up the handling of directory sanitization. Use abs paths everywhere [#79]
|
||||
|
||||
## 0.4.0 (December 15)
|
||||
|
||||
- Fix an issue where Manta HEAD requests do not return an error resp body [#77]
|
||||
- Add support for recursively creating child directories [#78]
|
||||
|
||||
## 0.3.0 (December 14)
|
||||
|
||||
- Introduce CloudAPI's ListRulesMachines under networking
|
||||
- Enable HTTP KeepAlives by default in the client. 15s idle timeout, 2x
|
||||
connections per host, total of 10x connections per client.
|
||||
- Expose an optional Headers attribute to clients to allow them to customize
|
||||
HTTP headers when making Object requests.
|
||||
- Fix a bug in Directory ListIndex [#69](https://github.com/joyent/issues/69)
|
||||
- Inputs to Object inputs have been relaxed to `io.Reader` (formerly a
|
||||
`io.ReadSeeker`) [#73](https://github.com/joyent/issues/73).
|
||||
- Add support for ForceDelete of all children of a directory [#71](https://github.com/joyent/issues/71)
|
||||
- storage: Introduce `Objects.GetInfo` and `Objects.IsDir` using HEAD requests [#74](https://github.com/joyent/triton-go/issues/74)
|
||||
|
||||
## 0.2.1 (November 8)
|
||||
|
||||
- Fixing a bug where CreateUser and UpdateUser didn't return the UserID
|
||||
|
||||
## 0.2.0 (November 7)
|
||||
|
||||
- Introduce CloudAPI's Ping under compute
|
||||
- Introduce CloudAPI's RebootMachine under compute instances
|
||||
- Introduce CloudAPI's ListUsers, GetUser, CreateUser, UpdateUser and DeleteUser under identity package
|
||||
- Introduce CloudAPI's ListMachineSnapshots, GetMachineSnapshot, CreateSnapshot, DeleteMachineSnapshot and StartMachineFromSnapshot under compute package
|
||||
- tools: Introduce unit testing and scripts for linting, etc.
|
||||
- bug: Fix the `compute.ListMachineRules` endpoint
|
||||
|
||||
## 0.1.0 (November 2)
|
||||
|
||||
- Initial release of a versioned SDK
|
||||
47
vendor/github.com/joyent/triton-go/GNUmakefile
generated
vendored
Normal file
47
vendor/github.com/joyent/triton-go/GNUmakefile
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
TEST?=$$(go list ./... |grep -Ev 'vendor|examples|testutils')
|
||||
GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
|
||||
|
||||
default: vet errcheck test
|
||||
|
||||
tools:: ## Download and install all dev/code tools
|
||||
@echo "==> Installing dev tools"
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
go get -u github.com/golang/lint/golint
|
||||
go get -u github.com/kisielk/errcheck
|
||||
@echo "==> Installing test package dependencies"
|
||||
go test -i $(TEST) || exit 1
|
||||
|
||||
test:: ## Run unit tests
|
||||
@echo "==> Running unit tests"
|
||||
@echo $(TEST) | \
|
||||
xargs -t go test -v $(TESTARGS) -timeout=30s -parallel=1 | grep -Ev 'TRITON_TEST|TestAcc'
|
||||
|
||||
testacc:: ## Run acceptance tests
|
||||
@echo "==> Running acceptance tests"
|
||||
TRITON_TEST=1 go test $(TEST) -v $(TESTARGS) -run -timeout 120m
|
||||
|
||||
vet:: ## Check for unwanted code constructs
|
||||
@echo "go vet ."
|
||||
@go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \
|
||||
echo ""; \
|
||||
echo "Vet found suspicious constructs. Please check the reported constructs"; \
|
||||
echo "and fix them if necessary before submitting the code for review."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
lint:: ## Lint and vet code by common Go standards
|
||||
@bash $(CURDIR)/scripts/lint.sh
|
||||
|
||||
fmt:: ## Format as canonical Go code
|
||||
gofmt -w $(GOFMT_FILES)
|
||||
|
||||
fmtcheck:: ## Check if code format is canonical Go
|
||||
@bash $(CURDIR)/scripts/gofmtcheck.sh
|
||||
|
||||
errcheck:: ## Check for unhandled errors
|
||||
@bash $(CURDIR)/scripts/errcheck.sh
|
||||
|
||||
.PHONY: help
|
||||
help:: ## Display this help message
|
||||
@echo "GNU make(1) targets:"
|
||||
@grep -E '^[a-zA-Z_.-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
|
||||
39
vendor/github.com/joyent/triton-go/Gopkg.lock
generated
vendored
Normal file
39
vendor/github.com/joyent/triton-go/Gopkg.lock
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/abdullin/seq"
|
||||
packages = ["."]
|
||||
revision = "d5467c17e7afe8d8f08f556c6c811a50c3feb28d"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/errwrap"
|
||||
packages = ["."]
|
||||
revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/sean-/seed"
|
||||
packages = ["."]
|
||||
revision = "e2103e2c35297fb7e17febb81e49b312087a2372"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent"]
|
||||
revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "28853a8970ee33112a9e7998b18e658bed04d177537ec69db678189f0b8a9a7d"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
42
vendor/github.com/joyent/triton-go/Gopkg.toml
generated
vendored
Normal file
42
vendor/github.com/joyent/triton-go/Gopkg.toml
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/abdullin/seq"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/errwrap"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/sean-/seed"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
373
vendor/github.com/joyent/triton-go/LICENSE
generated
vendored
Normal file
373
vendor/github.com/joyent/triton-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
217
vendor/github.com/joyent/triton-go/README.md
generated
vendored
Normal file
217
vendor/github.com/joyent/triton-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
# triton-go
|
||||
|
||||
`triton-go` is an idiomatic library exposing a client SDK for Go applications
|
||||
using Joyent's Triton Compute and Storage (Manta) APIs.
|
||||
|
||||
[](https://travis-ci.org/joyent/triton-go) [](https://goreportcard.com/report/github.com/joyent/triton-go)
|
||||
|
||||
## Usage
|
||||
|
||||
Triton uses [HTTP Signature][4] to sign the Date header in each HTTP request
|
||||
made to the Triton API. Currently, requests can be signed using either a private
|
||||
key file loaded from disk (using an [`authentication.PrivateKeySigner`][5]), or
|
||||
using a key stored with the local SSH Agent (using an [`SSHAgentSigner`][6].
|
||||
|
||||
To construct a Signer, use the `New*` range of methods in the `authentication`
|
||||
package. In the case of `authentication.NewSSHAgentSigner`, the parameters are
|
||||
the fingerprint of the key with which to sign, and the account name (normally
|
||||
stored in the `SDC_ACCOUNT` environment variable). For example:
|
||||
|
||||
```
|
||||
const fingerprint := "a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11"
|
||||
sshKeySigner, err := authentication.NewSSHAgentSigner(fingerprint, "AccountName")
|
||||
if err != nil {
|
||||
log.Fatalf("NewSSHAgentSigner: %s", err)
|
||||
}
|
||||
```
|
||||
|
||||
An appropriate key fingerprint can be generated using `ssh-keygen`.
|
||||
|
||||
```
|
||||
ssh-keygen -Emd5 -lf ~/.ssh/id_rsa.pub | cut -d " " -f 2 | sed 's/MD5://'
|
||||
```
|
||||
|
||||
Each top level package, `account`, `compute`, `identity`, `network`, all have
|
||||
their own seperate client. In order to initialize a package client, simply pass
|
||||
the global `triton.ClientConfig` struct into the client's constructor function.
|
||||
|
||||
```go
|
||||
config := &triton.ClientConfig{
|
||||
TritonURL: os.Getenv("SDC_URL"),
|
||||
MantaURL: os.Getenv("MANTA_URL"),
|
||||
AccountName: accountName,
|
||||
Signers: []authentication.Signer{sshKeySigner},
|
||||
}
|
||||
|
||||
c, err := compute.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("compute.NewClient: %s", err)
|
||||
}
|
||||
```
|
||||
|
||||
Constructing `compute.Client` returns an interface which exposes `compute` API
|
||||
resources. The same goes for all other packages. Reference their unique
|
||||
documentation for more information.
|
||||
|
||||
The same `triton.ClientConfig` will initialize the Manta `storage` client as
|
||||
well...
|
||||
|
||||
```go
|
||||
c, err := storage.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("storage.NewClient: %s", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
If an error is returned by the HTTP API, the `error` returned from the function
|
||||
will contain an instance of `compute.TritonError` in the chain. Error wrapping
|
||||
is performed using the [errwrap][7] library from HashiCorp.
|
||||
|
||||
## Acceptance Tests
|
||||
|
||||
Acceptance Tests run directly against the Triton API, so you will need either a
|
||||
local installation of Triton or an account with Joyent's Public Cloud offering
|
||||
in order to run them. The tests create real resources (and thus cost real
|
||||
money)!
|
||||
|
||||
In order to run acceptance tests, the following environment variables must be
|
||||
set:
|
||||
|
||||
- `TRITON_TEST` - must be set to any value in order to indicate desire to create
|
||||
resources
|
||||
- `SDC_URL` - the base endpoint for the Triton API
|
||||
- `SDC_ACCOUNT` - the account name for the Triton API
|
||||
- `SDC_KEY_ID` - the fingerprint of the SSH key identifying the key
|
||||
|
||||
Additionally, you may set `SDC_KEY_MATERIAL` to the contents of an unencrypted
|
||||
private key. If this is set, the PrivateKeySigner (see above) will be used - if
|
||||
not the SSHAgentSigner will be used.
|
||||
|
||||
### Example Run
|
||||
|
||||
The verbose output has been removed for brevity here.
|
||||
|
||||
```
|
||||
$ HTTP_PROXY=http://localhost:8888 \
|
||||
TRITON_TEST=1 \
|
||||
SDC_URL=https://us-sw-1.api.joyent.com \
|
||||
SDC_ACCOUNT=AccountName \
|
||||
SDC_KEY_ID=a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11 \
|
||||
go test -v -run "TestAccKey"
|
||||
=== RUN TestAccKey_Create
|
||||
--- PASS: TestAccKey_Create (12.46s)
|
||||
=== RUN TestAccKey_Get
|
||||
--- PASS: TestAccKey_Get (4.30s)
|
||||
=== RUN TestAccKey_Delete
|
||||
--- PASS: TestAccKey_Delete (15.08s)
|
||||
PASS
|
||||
ok github.com/joyent/triton-go 31.861s
|
||||
```
|
||||
|
||||
## Example API
|
||||
|
||||
There's an `examples/` directory available with sample code setup for many of
|
||||
the APIs within this library. Most of these can be run using `go run` and
|
||||
referencing your SSH key file use by your active `triton` CLI profile.
|
||||
|
||||
```sh
|
||||
$ eval "$(triton env us-sw-1)"
|
||||
$ SDC_KEY_FILE=~/.ssh/triton-id_rsa go run examples/compute/instances.go
|
||||
```
|
||||
|
||||
The following is a complete example of how to initialize the `compute` package
|
||||
client and list all instances under an account. More detailed usage of this
|
||||
library follows.
|
||||
|
||||
```go
|
||||
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
triton "github.com/joyent/triton-go"
|
||||
"github.com/joyent/triton-go/authentication"
|
||||
"github.com/joyent/triton-go/compute"
|
||||
)
|
||||
|
||||
func main() {
|
||||
keyID := os.Getenv("SDC_KEY_ID")
|
||||
accountName := os.Getenv("SDC_ACCOUNT")
|
||||
keyMaterial := os.Getenv("SDC_KEY_MATERIAL")
|
||||
|
||||
var signer authentication.Signer
|
||||
var err error
|
||||
|
||||
if keyMaterial == "" {
|
||||
signer, err = authentication.NewSSHAgentSigner(keyID, accountName)
|
||||
if err != nil {
|
||||
log.Fatalf("Error Creating SSH Agent Signer: {{err}}", err)
|
||||
}
|
||||
} else {
|
||||
var keyBytes []byte
|
||||
if _, err = os.Stat(keyMaterial); err == nil {
|
||||
keyBytes, err = ioutil.ReadFile(keyMaterial)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading key material from %s: %s",
|
||||
keyMaterial, err)
|
||||
}
|
||||
block, _ := pem.Decode(keyBytes)
|
||||
if block == nil {
|
||||
log.Fatalf(
|
||||
"Failed to read key material '%s': no key found", keyMaterial)
|
||||
}
|
||||
|
||||
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
||||
log.Fatalf(
|
||||
"Failed to read key '%s': password protected keys are\n"+
|
||||
"not currently supported. Please decrypt the key prior to use.", keyMaterial)
|
||||
}
|
||||
|
||||
} else {
|
||||
keyBytes = []byte(keyMaterial)
|
||||
}
|
||||
|
||||
signer, err = authentication.NewPrivateKeySigner(keyID, []byte(keyMaterial), accountName)
|
||||
if err != nil {
|
||||
log.Fatalf("Error Creating SSH Private Key Signer: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
config := &triton.ClientConfig{
|
||||
TritonURL: os.Getenv("SDC_URL"),
|
||||
AccountName: accountName,
|
||||
Signers: []authentication.Signer{signer},
|
||||
}
|
||||
|
||||
c, err := compute.NewClient(config)
|
||||
if err != nil {
|
||||
log.Fatalf("compute.NewClient: %s", err)
|
||||
}
|
||||
|
||||
listInput := &compute.ListInstancesInput{}
|
||||
instances, err := c.Instances().List(context.Background(), listInput)
|
||||
if err != nil {
|
||||
log.Fatalf("compute.Instances.List: %v", err)
|
||||
}
|
||||
numInstances := 0
|
||||
for _, instance := range instances {
|
||||
numInstances++
|
||||
fmt.Println(fmt.Sprintf("-- Instance: %v", instance.Name))
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
[4]: https://github.com/joyent/node-http-signature/blob/master/http_signing.md
|
||||
[5]: https://godoc.org/github.com/joyent/triton-go/authentication
|
||||
[6]: https://godoc.org/github.com/joyent/triton-go/authentication
|
||||
[7]: https://github.com/hashicorp/go-errwrap
|
||||
72
vendor/github.com/joyent/triton-go/authentication/dummy.go
generated
vendored
Normal file
72
vendor/github.com/joyent/triton-go/authentication/dummy.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package authentication
|
||||
|
||||
// DON'T USE THIS OUTSIDE TESTING ~ This key was only created to use for
|
||||
// internal unit testing. It should never be used for acceptance testing either.
|
||||
//
|
||||
// This is just a randomly generated key pair.
|
||||
var Dummy = struct {
|
||||
Fingerprint string
|
||||
PrivateKey []byte
|
||||
PublicKey []byte
|
||||
Signer Signer
|
||||
}{
|
||||
"9f:d6:65:fc:d6:60:dc:d0:4e:db:2d:75:f7:92:8c:31",
|
||||
[]byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKAIBAAKCAgEAui9lNjCJahHeFSFC6HXi/CNX588C/L2gJUx65bnNphVC98hW
|
||||
1wzoRvPXHx5aWnb7lEbpNhP6B0UoCBDTaPgt9hHfD/oNQ+6HT1QpDIGfZmXI91/t
|
||||
cjGVSBbxN7WaYt/HsPrGjbalwvQPChN53sMVmFkMTEDR5G3zOBOAGrOimlCT80wI
|
||||
2S5Xg0spd8jjKM5I1swDR0xtuDWnHTR1Ohin+pEQIE6glLTfYq7oQx6nmMXXBNmk
|
||||
+SaPD1FAyjkF/81im2EHXBygNEwraVrDcAxK2mKlU2XMJiogQKNYWlm3UkbNB6WP
|
||||
Le12+Ka02rmIVsSqIpc/ZCBraAlCaSWlYCkU+vJ2hH/+ypy5bXNlbaTiWZK+vuI7
|
||||
PC87T50yLNeXVuNZAynzDpBCvsjiiHrB/ZFRfVfF6PviV8CV+m7GTzfAwJhVeSbl
|
||||
rR6nts16K0HTD48v57DU0b0t5VOvC7cWPShs+afdSL3Z8ReL5EWMgU1wfvtycRKe
|
||||
hiDVGj3Ms2cf83RIANr387G+1LcTQYP7JJuB7Svy5j+R6+HjI0cgu4EMUPdWfCNG
|
||||
GyrlxwJNtPmUSfasH1xUKpqr7dC+0sN4/gfJw75WTAYrATkPzexoYNaMsGDfhuoh
|
||||
kYa3Tn2q1g3kqhsX/R0Fd5d8d5qc137qcRCxiZYz9f3bVkXQbhYmO9da3KsCAwEA
|
||||
AQKCAgAeEAURqOinPddUJhi9nDtYZwSMo3piAORY4W5+pW+1P32esLSE6MqgmkLD
|
||||
/YytSsT4fjKtzq/yeJIsKztXmasiLmSMGd4Gd/9VKcuu/0cTq5+1gcG/TI5EI6Az
|
||||
VJlnGacOxo9E1pcRUYMUJ2zoMSvNe6NmtJivf6lkBpIKvbKlpBkfkclj9/2db4d0
|
||||
lfVH43cTZ8Gnw4l70v320z+Sb+S/qqil7swy9rmTH5bVL5/0JQ3A9LuUl0tGN+J0
|
||||
RJzZXvprCFG958leaGYiDsu7zeBQPtlfC/LYvriSd02O2SmmmVQFxg/GZK9vGsvc
|
||||
/VQsXnjyOOW9bxaop8YXYELBsiB21ipTHzOwoqHT8wFnjgU9Y/7iZIv7YbZKQsCS
|
||||
DrwdlZ/Yw90wiif+ldYryIVinWfytt6ERv4Dgezc98+1XPi1Z/WB74/lIaDXFl3M
|
||||
3ypjtvLYbKew2IkIjeAwjvZJg/QpC/50RrrPtVDgeAI1Ni01ikixUhMYsHJ1kRih
|
||||
0tqLvLqSPoHmr6luFlaoKdc2eBqb+8U6K/TrXhKtT7BeUFiSbvnVfdbrH9r+AY/2
|
||||
zYtG6llzkE5DH8ZR3Qp+dx7QEDtvYhGftWhx9uasd79AN7CuGYnL54YFLKGRrWKN
|
||||
ylysqfUyOQYiitdWdNCw9PP2vGRx5JAsMMSy+ft18jjTJvNQ0QKCAQEA28M11EE6
|
||||
MpnHxfyP00Dl1+3wl2lRyNXZnZ4hgkk1f83EJGpoB2amiMTF8P1qJb7US1fXtf7l
|
||||
gkJMMk6t6iccexV1/NBh/7tDZHH/v4HPirFTXQFizflaghD8dEADy9DY4BpQYFRe
|
||||
8zGsv4/4U0txCXkUIfKcENt/FtXv2T9blJT6cDV0yTx9IAyd4Kor7Ly2FIYroSME
|
||||
uqnOQt5PwB+2qkE+9hdg4xBhFs9sW5dvyBvQvlBfX/xOmMw2ygH6vsaJlNfZ5VPa
|
||||
EP/wFP/qHyhDlCfbHdL6qF2//wUoM2QM9RgBdZNhcKU7zWuf7Ev199tmlLC5O14J
|
||||
PkQxUGftMfmWxQKCAQEA2OLKD8dwOzpwGJiPQdBmGpwCamfcCY4nDwqEaCu4vY1R
|
||||
OJR+rpYdC2hgl5PTXWH7qzJVdT/ZAz2xUQOgB1hD3Ltk7DQ+EZIA8+vJdaicQOme
|
||||
vfpMPNDxCEX9ee0AXAmAC3aET82B4cMFnjXjl1WXLLTowF/Jp/hMorm6tl2m15A2
|
||||
oTyWlB/i/W/cxHl2HFWK7o8uCNoKpKJjheNYn+emEcH1bkwrk8sxQ78cBNmqe/gk
|
||||
MLgu8qfXQ0LLKIL7wqmIUHeUpkepOod8uXcTmmN2X9saCIwFKx4Jal5hh5v5cy0G
|
||||
MkyZcUIhhnmzr7lXbepauE5V2Sj5Qp040AfRVjZcrwKCAQANe8OwuzPL6P2F20Ij
|
||||
zwaLIhEx6QdYkC5i6lHaAY3jwoc3SMQLODQdjh0q9RFvMW8rFD+q7fG89T5hk8w9
|
||||
4ppvvthXY52vqBixcAEmCdvnAYxA15XtV1BDTLGAnHDfL3gu/85QqryMpU6ZDkdJ
|
||||
LQbJcwFWN+F1c1Iv335w0N9YlW9sNQtuUWTH8544K5i4VLfDOJwyrchbf5GlLqir
|
||||
/AYkGg634KVUKSwbzywxzm/QUkyTcLD5Xayg2V6/NDHjRKEqXbgDxwpJIrrjPvRp
|
||||
ZvoGfA+Im+o/LElcZz+ZL5lP7GIiiaFf3PN3XhQY1mxIAdEgbFthFhrxFBQGf+ng
|
||||
uBSVAoIBAHl12K8pg8LHoUtE9MVoziWMxRWOAH4ha+JSg4BLK/SLlbbYAnIHg1CG
|
||||
LcH1eWNMokJnt9An54KXJBw4qYAzgB23nHdjcncoivwPSg1oVclMjCfcaqGMac+2
|
||||
UpPblF32vAyvXL3MWzZxn03Q5Bo2Rqk0zzwc6LP2rARdeyDyJaOHEfEOG03s5ZQE
|
||||
91/YnbqUdW/QI3m1kkxM3Ot4PIOgmTJMqwQQCD+GhZppBmn49C7k8m+OVkxyjm0O
|
||||
lPOlFxUXGE3oCgltDGrIwaKj+wh1Ny/LZjLvJ13UPnWhUYE+al6EEnpMx4nT/S5w
|
||||
LZ71bu8RVajtxcoN1jnmDpECL8vWOeUCggEBAIEuKoY7pVHfs5gr5dXfQeVZEtqy
|
||||
LnSdsd37/aqQZRlUpVmBrPNl1JBLiEVhk2SL3XJIDU4Er7f0idhtYLY3eE7wqZ4d
|
||||
38Iaj5tv3zBc/wb1bImPgOgXCH7QrrbW7uTiYMLScuUbMR4uSpfubLaV8Zc9WHT8
|
||||
kTJ2pKKtA1GPJ4V7HCIxuTjD2iyOK1CRkaqSC+5VUuq5gHf92CEstv9AIvvy5cWg
|
||||
gnfBQoS89m3aO035henSfRFKVJkHaEoasj8hB3pwl9FGZUJp1c2JxiKzONqZhyGa
|
||||
6tcIAM3od0QtAfDJ89tWJ5D31W8KNNysobFSQxZ62WgLUUtXrkN1LGodxGQ=
|
||||
-----END RSA PRIVATE KEY-----`),
|
||||
[]byte(`ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6L2U2MIlqEd4VIULodeL8I1fnzwL8vaAlTHrluc2mFUL3yFbXDOhG89cfHlpadvuURuk2E/oHRSgIENNo+C32Ed8P+g1D7odPVCkMgZ9mZcj3X+1yMZVIFvE3tZpi38ew+saNtqXC9A8KE3newxWYWQxMQNHkbfM4E4Aas6KaUJPzTAjZLleDSyl3yOMozkjWzANHTG24NacdNHU6GKf6kRAgTqCUtN9iruhDHqeYxdcE2aT5Jo8PUUDKOQX/zWKbYQdcHKA0TCtpWsNwDEraYqVTZcwmKiBAo1haWbdSRs0HpY8t7Xb4prTauYhWxKoilz9kIGtoCUJpJaVgKRT68naEf/7KnLltc2VtpOJZkr6+4js8LztPnTIs15dW41kDKfMOkEK+yOKIesH9kVF9V8Xo++JXwJX6bsZPN8DAmFV5JuWtHqe2zXorQdMPjy/nsNTRvS3lU68LtxY9KGz5p91IvdnxF4vkRYyBTXB++3JxEp6GINUaPcyzZx/zdEgA2vfzsb7UtxNBg/skm4HtK/LmP5Hr4eMjRyC7gQxQ91Z8I0YbKuXHAk20+ZRJ9qwfXFQqmqvt0L7Sw3j+B8nDvlZMBisBOQ/N7Ghg1oywYN+G6iGRhrdOfarWDeSqGxf9HQV3l3x3mpzXfupxELGJljP1/dtWRdBuFiY711rcqw== test-dummy-20171002140848`),
|
||||
nil,
|
||||
}
|
||||
|
||||
func init() {
|
||||
testSigner, _ := NewTestSigner()
|
||||
Dummy.Signer = testSigner
|
||||
}
|
||||
66
vendor/github.com/joyent/triton-go/authentication/ecdsa_signature.go
generated
vendored
Normal file
66
vendor/github.com/joyent/triton-go/authentication/ecdsa_signature.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type ecdsaSignature struct {
|
||||
hashAlgorithm string
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}
|
||||
|
||||
func (s *ecdsaSignature) SignatureType() string {
|
||||
return fmt.Sprintf("ecdsa-%s", s.hashAlgorithm)
|
||||
}
|
||||
|
||||
func (s *ecdsaSignature) String() string {
|
||||
toEncode := struct {
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}{
|
||||
R: s.R,
|
||||
S: s.S,
|
||||
}
|
||||
|
||||
signatureBytes, err := asn1.Marshal(toEncode)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error marshaling signature: %s", err))
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(signatureBytes)
|
||||
}
|
||||
|
||||
func newECDSASignature(signatureBlob []byte) (*ecdsaSignature, error) {
|
||||
var ecSig struct {
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}
|
||||
|
||||
if err := ssh.Unmarshal(signatureBlob, &ecSig); err != nil {
|
||||
return nil, errwrap.Wrapf("Error unmarshaling signature: {{err}}", err)
|
||||
}
|
||||
|
||||
rValue := ecSig.R.Bytes()
|
||||
var hashAlgorithm string
|
||||
switch len(rValue) {
|
||||
case 31, 32:
|
||||
hashAlgorithm = "sha256"
|
||||
case 65, 66:
|
||||
hashAlgorithm = "sha512"
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported key length: %d", len(rValue))
|
||||
}
|
||||
|
||||
return &ecdsaSignature{
|
||||
hashAlgorithm: hashAlgorithm,
|
||||
R: ecSig.R,
|
||||
S: ecSig.S,
|
||||
}, nil
|
||||
}
|
||||
106
vendor/github.com/joyent/triton-go/authentication/private_key_signer.go
generated
vendored
Normal file
106
vendor/github.com/joyent/triton-go/authentication/private_key_signer.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type PrivateKeySigner struct {
|
||||
formattedKeyFingerprint string
|
||||
keyFingerprint string
|
||||
algorithm string
|
||||
accountName string
|
||||
hashFunc crypto.Hash
|
||||
|
||||
privateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func NewPrivateKeySigner(keyFingerprint string, privateKeyMaterial []byte, accountName string) (*PrivateKeySigner, error) {
|
||||
keyFingerprintMD5 := strings.Replace(keyFingerprint, ":", "", -1)
|
||||
|
||||
block, _ := pem.Decode(privateKeyMaterial)
|
||||
if block == nil {
|
||||
return nil, errors.New("Error PEM-decoding private key material: nil block received")
|
||||
}
|
||||
|
||||
rsakey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error parsing private key: {{err}}", err)
|
||||
}
|
||||
|
||||
sshPublicKey, err := ssh.NewPublicKey(rsakey.Public())
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error parsing SSH key from private key: {{err}}", err)
|
||||
}
|
||||
|
||||
matchKeyFingerprint := formatPublicKeyFingerprint(sshPublicKey, false)
|
||||
displayKeyFingerprint := formatPublicKeyFingerprint(sshPublicKey, true)
|
||||
if matchKeyFingerprint != keyFingerprintMD5 {
|
||||
return nil, errors.New("Private key file does not match public key fingerprint")
|
||||
}
|
||||
|
||||
signer := &PrivateKeySigner{
|
||||
formattedKeyFingerprint: displayKeyFingerprint,
|
||||
keyFingerprint: keyFingerprint,
|
||||
accountName: accountName,
|
||||
|
||||
hashFunc: crypto.SHA1,
|
||||
privateKey: rsakey,
|
||||
}
|
||||
|
||||
_, algorithm, err := signer.SignRaw("HelloWorld")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot sign using ssh agent: %s", err)
|
||||
}
|
||||
signer.algorithm = algorithm
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
func (s *PrivateKeySigner) Sign(dateHeader string) (string, error) {
|
||||
const headerName = "date"
|
||||
|
||||
hash := s.hashFunc.New()
|
||||
hash.Write([]byte(fmt.Sprintf("%s: %s", headerName, dateHeader)))
|
||||
digest := hash.Sum(nil)
|
||||
|
||||
signed, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, s.hashFunc, digest)
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf("Error signing date header: {{err}}", err)
|
||||
}
|
||||
signedBase64 := base64.StdEncoding.EncodeToString(signed)
|
||||
|
||||
keyID := fmt.Sprintf("/%s/keys/%s", s.accountName, s.formattedKeyFingerprint)
|
||||
return fmt.Sprintf(authorizationHeaderFormat, keyID, "rsa-sha1", headerName, signedBase64), nil
|
||||
}
|
||||
|
||||
func (s *PrivateKeySigner) SignRaw(toSign string) (string, string, error) {
|
||||
hash := s.hashFunc.New()
|
||||
hash.Write([]byte(toSign))
|
||||
digest := hash.Sum(nil)
|
||||
|
||||
signed, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, s.hashFunc, digest)
|
||||
if err != nil {
|
||||
return "", "", errwrap.Wrapf("Error signing date header: {{err}}", err)
|
||||
}
|
||||
signedBase64 := base64.StdEncoding.EncodeToString(signed)
|
||||
return signedBase64, "rsa-sha1", nil
|
||||
}
|
||||
|
||||
func (s *PrivateKeySigner) KeyFingerprint() string {
|
||||
return s.formattedKeyFingerprint
|
||||
}
|
||||
|
||||
func (s *PrivateKeySigner) DefaultAlgorithm() string {
|
||||
return s.algorithm
|
||||
}
|
||||
25
vendor/github.com/joyent/triton-go/authentication/rsa_signature.go
generated
vendored
Normal file
25
vendor/github.com/joyent/triton-go/authentication/rsa_signature.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
type rsaSignature struct {
|
||||
hashAlgorithm string
|
||||
signature []byte
|
||||
}
|
||||
|
||||
func (s *rsaSignature) SignatureType() string {
|
||||
return s.hashAlgorithm
|
||||
}
|
||||
|
||||
func (s *rsaSignature) String() string {
|
||||
return base64.StdEncoding.EncodeToString(s.signature)
|
||||
}
|
||||
|
||||
func newRSASignature(signatureBlob []byte) (*rsaSignature, error) {
|
||||
return &rsaSignature{
|
||||
hashAlgorithm: "rsa-sha1",
|
||||
signature: signatureBlob,
|
||||
}, nil
|
||||
}
|
||||
27
vendor/github.com/joyent/triton-go/authentication/signature.go
generated
vendored
Normal file
27
vendor/github.com/joyent/triton-go/authentication/signature.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type httpAuthSignature interface {
|
||||
SignatureType() string
|
||||
String() string
|
||||
}
|
||||
|
||||
func keyFormatToKeyType(keyFormat string) (string, error) {
|
||||
if keyFormat == "ssh-rsa" {
|
||||
return "rsa", nil
|
||||
}
|
||||
|
||||
if keyFormat == "ssh-ed25519" {
|
||||
return "ed25519", nil
|
||||
}
|
||||
|
||||
if regexp.MustCompile("^ecdsa-sha2-*").Match([]byte(keyFormat)) {
|
||||
return "ecdsa", nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Unknown key format: %s", keyFormat)
|
||||
}
|
||||
10
vendor/github.com/joyent/triton-go/authentication/signer.go
generated
vendored
Normal file
10
vendor/github.com/joyent/triton-go/authentication/signer.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package authentication
|
||||
|
||||
const authorizationHeaderFormat = `Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"`
|
||||
|
||||
type Signer interface {
|
||||
DefaultAlgorithm() string
|
||||
KeyFingerprint() string
|
||||
Sign(dateHeader string) (string, error)
|
||||
SignRaw(toSign string) (string, string, error)
|
||||
}
|
||||
170
vendor/github.com/joyent/triton-go/authentication/ssh_agent_signer.go
generated
vendored
Normal file
170
vendor/github.com/joyent/triton-go/authentication/ssh_agent_signer.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsetEnvVar = errors.New("SSH_AUTH_SOCK is not set")
|
||||
)
|
||||
|
||||
type SSHAgentSigner struct {
|
||||
formattedKeyFingerprint string
|
||||
keyFingerprint string
|
||||
algorithm string
|
||||
accountName string
|
||||
keyIdentifier string
|
||||
|
||||
agent agent.Agent
|
||||
key ssh.PublicKey
|
||||
}
|
||||
|
||||
func NewSSHAgentSigner(keyFingerprint, accountName string) (*SSHAgentSigner, error) {
|
||||
sshAgentAddress, agentOk := os.LookupEnv("SSH_AUTH_SOCK")
|
||||
if !agentOk {
|
||||
return nil, ErrUnsetEnvVar
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", sshAgentAddress)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error dialing SSH agent: {{err}}", err)
|
||||
}
|
||||
|
||||
ag := agent.NewClient(conn)
|
||||
|
||||
signer := &SSHAgentSigner{
|
||||
keyFingerprint: keyFingerprint,
|
||||
accountName: accountName,
|
||||
agent: ag,
|
||||
}
|
||||
|
||||
matchingKey, err := signer.MatchKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signer.key = matchingKey
|
||||
signer.formattedKeyFingerprint = formatPublicKeyFingerprint(signer.key, true)
|
||||
signer.keyIdentifier = fmt.Sprintf("/%s/keys/%s", signer.accountName, signer.formattedKeyFingerprint)
|
||||
|
||||
_, algorithm, err := signer.SignRaw("HelloWorld")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot sign using ssh agent: %s", err)
|
||||
}
|
||||
signer.algorithm = algorithm
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
func (s *SSHAgentSigner) MatchKey() (ssh.PublicKey, error) {
|
||||
keys, err := s.agent.List()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error listing keys in SSH Agent: %s", err)
|
||||
}
|
||||
|
||||
keyFingerprintStripped := strings.TrimPrefix(s.keyFingerprint, "MD5:")
|
||||
keyFingerprintStripped = strings.TrimPrefix(keyFingerprintStripped, "SHA256:")
|
||||
keyFingerprintStripped = strings.Replace(keyFingerprintStripped, ":", "", -1)
|
||||
|
||||
var matchingKey ssh.PublicKey
|
||||
for _, key := range keys {
|
||||
keyMD5 := md5.New()
|
||||
keyMD5.Write(key.Marshal())
|
||||
finalizedMD5 := fmt.Sprintf("%x", keyMD5.Sum(nil))
|
||||
|
||||
keySHA256 := sha256.New()
|
||||
keySHA256.Write(key.Marshal())
|
||||
finalizedSHA256 := base64.RawStdEncoding.EncodeToString(keySHA256.Sum(nil))
|
||||
|
||||
if keyFingerprintStripped == finalizedMD5 || keyFingerprintStripped == finalizedSHA256 {
|
||||
matchingKey = key
|
||||
}
|
||||
}
|
||||
|
||||
if matchingKey == nil {
|
||||
return nil, fmt.Errorf("No key in the SSH Agent matches fingerprint: %s", s.keyFingerprint)
|
||||
}
|
||||
|
||||
return matchingKey, nil
|
||||
}
|
||||
|
||||
func (s *SSHAgentSigner) Sign(dateHeader string) (string, error) {
|
||||
const headerName = "date"
|
||||
|
||||
signature, err := s.agent.Sign(s.key, []byte(fmt.Sprintf("%s: %s", headerName, dateHeader)))
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf("Error signing date header: {{err}}", err)
|
||||
}
|
||||
|
||||
keyFormat, err := keyFormatToKeyType(signature.Format)
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf("Error reading signature: {{err}}", err)
|
||||
}
|
||||
|
||||
var authSignature httpAuthSignature
|
||||
switch keyFormat {
|
||||
case "rsa":
|
||||
authSignature, err = newRSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf("Error reading signature: {{err}}", err)
|
||||
}
|
||||
case "ecdsa":
|
||||
authSignature, err = newECDSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf("Error reading signature: {{err}}", err)
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("Unsupported algorithm from SSH agent: %s", signature.Format)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(authorizationHeaderFormat, s.keyIdentifier,
|
||||
authSignature.SignatureType(), headerName, authSignature.String()), nil
|
||||
}
|
||||
|
||||
func (s *SSHAgentSigner) SignRaw(toSign string) (string, string, error) {
|
||||
signature, err := s.agent.Sign(s.key, []byte(toSign))
|
||||
if err != nil {
|
||||
return "", "", errwrap.Wrapf("Error signing string: {{err}}", err)
|
||||
}
|
||||
|
||||
keyFormat, err := keyFormatToKeyType(signature.Format)
|
||||
if err != nil {
|
||||
return "", "", errwrap.Wrapf("Error reading signature: {{err}}", err)
|
||||
}
|
||||
|
||||
var authSignature httpAuthSignature
|
||||
switch keyFormat {
|
||||
case "rsa":
|
||||
authSignature, err = newRSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", "", errwrap.Wrapf("Error reading signature: {{err}}", err)
|
||||
}
|
||||
case "ecdsa":
|
||||
authSignature, err = newECDSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", "", errwrap.Wrapf("Error reading signature: {{err}}", err)
|
||||
}
|
||||
default:
|
||||
return "", "", fmt.Errorf("Unsupported algorithm from SSH agent: %s", signature.Format)
|
||||
}
|
||||
|
||||
return authSignature.String(), authSignature.SignatureType(), nil
|
||||
}
|
||||
|
||||
func (s *SSHAgentSigner) KeyFingerprint() string {
|
||||
return s.formattedKeyFingerprint
|
||||
}
|
||||
|
||||
func (s *SSHAgentSigner) DefaultAlgorithm() string {
|
||||
return s.algorithm
|
||||
}
|
||||
27
vendor/github.com/joyent/triton-go/authentication/test_signer.go
generated
vendored
Normal file
27
vendor/github.com/joyent/triton-go/authentication/test_signer.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package authentication
|
||||
|
||||
// TestSigner represents an authentication key signer which we can use for
|
||||
// testing purposes only. This will largely be a stub to send through client
|
||||
// unit tests.
|
||||
type TestSigner struct{}
|
||||
|
||||
// NewTestSigner constructs a new instance of test signer
|
||||
func NewTestSigner() (Signer, error) {
|
||||
return &TestSigner{}, nil
|
||||
}
|
||||
|
||||
func (s *TestSigner) DefaultAlgorithm() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *TestSigner) KeyFingerprint() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *TestSigner) Sign(dateHeader string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *TestSigner) SignRaw(toSign string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
29
vendor/github.com/joyent/triton-go/authentication/util.go
generated
vendored
Normal file
29
vendor/github.com/joyent/triton-go/authentication/util.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// formatPublicKeyFingerprint produces the MD5 fingerprint of the given SSH
|
||||
// public key. If display is true, the fingerprint is formatted with colons
|
||||
// between each byte, as per the output of OpenSSL.
|
||||
func formatPublicKeyFingerprint(key ssh.PublicKey, display bool) string {
|
||||
publicKeyFingerprint := md5.New()
|
||||
publicKeyFingerprint.Write(key.Marshal())
|
||||
publicKeyFingerprintString := fmt.Sprintf("%x", publicKeyFingerprint.Sum(nil))
|
||||
|
||||
if !display {
|
||||
return publicKeyFingerprintString
|
||||
}
|
||||
|
||||
formatted := ""
|
||||
for i := 0; i < len(publicKeyFingerprintString); i = i + 2 {
|
||||
formatted = fmt.Sprintf("%s%s:", formatted, publicKeyFingerprintString[i:i+2])
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(formatted, ":")
|
||||
}
|
||||
421
vendor/github.com/joyent/triton-go/client/client.go
generated
vendored
Normal file
421
vendor/github.com/joyent/triton-go/client/client.go
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/joyent/triton-go/authentication"
|
||||
)
|
||||
|
||||
const nilContext = "nil context"
|
||||
|
||||
var (
|
||||
ErrDefaultAuth = errors.New("default SSH agent authentication requires SDC_KEY_ID and SSH_AUTH_SOCK")
|
||||
ErrAccountName = errors.New("missing account name for Triton/Manta")
|
||||
ErrMissingURL = errors.New("missing Triton and/or Manta URL")
|
||||
|
||||
BadTritonURL = "invalid format of triton URL"
|
||||
BadMantaURL = "invalid format of manta URL"
|
||||
)
|
||||
|
||||
// Client represents a connection to the Triton Compute or Object Storage APIs.
|
||||
type Client struct {
|
||||
HTTPClient *http.Client
|
||||
Authorizers []authentication.Signer
|
||||
TritonURL url.URL
|
||||
MantaURL url.URL
|
||||
AccountName string
|
||||
}
|
||||
|
||||
// New is used to construct a Client in order to make API
|
||||
// requests to the Triton API.
|
||||
//
|
||||
// At least one signer must be provided - example signers include
|
||||
// authentication.PrivateKeySigner and authentication.SSHAgentSigner.
|
||||
func New(tritonURL string, mantaURL string, accountName string, signers ...authentication.Signer) (*Client, error) {
|
||||
if accountName == "" {
|
||||
return nil, ErrAccountName
|
||||
}
|
||||
|
||||
if tritonURL == "" && mantaURL == "" {
|
||||
return nil, ErrMissingURL
|
||||
}
|
||||
|
||||
cloudURL, err := url.Parse(tritonURL)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(BadTritonURL+": {{err}}", err)
|
||||
}
|
||||
|
||||
storageURL, err := url.Parse(mantaURL)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(BadMantaURL+": {{err}}", err)
|
||||
}
|
||||
|
||||
authorizers := make([]authentication.Signer, 0)
|
||||
for _, key := range signers {
|
||||
if key != nil {
|
||||
authorizers = append(authorizers, key)
|
||||
}
|
||||
}
|
||||
|
||||
newClient := &Client{
|
||||
HTTPClient: &http.Client{
|
||||
Transport: httpTransport(false),
|
||||
CheckRedirect: doNotFollowRedirects,
|
||||
},
|
||||
Authorizers: authorizers,
|
||||
TritonURL: *cloudURL,
|
||||
MantaURL: *storageURL,
|
||||
AccountName: accountName,
|
||||
}
|
||||
|
||||
// Default to constructing an SSHAgentSigner if there are no other signers
|
||||
// passed into NewClient and there's an SDC_KEY_ID and SSH_AUTH_SOCK
|
||||
// available in the user's environ(7).
|
||||
if len(newClient.Authorizers) == 0 {
|
||||
if err := newClient.DefaultAuth(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newClient, nil
|
||||
}
|
||||
|
||||
// initDefaultAuth provides a default key signer for a client. This should only
|
||||
// be used internally if the client has no other key signer for authenticating
|
||||
// with Triton. We first look for both `SDC_KEY_ID` and `SSH_AUTH_SOCK` in the
|
||||
// user's environ(7). If so we default to the SSH agent key signer.
|
||||
func (c *Client) DefaultAuth() error {
|
||||
if keyID, keyOk := os.LookupEnv("SDC_KEY_ID"); keyOk {
|
||||
defaultSigner, err := authentication.NewSSHAgentSigner(keyID, c.AccountName)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("problem initializing NewSSHAgentSigner: {{err}}", err)
|
||||
}
|
||||
c.Authorizers = append(c.Authorizers, defaultSigner)
|
||||
} else {
|
||||
return ErrDefaultAuth
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsecureSkipTLSVerify turns off TLS verification for the client connection. This
|
||||
// allows connection to an endpoint with a certificate which was signed by a non-
|
||||
// trusted CA, such as self-signed certificates. This can be useful when connecting
|
||||
// to temporary Triton installations such as Triton Cloud-On-A-Laptop.
|
||||
func (c *Client) InsecureSkipTLSVerify() {
|
||||
if c.HTTPClient == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.HTTPClient.Transport = httpTransport(true)
|
||||
}
|
||||
|
||||
func httpTransport(insecureSkipTLSVerify bool) *http.Transport {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
MaxIdleConns: 10,
|
||||
IdleConnTimeout: 15 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: insecureSkipTLSVerify,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func doNotFollowRedirects(*http.Request, []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
// TODO(justinwr): Deprecated?
|
||||
// func (c *Client) FormatURL(path string) string {
|
||||
// return fmt.Sprintf("%s%s", c.Endpoint, path)
|
||||
// }
|
||||
|
||||
func (c *Client) DecodeError(statusCode int, body io.Reader) error {
|
||||
err := &TritonError{
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
|
||||
errorDecoder := json.NewDecoder(body)
|
||||
if err := errorDecoder.Decode(err); err != nil {
|
||||
return errwrap.Wrapf("Error decoding error response: {{err}}", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type RequestInput struct {
|
||||
Method string
|
||||
Path string
|
||||
Query *url.Values
|
||||
Headers *http.Header
|
||||
Body interface{}
|
||||
}
|
||||
|
||||
func (c *Client) ExecuteRequestURIParams(ctx context.Context, inputs RequestInput) (io.ReadCloser, error) {
|
||||
method := inputs.Method
|
||||
path := inputs.Path
|
||||
body := inputs.Body
|
||||
query := inputs.Query
|
||||
|
||||
var requestBody io.Reader
|
||||
if body != nil {
|
||||
marshaled, err := json.MarshalIndent(body, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestBody = bytes.NewReader(marshaled)
|
||||
}
|
||||
|
||||
endpoint := c.TritonURL
|
||||
endpoint.Path = path
|
||||
if query != nil {
|
||||
endpoint.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, endpoint.String(), requestBody)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
||||
req.Header.Set("date", dateHeader)
|
||||
|
||||
// NewClient ensures there's always an authorizer (unless this is called
|
||||
// outside that constructor).
|
||||
authHeader, err := c.Authorizers[0].Sign(dateHeader)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err)
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Accept-Version", "8")
|
||||
req.Header.Set("User-Agent", "triton-go Client API")
|
||||
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
return nil, c.DecodeError(resp.StatusCode, resp.Body)
|
||||
}
|
||||
|
||||
func (c *Client) ExecuteRequest(ctx context.Context, inputs RequestInput) (io.ReadCloser, error) {
|
||||
return c.ExecuteRequestURIParams(ctx, inputs)
|
||||
}
|
||||
|
||||
func (c *Client) ExecuteRequestRaw(ctx context.Context, inputs RequestInput) (*http.Response, error) {
|
||||
method := inputs.Method
|
||||
path := inputs.Path
|
||||
body := inputs.Body
|
||||
|
||||
var requestBody io.Reader
|
||||
if body != nil {
|
||||
marshaled, err := json.MarshalIndent(body, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestBody = bytes.NewReader(marshaled)
|
||||
}
|
||||
|
||||
endpoint := c.TritonURL
|
||||
endpoint.Path = path
|
||||
|
||||
req, err := http.NewRequest(method, endpoint.String(), requestBody)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
||||
req.Header.Set("date", dateHeader)
|
||||
|
||||
// NewClient ensures there's always an authorizer (unless this is called
|
||||
// outside that constructor).
|
||||
authHeader, err := c.Authorizers[0].Sign(dateHeader)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err)
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Accept-Version", "8")
|
||||
req.Header.Set("User-Agent", "triton-go c API")
|
||||
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) ExecuteRequestStorage(ctx context.Context, inputs RequestInput) (io.ReadCloser, http.Header, error) {
|
||||
method := inputs.Method
|
||||
path := inputs.Path
|
||||
query := inputs.Query
|
||||
headers := inputs.Headers
|
||||
body := inputs.Body
|
||||
|
||||
endpoint := c.MantaURL
|
||||
endpoint.Path = path
|
||||
|
||||
var requestBody io.Reader
|
||||
if body != nil {
|
||||
marshaled, err := json.MarshalIndent(body, "", " ")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
requestBody = bytes.NewReader(marshaled)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, endpoint.String(), requestBody)
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
if body != nil && (headers == nil || headers.Get("Content-Type") == "") {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
if headers != nil {
|
||||
for key, values := range *headers {
|
||||
for _, value := range values {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
||||
req.Header.Set("date", dateHeader)
|
||||
|
||||
authHeader, err := c.Authorizers[0].Sign(dateHeader)
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err)
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("User-Agent", "manta-go client API")
|
||||
|
||||
if query != nil {
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return resp.Body, resp.Header, nil
|
||||
}
|
||||
|
||||
mantaError := &MantaError{
|
||||
StatusCode: resp.StatusCode,
|
||||
}
|
||||
|
||||
if req.Method != http.MethodHead {
|
||||
errorDecoder := json.NewDecoder(resp.Body)
|
||||
if err := errorDecoder.Decode(mantaError); err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error decoding error response: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
if mantaError.Message == "" {
|
||||
mantaError.Message = fmt.Sprintf("HTTP response returned status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil, nil, mantaError
|
||||
}
|
||||
|
||||
type RequestNoEncodeInput struct {
|
||||
Method string
|
||||
Path string
|
||||
Query *url.Values
|
||||
Headers *http.Header
|
||||
Body io.Reader
|
||||
}
|
||||
|
||||
func (c *Client) ExecuteRequestNoEncode(ctx context.Context, inputs RequestNoEncodeInput) (io.ReadCloser, http.Header, error) {
|
||||
method := inputs.Method
|
||||
path := inputs.Path
|
||||
query := inputs.Query
|
||||
headers := inputs.Headers
|
||||
body := inputs.Body
|
||||
|
||||
endpoint := c.MantaURL
|
||||
endpoint.Path = path
|
||||
|
||||
req, err := http.NewRequest(method, endpoint.String(), body)
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error constructing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
for key, values := range *headers {
|
||||
for _, value := range values {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dateHeader := time.Now().UTC().Format(time.RFC1123)
|
||||
req.Header.Set("date", dateHeader)
|
||||
|
||||
authHeader, err := c.Authorizers[0].Sign(dateHeader)
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error signing HTTP request: {{err}}", err)
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("User-Agent", "manta-go client API")
|
||||
|
||||
if query != nil {
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error executing HTTP request: {{err}}", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return resp.Body, resp.Header, nil
|
||||
}
|
||||
|
||||
mantaError := &MantaError{
|
||||
StatusCode: resp.StatusCode,
|
||||
}
|
||||
|
||||
errorDecoder := json.NewDecoder(resp.Body)
|
||||
if err := errorDecoder.Decode(mantaError); err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error decoding error response: {{err}}", err)
|
||||
}
|
||||
return nil, nil, mantaError
|
||||
}
|
||||
190
vendor/github.com/joyent/triton-go/client/errors.go
generated
vendored
Normal file
190
vendor/github.com/joyent/triton-go/client/errors.go
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
)
|
||||
|
||||
// ClientError represents an error code and message along with the status code
|
||||
// of the HTTP request which resulted in the error message.
|
||||
type ClientError struct {
|
||||
StatusCode int
|
||||
Code string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error implements interface Error on the TritonError type.
|
||||
func (e ClientError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// MantaError represents an error code and message along with
|
||||
// the status code of the HTTP request which resulted in the error
|
||||
// message. Error codes used by the Manta API are listed at
|
||||
// https://apidocs.joyent.com/manta/api.html#errors
|
||||
type MantaError struct {
|
||||
StatusCode int
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Error implements interface Error on the MantaError type.
|
||||
func (e MantaError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// TritonError represents an error code and message along with
|
||||
// the status code of the HTTP request which resulted in the error
|
||||
// message. Error codes used by the Triton API are listed at
|
||||
// https://apidocs.joyent.com/cloudapi/#cloudapi-http-responses
|
||||
type TritonError struct {
|
||||
StatusCode int
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Error implements interface Error on the TritonError type.
|
||||
func (e TritonError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func IsAuthSchemeError(err error) bool {
|
||||
return isSpecificError(err, "AuthScheme")
|
||||
}
|
||||
func IsAuthorizationError(err error) bool {
|
||||
return isSpecificError(err, "Authorization")
|
||||
}
|
||||
func IsBadRequestError(err error) bool {
|
||||
return isSpecificError(err, "BadRequest")
|
||||
}
|
||||
func IsChecksumError(err error) bool {
|
||||
return isSpecificError(err, "Checksum")
|
||||
}
|
||||
func IsConcurrentRequestError(err error) bool {
|
||||
return isSpecificError(err, "ConcurrentRequest")
|
||||
}
|
||||
func IsContentLengthError(err error) bool {
|
||||
return isSpecificError(err, "ContentLength")
|
||||
}
|
||||
func IsContentMD5MismatchError(err error) bool {
|
||||
return isSpecificError(err, "ContentMD5Mismatch")
|
||||
}
|
||||
func IsEntityExistsError(err error) bool {
|
||||
return isSpecificError(err, "EntityExists")
|
||||
}
|
||||
func IsInvalidArgumentError(err error) bool {
|
||||
return isSpecificError(err, "InvalidArgument")
|
||||
}
|
||||
func IsInvalidAuthTokenError(err error) bool {
|
||||
return isSpecificError(err, "InvalidAuthToken")
|
||||
}
|
||||
func IsInvalidCredentialsError(err error) bool {
|
||||
return isSpecificError(err, "InvalidCredentials")
|
||||
}
|
||||
func IsInvalidDurabilityLevelError(err error) bool {
|
||||
return isSpecificError(err, "InvalidDurabilityLevel")
|
||||
}
|
||||
func IsInvalidKeyIdError(err error) bool {
|
||||
return isSpecificError(err, "InvalidKeyId")
|
||||
}
|
||||
func IsInvalidJobError(err error) bool {
|
||||
return isSpecificError(err, "InvalidJob")
|
||||
}
|
||||
func IsInvalidLinkError(err error) bool {
|
||||
return isSpecificError(err, "InvalidLink")
|
||||
}
|
||||
func IsInvalidLimitError(err error) bool {
|
||||
return isSpecificError(err, "InvalidLimit")
|
||||
}
|
||||
func IsInvalidSignatureError(err error) bool {
|
||||
return isSpecificError(err, "InvalidSignature")
|
||||
}
|
||||
func IsInvalidUpdateError(err error) bool {
|
||||
return isSpecificError(err, "InvalidUpdate")
|
||||
}
|
||||
func IsDirectoryDoesNotExistError(err error) bool {
|
||||
return isSpecificError(err, "DirectoryDoesNotExist")
|
||||
}
|
||||
func IsDirectoryExistsError(err error) bool {
|
||||
return isSpecificError(err, "DirectoryExists")
|
||||
}
|
||||
func IsDirectoryNotEmptyError(err error) bool {
|
||||
return isSpecificError(err, "DirectoryNotEmpty")
|
||||
}
|
||||
func IsDirectoryOperationError(err error) bool {
|
||||
return isSpecificError(err, "DirectoryOperation")
|
||||
}
|
||||
func IsInternalError(err error) bool {
|
||||
return isSpecificError(err, "Internal")
|
||||
}
|
||||
func IsJobNotFoundError(err error) bool {
|
||||
return isSpecificError(err, "JobNotFound")
|
||||
}
|
||||
func IsJobStateError(err error) bool {
|
||||
return isSpecificError(err, "JobState")
|
||||
}
|
||||
func IsKeyDoesNotExistError(err error) bool {
|
||||
return isSpecificError(err, "KeyDoesNotExist")
|
||||
}
|
||||
func IsNotAcceptableError(err error) bool {
|
||||
return isSpecificError(err, "NotAcceptable")
|
||||
}
|
||||
func IsNotEnoughSpaceError(err error) bool {
|
||||
return isSpecificError(err, "NotEnoughSpace")
|
||||
}
|
||||
func IsLinkNotFoundError(err error) bool {
|
||||
return isSpecificError(err, "LinkNotFound")
|
||||
}
|
||||
func IsLinkNotObjectError(err error) bool {
|
||||
return isSpecificError(err, "LinkNotObject")
|
||||
}
|
||||
func IsLinkRequiredError(err error) bool {
|
||||
return isSpecificError(err, "LinkRequired")
|
||||
}
|
||||
func IsParentNotDirectoryError(err error) bool {
|
||||
return isSpecificError(err, "ParentNotDirectory")
|
||||
}
|
||||
func IsPreconditionFailedError(err error) bool {
|
||||
return isSpecificError(err, "PreconditionFailed")
|
||||
}
|
||||
func IsPreSignedRequestError(err error) bool {
|
||||
return isSpecificError(err, "PreSignedRequest")
|
||||
}
|
||||
func IsRequestEntityTooLargeError(err error) bool {
|
||||
return isSpecificError(err, "RequestEntityTooLarge")
|
||||
}
|
||||
func IsResourceNotFoundError(err error) bool {
|
||||
return isSpecificError(err, "ResourceNotFound")
|
||||
}
|
||||
func IsRootDirectoryError(err error) bool {
|
||||
return isSpecificError(err, "RootDirectory")
|
||||
}
|
||||
func IsServiceUnavailableError(err error) bool {
|
||||
return isSpecificError(err, "ServiceUnavailable")
|
||||
}
|
||||
func IsSSLRequiredError(err error) bool {
|
||||
return isSpecificError(err, "SSLRequired")
|
||||
}
|
||||
func IsUploadTimeoutError(err error) bool {
|
||||
return isSpecificError(err, "UploadTimeout")
|
||||
}
|
||||
func IsUserDoesNotExistError(err error) bool {
|
||||
return isSpecificError(err, "UserDoesNotExist")
|
||||
}
|
||||
|
||||
// isSpecificError checks whether the error represented by err wraps
|
||||
// an underlying MantaError with code errorCode.
|
||||
func isSpecificError(err error, errorCode string) bool {
|
||||
tritonErrorInterface := errwrap.GetType(err.(error), &MantaError{})
|
||||
if tritonErrorInterface == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
tritonErr := tritonErrorInterface.(*MantaError)
|
||||
if tritonErr.Code == errorCode {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
51
vendor/github.com/joyent/triton-go/storage/client.go
generated
vendored
Normal file
51
vendor/github.com/joyent/triton-go/storage/client.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
triton "github.com/joyent/triton-go"
|
||||
"github.com/joyent/triton-go/client"
|
||||
)
|
||||
|
||||
type StorageClient struct {
|
||||
Client *client.Client
|
||||
}
|
||||
|
||||
func newStorageClient(client *client.Client) *StorageClient {
|
||||
return &StorageClient{
|
||||
Client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient returns a new client for working with Storage endpoints and
|
||||
// resources within CloudAPI
|
||||
func NewClient(config *triton.ClientConfig) (*StorageClient, error) {
|
||||
// TODO: Utilize config interface within the function itself
|
||||
client, err := client.New(config.TritonURL, config.MantaURL, config.AccountName, config.Signers...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStorageClient(client), nil
|
||||
}
|
||||
|
||||
// Dir returns a DirectoryClient used for accessing functions pertaining to
|
||||
// Directories functionality of the Manta API.
|
||||
func (c *StorageClient) Dir() *DirectoryClient {
|
||||
return &DirectoryClient{c.Client}
|
||||
}
|
||||
|
||||
// Jobs returns a JobClient used for accessing functions pertaining to Jobs
|
||||
// functionality of the Triton Object Storage API.
|
||||
func (c *StorageClient) Jobs() *JobClient {
|
||||
return &JobClient{c.Client}
|
||||
}
|
||||
|
||||
// Objects returns an ObjectsClient used for accessing functions pertaining to
|
||||
// Objects functionality of the Triton Object Storage API.
|
||||
func (c *StorageClient) Objects() *ObjectsClient {
|
||||
return &ObjectsClient{c.Client}
|
||||
}
|
||||
|
||||
// SnapLinks returns an SnapLinksClient used for accessing functions pertaining to
|
||||
// SnapLinks functionality of the Triton Object Storage API.
|
||||
func (c *StorageClient) SnapLinks() *SnapLinksClient {
|
||||
return &SnapLinksClient{c.Client}
|
||||
}
|
||||
199
vendor/github.com/joyent/triton-go/storage/directory.go
generated
vendored
Normal file
199
vendor/github.com/joyent/triton-go/storage/directory.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/joyent/triton-go/client"
|
||||
)
|
||||
|
||||
type DirectoryClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// DirectoryEntry represents an object or directory in Manta.
|
||||
type DirectoryEntry struct {
|
||||
ETag string `json:"etag"`
|
||||
ModifiedTime time.Time `json:"mtime"`
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ListDirectoryInput represents parameters to a List operation.
|
||||
type ListDirectoryInput struct {
|
||||
DirectoryName string
|
||||
Limit uint64
|
||||
Marker string
|
||||
}
|
||||
|
||||
// ListDirectoryOutput contains the outputs of a List operation.
|
||||
type ListDirectoryOutput struct {
|
||||
Entries []*DirectoryEntry
|
||||
ResultSetSize uint64
|
||||
}
|
||||
|
||||
// List lists the contents of a directory on the Triton Object Store service.
|
||||
func (s *DirectoryClient) List(ctx context.Context, input *ListDirectoryInput) (*ListDirectoryOutput, error) {
|
||||
absPath := absFileInput(s.client.AccountName, input.DirectoryName)
|
||||
query := &url.Values{}
|
||||
if input.Limit != 0 {
|
||||
query.Set("limit", strconv.FormatUint(input.Limit, 10))
|
||||
}
|
||||
if input.Marker != "" {
|
||||
query.Set("manta_path", input.Marker)
|
||||
}
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: string(absPath),
|
||||
Query: query,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing List request: {{err}}", err)
|
||||
}
|
||||
defer respBody.Close()
|
||||
|
||||
var results []*DirectoryEntry
|
||||
scanner := bufio.NewScanner(respBody)
|
||||
for scanner.Scan() {
|
||||
current := &DirectoryEntry{}
|
||||
if err := json.Unmarshal(scanner.Bytes(), current); err != nil {
|
||||
return nil, errwrap.Wrapf("error decoding list response: {{err}}", err)
|
||||
}
|
||||
|
||||
results = append(results, current)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, errwrap.Wrapf("error decoding list responses: {{err}}", err)
|
||||
}
|
||||
|
||||
output := &ListDirectoryOutput{
|
||||
Entries: results,
|
||||
}
|
||||
|
||||
resultSetSize, err := strconv.ParseUint(respHeader.Get("Result-Set-Size"), 10, 64)
|
||||
if err == nil {
|
||||
output.ResultSetSize = resultSetSize
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// PutDirectoryInput represents parameters to a Put operation.
|
||||
type PutDirectoryInput struct {
|
||||
DirectoryName string
|
||||
}
|
||||
|
||||
// Put puts a directoy into the Triton Object Storage service is an idempotent
|
||||
// create-or-update operation. Your private namespace starts at /:login, and you
|
||||
// can create any nested set of directories or objects within it.
|
||||
func (s *DirectoryClient) Put(ctx context.Context, input *PutDirectoryInput) error {
|
||||
absPath := absFileInput(s.client.AccountName, input.DirectoryName)
|
||||
|
||||
headers := &http.Header{}
|
||||
headers.Set("Content-Type", "application/json; type=directory")
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodPut,
|
||||
Path: string(absPath),
|
||||
Headers: headers,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing Put request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDirectoryInput represents parameters to a Delete operation.
|
||||
type DeleteDirectoryInput struct {
|
||||
DirectoryName string
|
||||
ForceDelete bool //Will recursively delete all child directories and objects
|
||||
}
|
||||
|
||||
// Delete deletes a directory on the Triton Object Storage. The directory must
|
||||
// be empty.
|
||||
func (s *DirectoryClient) Delete(ctx context.Context, input *DeleteDirectoryInput) error {
|
||||
absPath := absFileInput(s.client.AccountName, input.DirectoryName)
|
||||
|
||||
if input.ForceDelete {
|
||||
err := deleteAll(*s, ctx, absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := deleteDirectory(*s, ctx, absPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteAll(c DirectoryClient, ctx context.Context, directoryPath _AbsCleanPath) error {
|
||||
objs, err := c.List(ctx, &ListDirectoryInput{
|
||||
DirectoryName: string(directoryPath),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, obj := range objs.Entries {
|
||||
newPath := absFileInput(c.client.AccountName, path.Join(string(directoryPath), obj.Name))
|
||||
if obj.Type == "directory" {
|
||||
err := deleteDirectory(c, ctx, newPath)
|
||||
if err != nil {
|
||||
return deleteAll(c, ctx, newPath)
|
||||
}
|
||||
} else {
|
||||
return deleteObject(c, ctx, newPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDirectory(c DirectoryClient, ctx context.Context, directoryPath _AbsCleanPath) error {
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodDelete,
|
||||
Path: string(directoryPath),
|
||||
}
|
||||
respBody, _, err := c.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing DeleteDirectory request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteObject(c DirectoryClient, ctx context.Context, path _AbsCleanPath) error {
|
||||
objClient := &ObjectsClient{
|
||||
client: c.client,
|
||||
}
|
||||
|
||||
err := objClient.Delete(ctx, &DeleteObjectInput{
|
||||
ObjectPath: string(path),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
440
vendor/github.com/joyent/triton-go/storage/job.go
generated
vendored
Normal file
440
vendor/github.com/joyent/triton-go/storage/job.go
generated
vendored
Normal file
@@ -0,0 +1,440 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/joyent/triton-go/client"
|
||||
)
|
||||
|
||||
type JobClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
const (
|
||||
JobStateDone = "done"
|
||||
JobStateRunning = "running"
|
||||
)
|
||||
|
||||
// JobPhase represents the specification for a map or reduce phase of a Manta
|
||||
// job.
|
||||
type JobPhase struct {
|
||||
// Type is the type of phase. Must be `map` or `reduce`.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
// Assets is an array of objects to be placed in your compute zones.
|
||||
Assets []string `json:"assets,omitempty"`
|
||||
|
||||
// Exec is the shell statement to execute. It may be any valid shell
|
||||
// command, including pipelines and other shell syntax. You can also
|
||||
// execute programs stored in the service by including them in "assets"
|
||||
// and referencing them as /assets/$manta_path.
|
||||
Exec string `json:"exec"`
|
||||
|
||||
// Init is a shell statement to execute in each compute zone before
|
||||
// any tasks are executed. The same constraints apply as to Exec.
|
||||
Init string `json:"init"`
|
||||
|
||||
// ReducerCount is an optional number of reducers for this phase. The
|
||||
// default value if not specified is 1. The maximum value is 1024.
|
||||
ReducerCount uint `json:"count,omitempty"`
|
||||
|
||||
// Memory is the amount of DRAM in MB to be allocated to the compute
|
||||
// zone. Valid values are 256, 512, 1024, 2048, 4096 or 8192.
|
||||
Memory uint64 `json:"memory,omitempty"`
|
||||
|
||||
// Disk is the amount of disk space in GB to be allocated to the compute
|
||||
// zone. Valid values are 2, 4, 8, 16, 32, 64, 128, 256, 512 or 1024.
|
||||
Disk uint64 `json:"disk,omitempty"`
|
||||
}
|
||||
|
||||
// JobSummary represents the summary of a compute job in Manta.
|
||||
type JobSummary struct {
|
||||
ModifiedTime time.Time `json:"mtime"`
|
||||
ID string `json:"name"`
|
||||
}
|
||||
|
||||
// Job represents a compute job in Manta.
|
||||
type Job struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Phases []*JobPhase `json:"phases"`
|
||||
State string `json:"state"`
|
||||
Cancelled bool `json:"cancelled"`
|
||||
InputDone bool `json:"inputDone"`
|
||||
CreatedTime time.Time `json:"timeCreated"`
|
||||
DoneTime time.Time `json:"timeDone"`
|
||||
Transient bool `json:"transient"`
|
||||
Stats *JobStats `json:"stats"`
|
||||
}
|
||||
|
||||
// JobStats represents statistics for a compute job in Manta.
|
||||
type JobStats struct {
|
||||
Errors uint64 `json:"errors"`
|
||||
Outputs uint64 `json:"outputs"`
|
||||
Retries uint64 `json:"retries"`
|
||||
Tasks uint64 `json:"tasks"`
|
||||
TasksDone uint64 `json:"tasksDone"`
|
||||
}
|
||||
|
||||
// CreateJobInput represents parameters to a CreateJob operation.
|
||||
type CreateJobInput struct {
|
||||
Name string `json:"name"`
|
||||
Phases []*JobPhase `json:"phases"`
|
||||
}
|
||||
|
||||
// CreateJobOutput contains the outputs of a CreateJob operation.
|
||||
type CreateJobOutput struct {
|
||||
JobID string
|
||||
}
|
||||
|
||||
// CreateJob submits a new job to be executed. This call is not
|
||||
// idempotent, so calling it twice will create two jobs.
|
||||
func (s *JobClient) Create(ctx context.Context, input *CreateJobInput) (*CreateJobOutput, error) {
|
||||
path := fmt.Sprintf("/%s/jobs", s.client.AccountName)
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodPost,
|
||||
Path: path,
|
||||
Body: input,
|
||||
}
|
||||
respBody, respHeaders, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing CreateJob request: {{err}}", err)
|
||||
}
|
||||
|
||||
jobURI := respHeaders.Get("Location")
|
||||
parts := strings.Split(jobURI, "/")
|
||||
jobID := parts[len(parts)-1]
|
||||
|
||||
response := &CreateJobOutput{
|
||||
JobID: jobID,
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// AddJobInputs represents parameters to a AddJobInputs operation.
|
||||
type AddJobInputsInput struct {
|
||||
JobID string
|
||||
ObjectPaths []string
|
||||
}
|
||||
|
||||
// AddJobInputs submits inputs to an already created job.
|
||||
func (s *JobClient) AddInputs(ctx context.Context, input *AddJobInputsInput) error {
|
||||
path := fmt.Sprintf("/%s/jobs/%s/live/in", s.client.AccountName, input.JobID)
|
||||
headers := &http.Header{}
|
||||
headers.Set("Content-Type", "text/plain")
|
||||
|
||||
reader := strings.NewReader(strings.Join(input.ObjectPaths, "\n"))
|
||||
|
||||
reqInput := client.RequestNoEncodeInput{
|
||||
Method: http.MethodPost,
|
||||
Path: path,
|
||||
Headers: headers,
|
||||
Body: reader,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestNoEncode(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing AddJobInputs request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EndJobInputInput represents parameters to a EndJobInput operation.
|
||||
type EndJobInputInput struct {
|
||||
JobID string
|
||||
}
|
||||
|
||||
// EndJobInput submits inputs to an already created job.
|
||||
func (s *JobClient) EndInput(ctx context.Context, input *EndJobInputInput) error {
|
||||
path := fmt.Sprintf("/%s/jobs/%s/live/in/end", s.client.AccountName, input.JobID)
|
||||
|
||||
reqInput := client.RequestNoEncodeInput{
|
||||
Method: http.MethodPost,
|
||||
Path: path,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestNoEncode(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing EndJobInput request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CancelJobInput represents parameters to a CancelJob operation.
|
||||
type CancelJobInput struct {
|
||||
JobID string
|
||||
}
|
||||
|
||||
// CancelJob cancels a job from doing any further work. Cancellation
|
||||
// is asynchronous and "best effort"; there is no guarantee the job
|
||||
// will actually stop. For example, short jobs where input is already
|
||||
// closed will likely still run to completion.
|
||||
//
|
||||
// This is however useful when:
|
||||
// - input is still open
|
||||
// - you have a long-running job
|
||||
func (s *JobClient) Cancel(ctx context.Context, input *CancelJobInput) error {
|
||||
path := fmt.Sprintf("/%s/jobs/%s/live/cancel", s.client.AccountName, input.JobID)
|
||||
|
||||
reqInput := client.RequestNoEncodeInput{
|
||||
Method: http.MethodPost,
|
||||
Path: path,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestNoEncode(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing CancelJob request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListJobsInput represents parameters to a ListJobs operation.
|
||||
type ListJobsInput struct {
|
||||
RunningOnly bool
|
||||
Limit uint64
|
||||
Marker string
|
||||
}
|
||||
|
||||
// ListJobsOutput contains the outputs of a ListJobs operation.
|
||||
type ListJobsOutput struct {
|
||||
Jobs []*JobSummary
|
||||
ResultSetSize uint64
|
||||
}
|
||||
|
||||
// ListJobs returns the list of jobs you currently have.
|
||||
func (s *JobClient) List(ctx context.Context, input *ListJobsInput) (*ListJobsOutput, error) {
|
||||
path := fmt.Sprintf("/%s/jobs", s.client.AccountName)
|
||||
query := &url.Values{}
|
||||
if input.RunningOnly {
|
||||
query.Set("state", "running")
|
||||
}
|
||||
if input.Limit != 0 {
|
||||
query.Set("limit", strconv.FormatUint(input.Limit, 10))
|
||||
}
|
||||
if input.Marker != "" {
|
||||
query.Set("manta_path", input.Marker)
|
||||
}
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: path,
|
||||
Query: query,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing ListJobs request: {{err}}", err)
|
||||
}
|
||||
|
||||
var results []*JobSummary
|
||||
for {
|
||||
current := &JobSummary{}
|
||||
decoder := json.NewDecoder(respBody)
|
||||
if err = decoder.Decode(¤t); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, errwrap.Wrapf("Error decoding ListJobs response: {{err}}", err)
|
||||
}
|
||||
results = append(results, current)
|
||||
}
|
||||
|
||||
output := &ListJobsOutput{
|
||||
Jobs: results,
|
||||
}
|
||||
|
||||
resultSetSize, err := strconv.ParseUint(respHeader.Get("Result-Set-Size"), 10, 64)
|
||||
if err == nil {
|
||||
output.ResultSetSize = resultSetSize
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// GetJobInput represents parameters to a GetJob operation.
|
||||
type GetJobInput struct {
|
||||
JobID string
|
||||
}
|
||||
|
||||
// GetJobOutput contains the outputs of a GetJob operation.
|
||||
type GetJobOutput struct {
|
||||
Job *Job
|
||||
}
|
||||
|
||||
// GetJob returns the list of jobs you currently have.
|
||||
func (s *JobClient) Get(ctx context.Context, input *GetJobInput) (*GetJobOutput, error) {
|
||||
path := fmt.Sprintf("/%s/jobs/%s/live/status", s.client.AccountName, input.JobID)
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: path,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing GetJob request: {{err}}", err)
|
||||
}
|
||||
|
||||
job := &Job{}
|
||||
decoder := json.NewDecoder(respBody)
|
||||
if err = decoder.Decode(&job); err != nil {
|
||||
return nil, errwrap.Wrapf("Error decoding GetJob response: {{err}}", err)
|
||||
}
|
||||
|
||||
return &GetJobOutput{
|
||||
Job: job,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetJobOutputInput represents parameters to a GetJobOutput operation.
|
||||
type GetJobOutputInput struct {
|
||||
JobID string
|
||||
}
|
||||
|
||||
// GetJobOutputOutput contains the outputs for a GetJobOutput operation. It is your
|
||||
// responsibility to ensure that the io.ReadCloser Items is closed.
|
||||
type GetJobOutputOutput struct {
|
||||
ResultSetSize uint64
|
||||
Items io.ReadCloser
|
||||
}
|
||||
|
||||
// GetJobOutput returns the current "live" set of outputs from a job. Think of
|
||||
// this like `tail -f`. If error is nil (i.e. the operation is successful), it is
|
||||
// your responsibility to close the io.ReadCloser named Items in the output.
|
||||
func (s *JobClient) GetOutput(ctx context.Context, input *GetJobOutputInput) (*GetJobOutputOutput, error) {
|
||||
path := fmt.Sprintf("/%s/jobs/%s/live/out", s.client.AccountName, input.JobID)
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: path,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing GetJobOutput request: {{err}}", err)
|
||||
}
|
||||
|
||||
output := &GetJobOutputOutput{
|
||||
Items: respBody,
|
||||
}
|
||||
|
||||
resultSetSize, err := strconv.ParseUint(respHeader.Get("Result-Set-Size"), 10, 64)
|
||||
if err == nil {
|
||||
output.ResultSetSize = resultSetSize
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// GetJobInputInput represents parameters to a GetJobOutput operation.
|
||||
type GetJobInputInput struct {
|
||||
JobID string
|
||||
}
|
||||
|
||||
// GetJobInputOutput contains the outputs for a GetJobOutput operation. It is your
|
||||
// responsibility to ensure that the io.ReadCloser Items is closed.
|
||||
type GetJobInputOutput struct {
|
||||
ResultSetSize uint64
|
||||
Items io.ReadCloser
|
||||
}
|
||||
|
||||
// GetJobInput returns the current "live" set of inputs from a job. Think of
|
||||
// this like `tail -f`. If error is nil (i.e. the operation is successful), it is
|
||||
// your responsibility to close the io.ReadCloser named Items in the output.
|
||||
func (s *JobClient) GetInput(ctx context.Context, input *GetJobInputInput) (*GetJobInputOutput, error) {
|
||||
path := fmt.Sprintf("/%s/jobs/%s/live/in", s.client.AccountName, input.JobID)
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: path,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing GetJobInput request: {{err}}", err)
|
||||
}
|
||||
|
||||
output := &GetJobInputOutput{
|
||||
Items: respBody,
|
||||
}
|
||||
|
||||
resultSetSize, err := strconv.ParseUint(respHeader.Get("Result-Set-Size"), 10, 64)
|
||||
if err == nil {
|
||||
output.ResultSetSize = resultSetSize
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// GetJobFailuresInput represents parameters to a GetJobFailures operation.
|
||||
type GetJobFailuresInput struct {
|
||||
JobID string
|
||||
}
|
||||
|
||||
// GetJobFailuresOutput contains the outputs for a GetJobFailures operation. It is your
|
||||
// responsibility to ensure that the io.ReadCloser Items is closed.
|
||||
type GetJobFailuresOutput struct {
|
||||
ResultSetSize uint64
|
||||
Items io.ReadCloser
|
||||
}
|
||||
|
||||
// GetJobFailures returns the current "live" set of outputs from a job. Think of
|
||||
// this like `tail -f`. If error is nil (i.e. the operation is successful), it is
|
||||
// your responsibility to close the io.ReadCloser named Items in the output.
|
||||
func (s *JobClient) GetFailures(ctx context.Context, input *GetJobFailuresInput) (*GetJobFailuresOutput, error) {
|
||||
path := fmt.Sprintf("/%s/jobs/%s/live/fail", s.client.AccountName, input.JobID)
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: path,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing GetJobFailures request: {{err}}", err)
|
||||
}
|
||||
|
||||
output := &GetJobFailuresOutput{
|
||||
Items: respBody,
|
||||
}
|
||||
|
||||
resultSetSize, err := strconv.ParseUint(respHeader.Get("Result-Set-Size"), 10, 64)
|
||||
if err == nil {
|
||||
output.ResultSetSize = resultSetSize
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
388
vendor/github.com/joyent/triton-go/storage/objects.go
generated
vendored
Normal file
388
vendor/github.com/joyent/triton-go/storage/objects.go
generated
vendored
Normal file
@@ -0,0 +1,388 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/joyent/triton-go/client"
|
||||
)
|
||||
|
||||
type ObjectsClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// GetObjectInput represents parameters to a GetObject operation.
|
||||
type GetInfoInput struct {
|
||||
ObjectPath string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// GetObjectOutput contains the outputs for a GetObject operation. It is your
|
||||
// responsibility to ensure that the io.ReadCloser ObjectReader is closed.
|
||||
type GetInfoOutput struct {
|
||||
ContentLength uint64
|
||||
ContentType string
|
||||
LastModified time.Time
|
||||
ContentMD5 string
|
||||
ETag string
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// GetInfo sends a HEAD request to an object in the Manta service. This function
|
||||
// does not return a response body.
|
||||
func (s *ObjectsClient) GetInfo(ctx context.Context, input *GetInfoInput) (*GetInfoOutput, error) {
|
||||
absPath := absFileInput(s.client.AccountName, input.ObjectPath)
|
||||
|
||||
headers := &http.Header{}
|
||||
for key, value := range input.Headers {
|
||||
headers.Set(key, value)
|
||||
}
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodHead,
|
||||
Path: string(absPath),
|
||||
Headers: headers,
|
||||
}
|
||||
_, respHeaders, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing get info request: {{err}}", err)
|
||||
}
|
||||
|
||||
response := &GetInfoOutput{
|
||||
ContentType: respHeaders.Get("Content-Type"),
|
||||
ContentMD5: respHeaders.Get("Content-MD5"),
|
||||
ETag: respHeaders.Get("Etag"),
|
||||
}
|
||||
|
||||
lastModified, err := time.Parse(time.RFC1123, respHeaders.Get("Last-Modified"))
|
||||
if err == nil {
|
||||
response.LastModified = lastModified
|
||||
}
|
||||
|
||||
contentLength, err := strconv.ParseUint(respHeaders.Get("Content-Length"), 10, 64)
|
||||
if err == nil {
|
||||
response.ContentLength = contentLength
|
||||
}
|
||||
|
||||
metadata := map[string]string{}
|
||||
for key, values := range respHeaders {
|
||||
if strings.HasPrefix(key, "m-") {
|
||||
metadata[key] = strings.Join(values, ", ")
|
||||
}
|
||||
}
|
||||
response.Metadata = metadata
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// IsDir is a convenience wrapper around the GetInfo function which takes an
|
||||
// ObjectPath and returns a boolean whether or not the object is a directory
|
||||
// type in Manta. Returns an error if GetInfo failed upstream for some reason.
|
||||
func (s *ObjectsClient) IsDir(ctx context.Context, objectPath string) (bool, error) {
|
||||
info, err := s.GetInfo(ctx, &GetInfoInput{
|
||||
ObjectPath: objectPath,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if info != nil {
|
||||
return strings.HasSuffix(info.ContentType, "type=directory"), nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetObjectInput represents parameters to a GetObject operation.
|
||||
type GetObjectInput struct {
|
||||
ObjectPath string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// GetObjectOutput contains the outputs for a GetObject operation. It is your
|
||||
// responsibility to ensure that the io.ReadCloser ObjectReader is closed.
|
||||
type GetObjectOutput struct {
|
||||
ContentLength uint64
|
||||
ContentType string
|
||||
LastModified time.Time
|
||||
ContentMD5 string
|
||||
ETag string
|
||||
Metadata map[string]string
|
||||
ObjectReader io.ReadCloser
|
||||
}
|
||||
|
||||
// Get retrieves an object from the Manta service. If error is nil (i.e. the
|
||||
// call returns successfully), it is your responsibility to close the
|
||||
// io.ReadCloser named ObjectReader in the operation output.
|
||||
func (s *ObjectsClient) Get(ctx context.Context, input *GetObjectInput) (*GetObjectOutput, error) {
|
||||
absPath := absFileInput(s.client.AccountName, input.ObjectPath)
|
||||
|
||||
headers := &http.Header{}
|
||||
for key, value := range input.Headers {
|
||||
headers.Set(key, value)
|
||||
}
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: string(absPath),
|
||||
Headers: headers,
|
||||
}
|
||||
respBody, respHeaders, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error executing Get request: {{err}}", err)
|
||||
}
|
||||
|
||||
response := &GetObjectOutput{
|
||||
ContentType: respHeaders.Get("Content-Type"),
|
||||
ContentMD5: respHeaders.Get("Content-MD5"),
|
||||
ETag: respHeaders.Get("Etag"),
|
||||
ObjectReader: respBody,
|
||||
}
|
||||
|
||||
lastModified, err := time.Parse(time.RFC1123, respHeaders.Get("Last-Modified"))
|
||||
if err == nil {
|
||||
response.LastModified = lastModified
|
||||
}
|
||||
|
||||
contentLength, err := strconv.ParseUint(respHeaders.Get("Content-Length"), 10, 64)
|
||||
if err == nil {
|
||||
response.ContentLength = contentLength
|
||||
}
|
||||
|
||||
metadata := map[string]string{}
|
||||
for key, values := range respHeaders {
|
||||
if strings.HasPrefix(key, "m-") {
|
||||
metadata[key] = strings.Join(values, ", ")
|
||||
}
|
||||
}
|
||||
response.Metadata = metadata
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// DeleteObjectInput represents parameters to a DeleteObject operation.
|
||||
type DeleteObjectInput struct {
|
||||
ObjectPath string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// DeleteObject deletes an object.
|
||||
func (s *ObjectsClient) Delete(ctx context.Context, input *DeleteObjectInput) error {
|
||||
absPath := absFileInput(s.client.AccountName, input.ObjectPath)
|
||||
|
||||
headers := &http.Header{}
|
||||
for key, value := range input.Headers {
|
||||
headers.Set(key, value)
|
||||
}
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodDelete,
|
||||
Path: string(absPath),
|
||||
Headers: headers,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing Delete request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutObjectMetadataInput represents parameters to a PutObjectMetadata operation.
|
||||
type PutObjectMetadataInput struct {
|
||||
ObjectPath string
|
||||
ContentType string
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// PutObjectMetadata allows you to overwrite the HTTP headers for an already
|
||||
// existing object, without changing the data. Note this is an idempotent "replace"
|
||||
// operation, so you must specify the complete set of HTTP headers you want
|
||||
// stored on each request.
|
||||
//
|
||||
// You cannot change "critical" headers:
|
||||
// - Content-Length
|
||||
// - Content-MD5
|
||||
// - Durability-Level
|
||||
func (s *ObjectsClient) PutMetadata(ctx context.Context, input *PutObjectMetadataInput) error {
|
||||
absPath := absFileInput(s.client.AccountName, input.ObjectPath)
|
||||
query := &url.Values{}
|
||||
query.Set("metadata", "true")
|
||||
|
||||
headers := &http.Header{}
|
||||
headers.Set("Content-Type", input.ContentType)
|
||||
for key, value := range input.Metadata {
|
||||
headers.Set(key, value)
|
||||
}
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodPut,
|
||||
Path: string(absPath),
|
||||
Query: query,
|
||||
Headers: headers,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing PutMetadata request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutObjectInput represents parameters to a PutObject operation.
|
||||
type PutObjectInput struct {
|
||||
ObjectPath string
|
||||
DurabilityLevel uint64
|
||||
ContentType string
|
||||
ContentMD5 string
|
||||
IfMatch string
|
||||
IfModifiedSince *time.Time
|
||||
ContentLength uint64
|
||||
MaxContentLength uint64
|
||||
ObjectReader io.Reader
|
||||
Headers map[string]string
|
||||
ForceInsert bool //Force the creation of the directory tree
|
||||
}
|
||||
|
||||
func (s *ObjectsClient) Put(ctx context.Context, input *PutObjectInput) error {
|
||||
absPath := absFileInput(s.client.AccountName, input.ObjectPath)
|
||||
|
||||
if input.ForceInsert {
|
||||
// IsDir() uses a path relative to the account
|
||||
absDirName := _AbsCleanPath(path.Dir(string(absPath)))
|
||||
exists, err := checkDirectoryTreeExists(*s, ctx, absDirName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
err := createDirectory(*s, ctx, absDirName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return putObject(*s, ctx, input, absPath)
|
||||
}
|
||||
}
|
||||
|
||||
return putObject(*s, ctx, input, absPath)
|
||||
}
|
||||
|
||||
// _AbsCleanPath is an internal type that means the input has been
|
||||
// path.Clean()'ed and is an absolute path.
|
||||
type _AbsCleanPath string
|
||||
|
||||
func absFileInput(accountName, objPath string) _AbsCleanPath {
|
||||
cleanInput := path.Clean(objPath)
|
||||
if strings.HasPrefix(cleanInput, path.Join("/", accountName, "/")) {
|
||||
return _AbsCleanPath(cleanInput)
|
||||
}
|
||||
|
||||
cleanAbs := path.Clean(path.Join("/", accountName, objPath))
|
||||
return _AbsCleanPath(cleanAbs)
|
||||
}
|
||||
|
||||
func putObject(c ObjectsClient, ctx context.Context, input *PutObjectInput, absPath _AbsCleanPath) error {
|
||||
if input.MaxContentLength != 0 && input.ContentLength != 0 {
|
||||
return errors.New("ContentLength and MaxContentLength may not both be set to non-zero values.")
|
||||
}
|
||||
|
||||
headers := &http.Header{}
|
||||
for key, value := range input.Headers {
|
||||
headers.Set(key, value)
|
||||
}
|
||||
if input.DurabilityLevel != 0 {
|
||||
headers.Set("Durability-Level", strconv.FormatUint(input.DurabilityLevel, 10))
|
||||
}
|
||||
if input.ContentType != "" {
|
||||
headers.Set("Content-Type", input.ContentType)
|
||||
}
|
||||
if input.ContentMD5 != "" {
|
||||
headers.Set("Content-MD$", input.ContentMD5)
|
||||
}
|
||||
if input.IfMatch != "" {
|
||||
headers.Set("If-Match", input.IfMatch)
|
||||
}
|
||||
if input.IfModifiedSince != nil {
|
||||
headers.Set("If-Modified-Since", input.IfModifiedSince.Format(time.RFC1123))
|
||||
}
|
||||
if input.ContentLength != 0 {
|
||||
headers.Set("Content-Length", strconv.FormatUint(input.ContentLength, 10))
|
||||
}
|
||||
if input.MaxContentLength != 0 {
|
||||
headers.Set("Max-Content-Length", strconv.FormatUint(input.MaxContentLength, 10))
|
||||
}
|
||||
|
||||
reqInput := client.RequestNoEncodeInput{
|
||||
Method: http.MethodPut,
|
||||
Path: string(absPath),
|
||||
Headers: headers,
|
||||
Body: input.ObjectReader,
|
||||
}
|
||||
respBody, _, err := c.client.ExecuteRequestNoEncode(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing Put request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDirectory(c ObjectsClient, ctx context.Context, absPath _AbsCleanPath) error {
|
||||
dirClient := &DirectoryClient{
|
||||
client: c.client,
|
||||
}
|
||||
|
||||
// An abspath starts w/ a leading "/" which gets added to the slice as an
|
||||
// empty string. Start all array math at 1.
|
||||
parts := strings.Split(string(absPath), "/")
|
||||
if len(parts) < 2 {
|
||||
return errors.New("no path components to create directory")
|
||||
}
|
||||
|
||||
folderPath := parts[1]
|
||||
// Don't attempt to create a manta account as a directory
|
||||
for i := 2; i < len(parts); i++ {
|
||||
part := parts[i]
|
||||
folderPath = path.Clean(path.Join("/", folderPath, part))
|
||||
err := dirClient.Put(ctx, &PutDirectoryInput{
|
||||
DirectoryName: folderPath,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDirectoryTreeExists(c ObjectsClient, ctx context.Context, absPath _AbsCleanPath) (bool, error) {
|
||||
exists, err := c.IsDir(ctx, string(absPath))
|
||||
if err != nil {
|
||||
errType := &client.MantaError{}
|
||||
if errwrap.ContainsType(err, errType) {
|
||||
mantaErr := errwrap.GetType(err, errType).(*client.MantaError)
|
||||
if mantaErr.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if exists {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
81
vendor/github.com/joyent/triton-go/storage/signing.go
generated
vendored
Normal file
81
vendor/github.com/joyent/triton-go/storage/signing.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
)
|
||||
|
||||
// SignURLInput represents parameters to a SignURL operation.
|
||||
type SignURLInput struct {
|
||||
ValidityPeriod time.Duration
|
||||
Method string
|
||||
ObjectPath string
|
||||
}
|
||||
|
||||
// SignURLOutput contains the outputs of a SignURL operation. To simply
|
||||
// access the signed URL, use the SignedURL method.
|
||||
type SignURLOutput struct {
|
||||
host string
|
||||
objectPath string
|
||||
Method string
|
||||
Algorithm string
|
||||
Signature string
|
||||
Expires string
|
||||
KeyID string
|
||||
}
|
||||
|
||||
// SignedURL returns a signed URL for the given scheme. Valid schemes are
|
||||
// `http` and `https`.
|
||||
func (output *SignURLOutput) SignedURL(scheme string) string {
|
||||
query := &url.Values{}
|
||||
query.Set("algorithm", output.Algorithm)
|
||||
query.Set("expires", output.Expires)
|
||||
query.Set("keyId", output.KeyID)
|
||||
query.Set("signature", output.Signature)
|
||||
|
||||
sUrl := url.URL{}
|
||||
sUrl.Scheme = scheme
|
||||
sUrl.Host = output.host
|
||||
sUrl.Path = output.objectPath
|
||||
sUrl.RawQuery = query.Encode()
|
||||
|
||||
return sUrl.String()
|
||||
}
|
||||
|
||||
// SignURL creates a time-expiring URL that can be shared with others.
|
||||
// This is useful to generate HTML links, for example.
|
||||
func (s *StorageClient) SignURL(input *SignURLInput) (*SignURLOutput, error) {
|
||||
output := &SignURLOutput{
|
||||
host: s.Client.MantaURL.Host,
|
||||
objectPath: fmt.Sprintf("/%s%s", s.Client.AccountName, input.ObjectPath),
|
||||
Method: input.Method,
|
||||
Algorithm: strings.ToUpper(s.Client.Authorizers[0].DefaultAlgorithm()),
|
||||
Expires: strconv.FormatInt(time.Now().Add(input.ValidityPeriod).Unix(), 10),
|
||||
KeyID: fmt.Sprintf("/%s/keys/%s", s.Client.AccountName, s.Client.Authorizers[0].KeyFingerprint()),
|
||||
}
|
||||
|
||||
toSign := bytes.Buffer{}
|
||||
toSign.WriteString(input.Method + "\n")
|
||||
toSign.WriteString(s.Client.MantaURL.Host + "\n")
|
||||
toSign.WriteString(fmt.Sprintf("/%s%s\n", s.Client.AccountName, input.ObjectPath))
|
||||
|
||||
query := &url.Values{}
|
||||
query.Set("algorithm", output.Algorithm)
|
||||
query.Set("expires", output.Expires)
|
||||
query.Set("keyId", output.KeyID)
|
||||
toSign.WriteString(query.Encode())
|
||||
|
||||
signature, _, err := s.Client.Authorizers[0].SignRaw(toSign.String())
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("Error signing string: {{err}}", err)
|
||||
}
|
||||
|
||||
output.Signature = signature
|
||||
return output, nil
|
||||
}
|
||||
46
vendor/github.com/joyent/triton-go/storage/snaplink.go
generated
vendored
Normal file
46
vendor/github.com/joyent/triton-go/storage/snaplink.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/joyent/triton-go/client"
|
||||
)
|
||||
|
||||
type SnapLinksClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// PutSnapLinkInput represents parameters to a PutSnapLink operation.
|
||||
type PutSnapLinkInput struct {
|
||||
LinkPath string
|
||||
SourcePath string
|
||||
}
|
||||
|
||||
// PutSnapLink creates a SnapLink to an object.
|
||||
func (s *SnapLinksClient) Put(ctx context.Context, input *PutSnapLinkInput) error {
|
||||
linkPath := fmt.Sprintf("/%s%s", s.client.AccountName, input.LinkPath)
|
||||
sourcePath := fmt.Sprintf("/%s%s", s.client.AccountName, input.SourcePath)
|
||||
headers := &http.Header{}
|
||||
headers.Set("Content-Type", "application/json; type=link")
|
||||
headers.Set("location", sourcePath)
|
||||
headers.Set("Accept", "~1.0")
|
||||
headers.Set("Accept-Version", "application/json, */*")
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodPut,
|
||||
Path: linkPath,
|
||||
Headers: headers,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Error executing PutSnapLink request: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
18
vendor/github.com/joyent/triton-go/triton.go
generated
vendored
Normal file
18
vendor/github.com/joyent/triton-go/triton.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"github.com/joyent/triton-go/authentication"
|
||||
)
|
||||
|
||||
// Universal package used for defining configuration used across all client
|
||||
// constructors.
|
||||
|
||||
// ClientConfig is a placeholder/input struct around the behavior of configuring
|
||||
// a client constructor through the implementation's runtime environment
|
||||
// (SDC/MANTA env vars).
|
||||
type ClientConfig struct {
|
||||
TritonURL string
|
||||
MantaURL string
|
||||
AccountName string
|
||||
Signers []authentication.Signer
|
||||
}
|
||||
Reference in New Issue
Block a user