mirror of
https://github.com/pgsty/minio.git
synced 2026-07-20 04:30:26 +03:00
Remove gateway implementations for manta, sia and b2 (#7115)
This commit is contained in:
committed by
kannappanr
parent
4fdacb8b14
commit
3265112d04
-64
@@ -1,64 +0,0 @@
|
||||
## Unreleased
|
||||
|
||||
- Add support for managing columes in Triton [#100]
|
||||
- identity/policies: Add support for managing policies in Triton [#86]
|
||||
- addition of triton-go errors package to expose unwraping of internal errors
|
||||
- Migration from hashicorp/errwrap to pkg/errors
|
||||
- Using path.Join() for URL structures rather than fmt.Sprintf()
|
||||
|
||||
## 0.5.2 (December 28)
|
||||
|
||||
- Standardise the API SSH Signers input casing and naming
|
||||
|
||||
## 0.5.1 (December 28)
|
||||
|
||||
- Include leading '/' when working with SSH Agent signers
|
||||
|
||||
## 0.5.0 (December 28)
|
||||
|
||||
- Add support for RBAC in triton-go [#82]
|
||||
This is a breaking change. No longer do we pass individual parameters to the SSH Signer funcs, but we now pass an input Struct. This will guard from from additional parameter changes in the future.
|
||||
We also now add support for using `SDC_*` and `TRITON_*` env vars when working with the Default agent signer
|
||||
|
||||
## 0.4.2 (December 22)
|
||||
|
||||
- Fixing a panic when the user loses network connectivity when making a GET request to instance [#81]
|
||||
|
||||
## 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
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
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 test with coverage"
|
||||
@./scripts/go-test-with-coverage.sh
|
||||
|
||||
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}'
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
# 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]]
|
||||
branch = "master"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "e881fd58d78e04cf6d0de1217f8707c8cc2249bc"
|
||||
|
||||
[[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 = "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "f7efd974ae38e2ee077c4d2698df74128a04797460b5f9c833853ddfaa86a0a0"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
|
||||
# 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]]
|
||||
branch = "master"
|
||||
name = "github.com/sean-/seed"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/pkg/errors"
|
||||
-373
@@ -1,373 +0,0 @@
|
||||
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.
|
||||
-238
@@ -1,238 +0,0 @@
|
||||
# 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 `TRITON_ACCOUNT` environment variable). There is also support for
|
||||
passing in a username, this will allow you to use an account other than the main
|
||||
Triton account. For example:
|
||||
|
||||
```go
|
||||
input := authentication.SSHAgentSignerInput{
|
||||
KeyID: "a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11",
|
||||
AccountName: "AccountName",
|
||||
Username: "Username",
|
||||
}
|
||||
sshKeySigner, err := authentication.NewSSHAgentSigner(input)
|
||||
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("TRITON_URL"),
|
||||
MantaURL: os.Getenv("MANTA_URL"),
|
||||
AccountName: accountName,
|
||||
Username: os.Getenv("TRITON_USER"),
|
||||
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
|
||||
- `TRITON_URL` - the base endpoint for the Triton API
|
||||
- `TRITON_ACCOUNT` - the account name for the Triton API
|
||||
- `TRITON_KEY_ID` - the fingerprint of the SSH key identifying the key
|
||||
|
||||
Additionally, you may set `TRITON_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. You can also set `TRITON_USER` to run the tests
|
||||
against an account other than the main Triton account
|
||||
|
||||
### Example Run
|
||||
|
||||
The verbose output has been removed for brevity here.
|
||||
|
||||
```
|
||||
$ HTTP_PROXY=http://localhost:8888 \
|
||||
TRITON_TEST=1 \
|
||||
TRITON_URL=https://us-sw-1.api.joyent.com \
|
||||
TRITON_ACCOUNT=AccountName \
|
||||
TRITON_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)"
|
||||
$ TRITON_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("TRITON_KEY_ID")
|
||||
accountName := os.Getenv("TRITON_ACCOUNT")
|
||||
keyMaterial := os.Getenv("TRITON_KEY_MATERIAL")
|
||||
userName := os.Getenv("TRITON_USER")
|
||||
|
||||
var signer authentication.Signer
|
||||
var err error
|
||||
|
||||
if keyMaterial == "" {
|
||||
input := authentication.SSHAgentSignerInput{
|
||||
KeyID: keyID,
|
||||
AccountName: accountName,
|
||||
Username: userName,
|
||||
}
|
||||
signer, err = authentication.NewSSHAgentSigner(input)
|
||||
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)
|
||||
}
|
||||
|
||||
input := authentication.PrivateKeySignerInput{
|
||||
KeyID: keyID,
|
||||
PrivateKeyMaterial: keyBytes,
|
||||
AccountName: accountName,
|
||||
Username: userName,
|
||||
}
|
||||
signer, err = authentication.NewPrivateKeySigner(input)
|
||||
if err != nil {
|
||||
log.Fatalf("Error Creating SSH Private Key Signer: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
config := &triton.ClientConfig{
|
||||
TritonURL: os.Getenv("TRITON_URL"),
|
||||
AccountName: accountName,
|
||||
Username: userName,
|
||||
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
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
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
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"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, errors.Wrap(err, "unable to unmarshall signature")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type PrivateKeySigner struct {
|
||||
formattedKeyFingerprint string
|
||||
keyFingerprint string
|
||||
algorithm string
|
||||
accountName string
|
||||
userName string
|
||||
hashFunc crypto.Hash
|
||||
|
||||
privateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
type PrivateKeySignerInput struct {
|
||||
KeyID string
|
||||
PrivateKeyMaterial []byte
|
||||
AccountName string
|
||||
Username string
|
||||
}
|
||||
|
||||
func NewPrivateKeySigner(input PrivateKeySignerInput) (*PrivateKeySigner, error) {
|
||||
keyFingerprintMD5 := strings.Replace(input.KeyID, ":", "", -1)
|
||||
|
||||
block, _ := pem.Decode(input.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, errors.Wrap(err, "unable to parse private key")
|
||||
}
|
||||
|
||||
sshPublicKey, err := ssh.NewPublicKey(rsakey.Public())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse SSH key from private key")
|
||||
}
|
||||
|
||||
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: input.KeyID,
|
||||
accountName: input.AccountName,
|
||||
|
||||
hashFunc: crypto.SHA1,
|
||||
privateKey: rsakey,
|
||||
}
|
||||
|
||||
if input.Username != "" {
|
||||
signer.userName = input.Username
|
||||
}
|
||||
|
||||
_, 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 "", errors.Wrap(err, "unable to sign date header")
|
||||
}
|
||||
signedBase64 := base64.StdEncoding.EncodeToString(signed)
|
||||
|
||||
var keyID string
|
||||
if s.userName != "" {
|
||||
|
||||
keyID = path.Join("/", s.accountName, "users", s.userName, "keys", s.formattedKeyFingerprint)
|
||||
} else {
|
||||
keyID = path.Join("/", s.accountName, "keys", 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 "", "", errors.Wrap(err, "unable to sign date header")
|
||||
}
|
||||
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
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
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
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
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)
|
||||
}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
pkgerrors "github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsetEnvVar = pkgerrors.New("environment variable SSH_AUTH_SOCK not set")
|
||||
)
|
||||
|
||||
type SSHAgentSigner struct {
|
||||
formattedKeyFingerprint string
|
||||
keyFingerprint string
|
||||
algorithm string
|
||||
accountName string
|
||||
userName string
|
||||
keyIdentifier string
|
||||
|
||||
agent agent.Agent
|
||||
key ssh.PublicKey
|
||||
}
|
||||
|
||||
type SSHAgentSignerInput struct {
|
||||
KeyID string
|
||||
AccountName string
|
||||
Username string
|
||||
}
|
||||
|
||||
func NewSSHAgentSigner(input SSHAgentSignerInput) (*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, pkgerrors.Wrap(err, "unable to dial SSH agent")
|
||||
}
|
||||
|
||||
ag := agent.NewClient(conn)
|
||||
|
||||
signer := &SSHAgentSigner{
|
||||
keyFingerprint: input.KeyID,
|
||||
accountName: input.AccountName,
|
||||
agent: ag,
|
||||
}
|
||||
|
||||
matchingKey, err := signer.MatchKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signer.key = matchingKey
|
||||
signer.formattedKeyFingerprint = formatPublicKeyFingerprint(signer.key, true)
|
||||
if input.Username != "" {
|
||||
signer.userName = input.Username
|
||||
signer.keyIdentifier = path.Join("/", signer.accountName, "users", input.Username, "keys", signer.formattedKeyFingerprint)
|
||||
} else {
|
||||
signer.keyIdentifier = path.Join("/", signer.accountName, "keys", 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, pkgerrors.Wrap(err, "unable to list keys in SSH Agent")
|
||||
}
|
||||
|
||||
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 "", pkgerrors.Wrap(err, "unable to sign date header")
|
||||
}
|
||||
|
||||
keyFormat, err := keyFormatToKeyType(signature.Format)
|
||||
if err != nil {
|
||||
return "", pkgerrors.Wrap(err, "unable to format signature")
|
||||
}
|
||||
|
||||
var authSignature httpAuthSignature
|
||||
switch keyFormat {
|
||||
case "rsa":
|
||||
authSignature, err = newRSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", pkgerrors.Wrap(err, "unable to read RSA signature")
|
||||
}
|
||||
case "ecdsa":
|
||||
authSignature, err = newECDSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", pkgerrors.Wrap(err, "unable to read ECDSA signature")
|
||||
}
|
||||
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 "", "", pkgerrors.Wrap(err, "unable to sign string")
|
||||
}
|
||||
|
||||
keyFormat, err := keyFormatToKeyType(signature.Format)
|
||||
if err != nil {
|
||||
return "", "", pkgerrors.Wrap(err, "unable to format key")
|
||||
}
|
||||
|
||||
var authSignature httpAuthSignature
|
||||
switch keyFormat {
|
||||
case "rsa":
|
||||
authSignature, err = newRSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", "", pkgerrors.Wrap(err, "unable to read RSA signature")
|
||||
}
|
||||
case "ecdsa":
|
||||
authSignature, err = newECDSASignature(signature.Blob)
|
||||
if err != nil {
|
||||
return "", "", pkgerrors.Wrap(err, "unable to read ECDSA signature")
|
||||
}
|
||||
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
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
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
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
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, ":")
|
||||
}
|
||||
-444
@@ -1,444 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/joyent/triton-go"
|
||||
"github.com/joyent/triton-go/authentication"
|
||||
"github.com/joyent/triton-go/errors"
|
||||
pkgerrors "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const nilContext = "nil context"
|
||||
|
||||
var (
|
||||
ErrDefaultAuth = pkgerrors.New("default SSH agent authentication requires SDC_KEY_ID / TRITON_KEY_ID and SSH_AUTH_SOCK")
|
||||
ErrAccountName = pkgerrors.New("missing account name")
|
||||
ErrMissingURL = pkgerrors.New("missing API URL")
|
||||
|
||||
InvalidTritonURL = "invalid format of Triton URL"
|
||||
InvalidMantaURL = "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
|
||||
Username 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, pkgerrors.Wrapf(err, InvalidTritonURL)
|
||||
}
|
||||
|
||||
storageURL, err := url.Parse(mantaURL)
|
||||
if err != nil {
|
||||
return nil, pkgerrors.Wrapf(err, InvalidMantaURL)
|
||||
}
|
||||
|
||||
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 TRITON_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
|
||||
}
|
||||
|
||||
var envPrefixes = []string{"TRITON", "SDC"}
|
||||
|
||||
// GetTritonEnv looks up environment variables using the preferred "TRITON"
|
||||
// prefix, but falls back to the SDC prefix. For example, looking up "USER"
|
||||
// will search for "TRITON_USER" followed by "SDC_USER". If the environment
|
||||
// variable is not set, an empty string is returned. GetTritonEnv() is used to
|
||||
// aid in the transition and deprecation of the SDC_* environment variables.
|
||||
func GetTritonEnv(name string) string {
|
||||
for _, prefix := range envPrefixes {
|
||||
if val, found := os.LookupEnv(prefix + "_" + name); found {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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 {
|
||||
tritonKeyId := GetTritonEnv("KEY_ID")
|
||||
if tritonKeyId != "" {
|
||||
input := authentication.SSHAgentSignerInput{
|
||||
KeyID: tritonKeyId,
|
||||
AccountName: c.AccountName,
|
||||
Username: c.Username,
|
||||
}
|
||||
defaultSigner, err := authentication.NewSSHAgentSigner(input)
|
||||
if err != nil {
|
||||
return pkgerrors.Wrapf(err, "unable to initialize NewSSHAgentSigner")
|
||||
}
|
||||
c.Authorizers = append(c.Authorizers, defaultSigner)
|
||||
}
|
||||
|
||||
return ErrDefaultAuth
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (c *Client) DecodeError(resp *http.Response, requestMethod string) error {
|
||||
err := &errors.APIError{
|
||||
StatusCode: resp.StatusCode,
|
||||
}
|
||||
|
||||
if requestMethod != http.MethodHead && resp.Body != nil {
|
||||
errorDecoder := json.NewDecoder(resp.Body)
|
||||
if err := errorDecoder.Decode(err); err != nil {
|
||||
return pkgerrors.Wrapf(err, "unable to decode error response")
|
||||
}
|
||||
}
|
||||
|
||||
if err.Message == "" {
|
||||
err.Message = fmt.Sprintf("HTTP response returned status code %d", err.StatusCode)
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Accept-Version", triton.CloudAPIMajorVersion)
|
||||
req.Header.Set("User-Agent", triton.UserAgent())
|
||||
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
||||
}
|
||||
|
||||
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
||||
// StatusMultipleChoices is StatusCode 300
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
return nil, c.DecodeError(resp, req.Method)
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Accept-Version", triton.CloudAPIMajorVersion)
|
||||
req.Header.Set("User-Agent", triton.UserAgent())
|
||||
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
||||
}
|
||||
|
||||
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
||||
// StatusMultipleChoices is StatusCode 300
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return nil, c.DecodeError(resp, req.Method)
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("User-Agent", triton.UserAgent())
|
||||
|
||||
if query != nil {
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
||||
}
|
||||
|
||||
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
||||
// StatusMultipleChoices is StatusCode 300
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return resp.Body, resp.Header, nil
|
||||
}
|
||||
|
||||
return nil, nil, c.DecodeError(resp, req.Method)
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to construct HTTP request")
|
||||
}
|
||||
|
||||
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, pkgerrors.Wrapf(err, "unable to sign HTTP request")
|
||||
}
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("Accept-Version", triton.CloudAPIMajorVersion)
|
||||
req.Header.Set("User-Agent", triton.UserAgent())
|
||||
|
||||
if query != nil {
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, nil, pkgerrors.Wrapf(err, "unable to execute HTTP request")
|
||||
}
|
||||
|
||||
// We will only return a response from the API it is in the HTTP StatusCode 2xx range
|
||||
// StatusMultipleChoices is StatusCode 300
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return resp.Body, resp.Header, nil
|
||||
}
|
||||
|
||||
return nil, nil, c.DecodeError(resp, req.Method)
|
||||
}
|
||||
-297
@@ -1,297 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// APIError 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
|
||||
// Error codes used by the Manta API are listed at
|
||||
// https://apidocs.joyent.com/manta/api.html#errors
|
||||
type APIError struct {
|
||||
StatusCode int
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Error implements interface Error on the APIError type.
|
||||
func (e APIError) Error() string {
|
||||
return strings.Trim(fmt.Sprintf("%+q", e.Code), `"`) + ": " + strings.Trim(fmt.Sprintf("%+q", e.Message), `"`)
|
||||
}
|
||||
|
||||
// ClientError represents an error code and message returned
|
||||
// when connecting to the triton-go client
|
||||
type ClientError struct {
|
||||
StatusCode int
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Error implements interface Error on the ClientError type.
|
||||
func (e ClientError) Error() string {
|
||||
return strings.Trim(fmt.Sprintf("%+q", e.Code), `"`) + ": " + strings.Trim(fmt.Sprintf("%+q", 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")
|
||||
}
|
||||
|
||||
func IsBadRequest(err error) bool {
|
||||
return IsSpecificError(err, "BadRequest")
|
||||
}
|
||||
|
||||
func IsInUseError(err error) bool {
|
||||
return IsSpecificError(err, "InUseError")
|
||||
}
|
||||
|
||||
func IsInvalidArgument(err error) bool {
|
||||
return IsSpecificError(err, "InvalidArgument")
|
||||
}
|
||||
|
||||
func IsInvalidCredentials(err error) bool {
|
||||
return IsSpecificError(err, "InvalidCredentials")
|
||||
}
|
||||
|
||||
func IsInvalidHeader(err error) bool {
|
||||
return IsSpecificError(err, "InvalidHeader")
|
||||
}
|
||||
|
||||
func IsInvalidVersion(err error) bool {
|
||||
return IsSpecificError(err, "InvalidVersion")
|
||||
}
|
||||
|
||||
func IsMissingParameter(err error) bool {
|
||||
return IsSpecificError(err, "MissingParameter")
|
||||
}
|
||||
|
||||
func IsNotAuthorized(err error) bool {
|
||||
return IsSpecificError(err, "NotAuthorized")
|
||||
}
|
||||
|
||||
func IsRequestThrottled(err error) bool {
|
||||
return IsSpecificError(err, "RequestThrottled")
|
||||
}
|
||||
|
||||
func IsRequestTooLarge(err error) bool {
|
||||
return IsSpecificError(err, "RequestTooLarge")
|
||||
}
|
||||
|
||||
func IsRequestMoved(err error) bool {
|
||||
return IsSpecificError(err, "RequestMoved")
|
||||
}
|
||||
|
||||
func IsResourceFound(err error) bool {
|
||||
return IsSpecificError(err, "ResourceFound")
|
||||
}
|
||||
|
||||
func IsResourceNotFound(err error) bool {
|
||||
return IsSpecificError(err, "ResourceNotFound")
|
||||
}
|
||||
|
||||
func IsUnknownError(err error) bool {
|
||||
return IsSpecificError(err, "UnknownError")
|
||||
}
|
||||
|
||||
func IsEmptyResponse(err error) bool {
|
||||
return IsSpecificError(err, "EmptyResponse")
|
||||
}
|
||||
|
||||
func IsStatusNotFoundCode(err error) bool {
|
||||
return IsSpecificStatusCode(err, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func IsSpecificError(myError error, errorCode string) bool {
|
||||
switch err := errors.Cause(myError).(type) {
|
||||
case *APIError:
|
||||
if err.Code == errorCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsSpecificStatusCode(myError error, statusCode int) bool {
|
||||
switch err := errors.Cause(myError).(type) {
|
||||
case *APIError:
|
||||
if err.StatusCode == statusCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
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}
|
||||
}
|
||||
-207
@@ -1,207 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/joyent/triton-go/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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, errors.Wrap(err, "unable to list directory")
|
||||
}
|
||||
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, errors.Wrap(err, "unable to decode list directories response")
|
||||
}
|
||||
|
||||
results = append(results, current)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "unable to decode list directories response")
|
||||
}
|
||||
|
||||
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 errors.Wrap(err, "unable to put directory")
|
||||
}
|
||||
|
||||
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 errors.Wrap(err, "unable to delete directory")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
-448
@@ -1,448 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joyent/triton-go/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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) {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs")
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodPost,
|
||||
Path: fullPath,
|
||||
Body: input,
|
||||
}
|
||||
respBody, respHeaders, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to create job")
|
||||
}
|
||||
|
||||
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 {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs", input.JobID, "live", "in")
|
||||
headers := &http.Header{}
|
||||
headers.Set("Content-Type", "text/plain")
|
||||
|
||||
reader := strings.NewReader(strings.Join(input.ObjectPaths, "\n"))
|
||||
|
||||
reqInput := client.RequestNoEncodeInput{
|
||||
Method: http.MethodPost,
|
||||
Path: fullPath,
|
||||
Headers: headers,
|
||||
Body: reader,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestNoEncode(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to add job inputs")
|
||||
}
|
||||
|
||||
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 {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs", input.JobID, "live", "in", "end")
|
||||
|
||||
reqInput := client.RequestNoEncodeInput{
|
||||
Method: http.MethodPost,
|
||||
Path: fullPath,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestNoEncode(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to end job inputs")
|
||||
}
|
||||
|
||||
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 {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs", input.JobID, "live", "cancel")
|
||||
|
||||
reqInput := client.RequestNoEncodeInput{
|
||||
Method: http.MethodPost,
|
||||
Path: fullPath,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestNoEncode(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to cancel job")
|
||||
}
|
||||
|
||||
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) {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs")
|
||||
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: fullPath,
|
||||
Query: query,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to list jobs")
|
||||
}
|
||||
|
||||
var results []*JobSummary
|
||||
for {
|
||||
current := &JobSummary{}
|
||||
decoder := json.NewDecoder(respBody)
|
||||
if err = decoder.Decode(¤t); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, errors.Wrap(err, "unable to decode list jobs response")
|
||||
}
|
||||
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) {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs", input.JobID, "live", "status")
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: fullPath,
|
||||
}
|
||||
respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get job")
|
||||
}
|
||||
|
||||
job := &Job{}
|
||||
decoder := json.NewDecoder(respBody)
|
||||
if err = decoder.Decode(&job); err != nil {
|
||||
return nil, errors.Wrap(err, "unable to decode get job response")
|
||||
}
|
||||
|
||||
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) {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs", input.JobID, "live", "out")
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: fullPath,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get job output")
|
||||
}
|
||||
|
||||
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) {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs", input.JobID, "live", "in")
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: fullPath,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get job input")
|
||||
}
|
||||
|
||||
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) {
|
||||
fullPath := path.Join("/", s.client.AccountName, "jobs", input.JobID, "live", "fail")
|
||||
|
||||
reqInput := client.RequestInput{
|
||||
Method: http.MethodGet,
|
||||
Path: fullPath,
|
||||
}
|
||||
respBody, respHeader, err := s.client.ExecuteRequestStorage(ctx, reqInput)
|
||||
if respBody != nil {
|
||||
defer respBody.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get job failures")
|
||||
}
|
||||
|
||||
output := &GetJobFailuresOutput{
|
||||
Items: respBody,
|
||||
}
|
||||
|
||||
resultSetSize, err := strconv.ParseUint(respHeader.Get("Result-Set-Size"), 10, 64)
|
||||
if err == nil {
|
||||
output.ResultSetSize = resultSetSize
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
-391
@@ -1,391 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joyent/triton-go/client"
|
||||
tt "github.com/joyent/triton-go/errors"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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, errors.Wrap(err, "unable to get info")
|
||||
}
|
||||
|
||||
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, errors.Wrap(err, "unable to get object")
|
||||
}
|
||||
|
||||
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 errors.Wrap(err, "unable to delete object")
|
||||
}
|
||||
|
||||
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 errors.Wrap(err, "unable to put metadata")
|
||||
}
|
||||
|
||||
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 {
|
||||
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 errors.Wrap(err, "unable to put object")
|
||||
}
|
||||
|
||||
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 {
|
||||
if tt.IsResourceNotFoundError(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if exists {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 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: path.Join("/", s.Client.AccountName, "keys", 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, errors.Wrapf(err, "error signing string")
|
||||
}
|
||||
|
||||
output.Signature = signature
|
||||
return output, nil
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/joyent/triton-go/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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 errors.Wrapf(err, "unable to put snaplink")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
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
|
||||
Username string
|
||||
Signers []authentication.Signer
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018, Joyent, Inc. All rights reserved.
|
||||
//
|
||||
// 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/.
|
||||
//
|
||||
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// The main version number of the current released Triton-go SDK.
|
||||
const Version = "0.9.0"
|
||||
|
||||
// A pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
// such as "dev" (in development), "beta", "rc1", etc.
|
||||
var Prerelease = ""
|
||||
|
||||
func UserAgent() string {
|
||||
if Prerelease != "" {
|
||||
return fmt.Sprintf("triton-go/%s-%s (%s-%s; %s)", Version, Prerelease, runtime.GOARCH, runtime.GOOS, runtime.Version())
|
||||
} else {
|
||||
return fmt.Sprintf("triton-go/%s (%s-%s; %s)", Version, runtime.GOARCH, runtime.GOOS, runtime.Version())
|
||||
}
|
||||
}
|
||||
|
||||
const CloudAPIMajorVersion = "8"
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Klaus Post
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
# readahead
|
||||
Asynchronous read-ahead for Go readers
|
||||
|
||||
This package will allow you to add readhead to any reader. This means a separate goroutine will perform reads from your upstream reader, so you can request from this reader without delay.
|
||||
|
||||
This is helpful for splitting an input stream into concurrent processing, and also helps smooth out **bursts** of input or output.
|
||||
|
||||
This should be fully transparent, except that once an error has been returned from the Reader, it will not recover. A panic will be caught and returned as an error.
|
||||
|
||||
The readahead object also fulfills the [`io.WriterTo`](https://golang.org/pkg/io/#WriterTo) interface, which is likely to speed up `io.Copy` and other code that use the interface.
|
||||
|
||||
See an introduction: [An Async Read-ahead Package for Go](https://blog.klauspost.com/an-async-read-ahead-package-for-go/)
|
||||
|
||||
[![GoDoc][1]][2] [![Build Status][3]][4]
|
||||
|
||||
[1]: https://godoc.org/github.com/klauspost/readahead?status.svg
|
||||
[2]: https://godoc.org/github.com/klauspost/readahead
|
||||
[3]: https://travis-ci.org/klauspost/readahead.svg
|
||||
[4]: https://travis-ci.org/klauspost/readahead
|
||||
|
||||
# usage
|
||||
|
||||
To get the package use `go get -u github.com/klauspost/readahead`.
|
||||
|
||||
Here is a simple example that does file copy. Error handling has been omitted for brevity.
|
||||
```Go
|
||||
input, _ := os.Open("input.txt")
|
||||
output, _ := os.Create("output.txt")
|
||||
defer input.Close()
|
||||
defer output.Close()
|
||||
|
||||
// Create a read-ahead Reader with default settings
|
||||
ra := readahead.NewReader(input)
|
||||
defer ra.Close()
|
||||
|
||||
// Copy the content to our output
|
||||
_, _ = io.Copy(output, ra)
|
||||
```
|
||||
|
||||
# settings
|
||||
|
||||
You can finetune the read-ahead for your specific use case, and adjust the number of buffers and the size of each buffer.
|
||||
|
||||
The default the size of each buffer is 1MB, and there are 4 buffers. Do not make your buffers too small since there is a small overhead for passing buffers between goroutines. Other than that you are free to experiment with buffer sizes.
|
||||
|
||||
# contributions
|
||||
|
||||
On this project contributions in terms of new features is limited to:
|
||||
|
||||
* Features that are widely usable and
|
||||
* Features that have extensive tests
|
||||
|
||||
This package is meant to be simple and stable, so therefore these strict requirements.
|
||||
|
||||
The only feature I have considered is supporting the `io.Seeker` interface. I currently do not plan to add it myself, but if you can show a clean and well-tested way to implementing it, I will consider to merge it. If not, I will be happy to link to it.
|
||||
|
||||
# license
|
||||
|
||||
This package is released under the MIT license. See the supplied LICENSE file for more info.
|
||||
-282
@@ -1,282 +0,0 @@
|
||||
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||
|
||||
// The readahead package will do asynchronous read-ahead from an input io.Reader
|
||||
// and make the data available as an io.Reader.
|
||||
//
|
||||
// This should be fully transparent, except that once an error
|
||||
// has been returned from the Reader, it will not recover.
|
||||
//
|
||||
// The readahead object also fulfills the io.WriterTo interface, which
|
||||
// is likely to speed up copies.
|
||||
//
|
||||
// Package home: https://github.com/klauspost/readahead
|
||||
//
|
||||
package readahead
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type reader struct {
|
||||
in io.Reader // Input reader
|
||||
closer io.Closer // Optional closer
|
||||
ready chan *buffer // Buffers ready to be handed to the reader
|
||||
reuse chan *buffer // Buffers to reuse for input reading
|
||||
exit chan struct{} // Closes when finished
|
||||
buffers int // Number of buffers
|
||||
err error // If an error has occurred it is here
|
||||
cur *buffer // Current buffer being served
|
||||
exited chan struct{} // Channel is closed been the async reader shuts down
|
||||
}
|
||||
|
||||
// New returns a reader that will asynchronously read from
|
||||
// the supplied reader into 4 buffers of 1MB each.
|
||||
//
|
||||
// It will start reading from the input at once, maybe even before this
|
||||
// function has returned.
|
||||
//
|
||||
// The input can be read from the returned reader.
|
||||
// When done use Close() to release the buffers.
|
||||
func NewReader(rd io.Reader) io.ReadCloser {
|
||||
if rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret, err := NewReaderSize(rd, 4, 1<<20)
|
||||
|
||||
// Should not be possible to trigger from other packages.
|
||||
if err != nil {
|
||||
panic("unexpected error:" + err.Error())
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// New returns a reader that will asynchronously read from
|
||||
// the supplied reader into 4 buffers of 1MB each.
|
||||
//
|
||||
// It will start reading from the input at once, maybe even before this
|
||||
// function has returned.
|
||||
//
|
||||
// The input can be read from the returned reader.
|
||||
// When done use Close() to release the buffers,
|
||||
// which will also close the supplied closer.
|
||||
func NewReadCloser(rd io.ReadCloser) io.ReadCloser {
|
||||
if rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret, err := NewReadCloserSize(rd, 4, 1<<20)
|
||||
|
||||
// Should not be possible to trigger from other packages.
|
||||
if err != nil {
|
||||
panic("unexpected error:" + err.Error())
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewReaderSize returns a reader with a custom number of buffers and size.
|
||||
// buffers is the number of queued buffers and size is the size of each
|
||||
// buffer in bytes.
|
||||
func NewReaderSize(rd io.Reader, buffers, size int) (io.ReadCloser, error) {
|
||||
if size <= 0 {
|
||||
return nil, fmt.Errorf("buffer size too small")
|
||||
}
|
||||
if buffers <= 0 {
|
||||
return nil, fmt.Errorf("number of buffers too small")
|
||||
}
|
||||
if rd == nil {
|
||||
return nil, fmt.Errorf("nil input reader supplied")
|
||||
}
|
||||
a := &reader{}
|
||||
a.init(rd, buffers, size)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// NewReadCloserSize returns a reader with a custom number of buffers and size.
|
||||
// buffers is the number of queued buffers and size is the size of each
|
||||
// buffer in bytes.
|
||||
func NewReadCloserSize(rc io.ReadCloser, buffers, size int) (io.ReadCloser, error) {
|
||||
if size <= 0 {
|
||||
return nil, fmt.Errorf("buffer size too small")
|
||||
}
|
||||
if buffers <= 0 {
|
||||
return nil, fmt.Errorf("number of buffers too small")
|
||||
}
|
||||
if rc == nil {
|
||||
return nil, fmt.Errorf("nil input reader supplied")
|
||||
}
|
||||
a := &reader{closer: rc}
|
||||
a.init(rc, buffers, size)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// initialize the reader
|
||||
func (a *reader) init(rd io.Reader, buffers, size int) {
|
||||
a.in = rd
|
||||
a.ready = make(chan *buffer, buffers)
|
||||
a.reuse = make(chan *buffer, buffers)
|
||||
a.exit = make(chan struct{}, 0)
|
||||
a.exited = make(chan struct{}, 0)
|
||||
a.buffers = buffers
|
||||
a.cur = nil
|
||||
|
||||
// Create buffers
|
||||
for i := 0; i < buffers; i++ {
|
||||
a.reuse <- newBuffer(size)
|
||||
}
|
||||
|
||||
// Start async reader
|
||||
go func() {
|
||||
// Ensure that when we exit this is signalled.
|
||||
defer close(a.exited)
|
||||
for {
|
||||
select {
|
||||
case b := <-a.reuse:
|
||||
err := b.read(a.in)
|
||||
a.ready <- b
|
||||
if err != nil {
|
||||
close(a.ready)
|
||||
return
|
||||
}
|
||||
case <-a.exit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// fill will check if the current buffer is empty and fill it if it is.
|
||||
// If an error was returned at the end of the current buffer it is returned.
|
||||
func (a *reader) fill() (err error) {
|
||||
if a.cur.isEmpty() {
|
||||
if a.cur != nil {
|
||||
a.reuse <- a.cur
|
||||
a.cur = nil
|
||||
}
|
||||
b, ok := <-a.ready
|
||||
if !ok {
|
||||
return a.err
|
||||
}
|
||||
a.cur = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read will return the next available data.
|
||||
func (a *reader) Read(p []byte) (n int, err error) {
|
||||
// Swap buffer and maybe return error
|
||||
err = a.fill()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Copy what we can
|
||||
n = copy(p, a.cur.buffer())
|
||||
a.cur.inc(n)
|
||||
|
||||
// If at end of buffer, return any error, if present
|
||||
if a.cur.isEmpty() {
|
||||
a.err = a.cur.err
|
||||
return n, a.err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// WriteTo writes data to w until there's no more data to write or when an error occurs.
|
||||
// The return value n is the number of bytes written.
|
||||
// Any error encountered during the write is also returned.
|
||||
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n = 0
|
||||
for {
|
||||
err = a.fill()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n2, err := w.Write(a.cur.buffer())
|
||||
a.cur.inc(n2)
|
||||
n += int64(n2)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if a.cur.err != nil {
|
||||
// io.Writer should return nil if we are at EOF.
|
||||
if a.cur.err == io.EOF {
|
||||
a.err = a.cur.err
|
||||
return n, nil
|
||||
}
|
||||
a.err = a.cur.err
|
||||
return n, a.cur.err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close will ensure that the underlying async reader is shut down.
|
||||
// It will also close the input supplied on newAsyncReader.
|
||||
func (a *reader) Close() (err error) {
|
||||
select {
|
||||
case <-a.exited:
|
||||
case a.exit <- struct{}{}:
|
||||
<-a.exited
|
||||
}
|
||||
if a.closer != nil {
|
||||
// Only call once
|
||||
c := a.closer
|
||||
a.closer = nil
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Internal buffer representing a single read.
|
||||
// If an error is present, it must be returned
|
||||
// once all buffer content has been served.
|
||||
type buffer struct {
|
||||
buf []byte
|
||||
err error
|
||||
offset int
|
||||
size int
|
||||
}
|
||||
|
||||
func newBuffer(size int) *buffer {
|
||||
return &buffer{buf: make([]byte, size), err: nil, size: size}
|
||||
}
|
||||
|
||||
// isEmpty returns true is offset is at end of
|
||||
// buffer, or if the buffer is nil
|
||||
func (b *buffer) isEmpty() bool {
|
||||
if b == nil {
|
||||
return true
|
||||
}
|
||||
if len(b.buf)-b.offset <= 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// read into start of the buffer from the supplied reader,
|
||||
// resets the offset and updates the size of the buffer.
|
||||
// Any error encountered during the read is returned.
|
||||
func (b *buffer) read(rd io.Reader) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic reading: %v", r)
|
||||
b.err = err
|
||||
}
|
||||
}()
|
||||
var n int
|
||||
n, b.err = rd.Read(b.buf[0:b.size])
|
||||
b.buf = b.buf[0:n]
|
||||
b.offset = 0
|
||||
return b.err
|
||||
}
|
||||
|
||||
// Return the buffer at current offset
|
||||
func (b *buffer) buffer() []byte {
|
||||
return b.buf[b.offset:]
|
||||
}
|
||||
|
||||
// inc will increment the read offset
|
||||
func (b *buffer) inc(n int) {
|
||||
b.offset += n
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
Copyright 2016, Google
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-1204
File diff suppressed because it is too large
Load Diff
-81
@@ -1,81 +0,0 @@
|
||||
// Copyright 2017, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func noEscape(c byte) bool {
|
||||
switch c {
|
||||
case '.', '_', '-', '/', '~', '!', '$', '\'', '(', ')', '*', ';', '=', ':', '@':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func escape(s string) string {
|
||||
// cribbed from url.go, kinda
|
||||
b := &bytes.Buffer{}
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch c := s[i]; {
|
||||
case c == '/':
|
||||
b.WriteByte(c)
|
||||
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9':
|
||||
b.WriteByte(c)
|
||||
case noEscape(c):
|
||||
b.WriteByte(c)
|
||||
default:
|
||||
fmt.Fprintf(b, "%%%X", c)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func unescape(s string) (string, error) {
|
||||
b := &bytes.Buffer{}
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch c {
|
||||
case '/':
|
||||
b.WriteString("/")
|
||||
case '+':
|
||||
b.WriteString(" ")
|
||||
case '%':
|
||||
if len(s)-i < 3 {
|
||||
return "", errors.New("unescape: bad encoding")
|
||||
}
|
||||
b.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2]))
|
||||
i += 2
|
||||
default:
|
||||
b.WriteByte(c)
|
||||
}
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func unhex(c byte) byte {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0'
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10
|
||||
}
|
||||
return 0
|
||||
}
|
||||
-255
@@ -1,255 +0,0 @@
|
||||
// Copyright 2016, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package b2types implements internal types common to the B2 API.
|
||||
package b2types
|
||||
|
||||
// You know what would be amazing? If I could autogen this from like a JSON
|
||||
// file. Wouldn't that be amazing? That would be amazing.
|
||||
|
||||
const (
|
||||
V1api = "/b2api/v1/"
|
||||
)
|
||||
|
||||
type ErrorMessage struct {
|
||||
Status int `json:"status"`
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"message"`
|
||||
}
|
||||
|
||||
type AuthorizeAccountResponse struct {
|
||||
AccountID string `json:"accountId"`
|
||||
AuthToken string `json:"authorizationToken"`
|
||||
URI string `json:"apiUrl"`
|
||||
DownloadURI string `json:"downloadUrl"`
|
||||
MinPartSize int `json:"minimumPartSize"`
|
||||
}
|
||||
|
||||
type LifecycleRule struct {
|
||||
DaysHiddenUntilDeleted int `json:"daysFromHidingToDeleting,omitempty"`
|
||||
DaysNewUntilHidden int `json:"daysFromUploadingToHiding,omitempty"`
|
||||
Prefix string `json:"fileNamePrefix"`
|
||||
}
|
||||
|
||||
type CreateBucketRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
Name string `json:"bucketName"`
|
||||
Type string `json:"bucketType"`
|
||||
Info map[string]string `json:"bucketInfo"`
|
||||
LifecycleRules []LifecycleRule `json:"lifecycleRules"`
|
||||
}
|
||||
|
||||
type CreateBucketResponse struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Name string `json:"bucketName"`
|
||||
Type string `json:"bucketType"`
|
||||
Info map[string]string `json:"bucketInfo"`
|
||||
LifecycleRules []LifecycleRule `json:"lifecycleRules"`
|
||||
Revision int `json:"revision"`
|
||||
}
|
||||
|
||||
type DeleteBucketRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
BucketID string `json:"bucketId"`
|
||||
}
|
||||
|
||||
type ListBucketsRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
}
|
||||
|
||||
type ListBucketsResponse struct {
|
||||
Buckets []CreateBucketResponse `json:"buckets"`
|
||||
}
|
||||
|
||||
type UpdateBucketRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
BucketID string `json:"bucketId"`
|
||||
// bucketName is a required field according to
|
||||
// https://www.backblaze.com/b2/docs/b2_update_bucket.html.
|
||||
//
|
||||
// However, actually setting it returns 400: unknown field in
|
||||
// com.backblaze.modules.b2.data.UpdateBucketRequest: bucketName
|
||||
//
|
||||
//Name string `json:"bucketName"`
|
||||
Type string `json:"bucketType,omitempty"`
|
||||
Info map[string]string `json:"bucketInfo,omitempty"`
|
||||
LifecycleRules []LifecycleRule `json:"lifecycleRules,omitempty"`
|
||||
IfRevisionIs int `json:"ifRevisionIs,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateBucketResponse CreateBucketResponse
|
||||
|
||||
type GetUploadURLRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
}
|
||||
|
||||
type GetUploadURLResponse struct {
|
||||
URI string `json:"uploadUrl"`
|
||||
Token string `json:"authorizationToken"`
|
||||
}
|
||||
|
||||
type UploadFileResponse struct {
|
||||
FileID string `json:"fileId"`
|
||||
Timestamp int64 `json:"uploadTimestamp"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
type DeleteFileVersionRequest struct {
|
||||
Name string `json:"fileName"`
|
||||
FileID string `json:"fileId"`
|
||||
}
|
||||
|
||||
type StartLargeFileRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Name string `json:"fileName"`
|
||||
ContentType string `json:"contentType"`
|
||||
Info map[string]string `json:"fileInfo,omitempty"`
|
||||
}
|
||||
|
||||
type StartLargeFileResponse struct {
|
||||
ID string `json:"fileId"`
|
||||
}
|
||||
|
||||
type CancelLargeFileRequest struct {
|
||||
ID string `json:"fileId"`
|
||||
}
|
||||
|
||||
type ListUnfinishedLargeFilesRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Continuation string `json:"startFileId,omitempty"`
|
||||
Count int `json:"maxFileCount,omitempty"`
|
||||
}
|
||||
|
||||
type ListUnfinishedLargeFilesResponse struct {
|
||||
NextID string `json:"nextFileId"`
|
||||
Files []struct {
|
||||
AccountID string `json:"accountId"`
|
||||
BucketID string `json:"bucketId"`
|
||||
Name string `json:"fileName"`
|
||||
ID string `json:"fileId"`
|
||||
Timestamp int64 `json:"uploadTimestamp"`
|
||||
ContentType string `json:"contentType"`
|
||||
Info map[string]string `json:"fileInfo,omitempty"`
|
||||
} `json:"files"`
|
||||
}
|
||||
|
||||
type ListPartsRequest struct {
|
||||
ID string `json:"fileId"`
|
||||
Start int `json:"startPartNumber"`
|
||||
Count int `json:"maxPartCount"`
|
||||
}
|
||||
|
||||
type ListPartsResponse struct {
|
||||
Next int `json:"nextPartNumber"`
|
||||
Parts []struct {
|
||||
ID string `json:"fileId"`
|
||||
Number int `json:"partNumber"`
|
||||
SHA1 string `json:"contentSha1"`
|
||||
Size int64 `json:"contentLength"`
|
||||
} `json:"parts"`
|
||||
}
|
||||
|
||||
type getUploadPartURLRequest struct {
|
||||
ID string `json:"fileId"`
|
||||
}
|
||||
|
||||
type getUploadPartURLResponse struct {
|
||||
URL string `json:"uploadUrl"`
|
||||
Token string `json:"authorizationToken"`
|
||||
}
|
||||
|
||||
type UploadPartResponse struct {
|
||||
ID string `json:"fileId"`
|
||||
PartNumber int `json:"partNumber"`
|
||||
Size int64 `json:"contentLength"`
|
||||
SHA1 string `json:"contentSha1"`
|
||||
}
|
||||
|
||||
type FinishLargeFileRequest struct {
|
||||
ID string `json:"fileId"`
|
||||
Hashes []string `json:"partSha1Array"`
|
||||
}
|
||||
|
||||
type FinishLargeFileResponse struct {
|
||||
Name string `json:"fileName"`
|
||||
FileID string `json:"fileId"`
|
||||
Timestamp int64 `json:"uploadTimestamp"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
type ListFileNamesRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Count int `json:"maxFileCount"`
|
||||
Continuation string `json:"startFileName,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty"`
|
||||
Delimiter string `json:"delimiter,omitempty"`
|
||||
}
|
||||
|
||||
type ListFileNamesResponse struct {
|
||||
Continuation string `json:"nextFileName"`
|
||||
Files []GetFileInfoResponse `json:"files"`
|
||||
}
|
||||
|
||||
type ListFileVersionsRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Count int `json:"maxFileCount"`
|
||||
StartName string `json:"startFileName,omitempty"`
|
||||
StartID string `json:"startFileId,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty"`
|
||||
Delimiter string `json:"delimiter,omitempty"`
|
||||
}
|
||||
|
||||
type ListFileVersionsResponse struct {
|
||||
NextName string `json:"nextFileName"`
|
||||
NextID string `json:"nextFileId"`
|
||||
Files []GetFileInfoResponse `json:"files"`
|
||||
}
|
||||
|
||||
type HideFileRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
File string `json:"fileName"`
|
||||
}
|
||||
|
||||
type HideFileResponse struct {
|
||||
ID string `json:"fileId"`
|
||||
Timestamp int64 `json:"uploadTimestamp"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
type GetFileInfoRequest struct {
|
||||
ID string `json:"fileId"`
|
||||
}
|
||||
|
||||
type GetFileInfoResponse struct {
|
||||
FileID string `json:"fileId"`
|
||||
Name string `json:"fileName"`
|
||||
SHA1 string `json:"contentSha1"`
|
||||
Size int64 `json:"contentLength"`
|
||||
ContentType string `json:"contentType"`
|
||||
Info map[string]string `json:"fileInfo"`
|
||||
Action string `json:"action"`
|
||||
Timestamp int64 `json:"uploadTimestamp"`
|
||||
}
|
||||
|
||||
type GetDownloadAuthorizationRequest struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Prefix string `json:"fileNamePrefix"`
|
||||
Valid int `json:"validDurationInSeconds"`
|
||||
}
|
||||
|
||||
type GetDownloadAuthorizationResponse struct {
|
||||
BucketID string `json:"bucketId"`
|
||||
Prefix string `json:"fileNamePrefix"`
|
||||
Token string `json:"authorizationToken"`
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
// Copyright 2017, Google
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package blog implements a private logger, in the manner of glog, without
|
||||
// polluting the flag namespace or leaving files all over /tmp.
|
||||
//
|
||||
// It has almost no features, and a bunch of global state.
|
||||
package blog
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var level int32
|
||||
|
||||
type Verbose bool
|
||||
|
||||
func init() {
|
||||
lvl := os.Getenv("B2_LOG_LEVEL")
|
||||
i, err := strconv.ParseInt(lvl, 10, 32)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
level = int32(i)
|
||||
}
|
||||
|
||||
func (v Verbose) Info(a ...interface{}) {
|
||||
if v {
|
||||
log.Print(a...)
|
||||
}
|
||||
}
|
||||
|
||||
func (v Verbose) Infof(format string, a ...interface{}) {
|
||||
if v {
|
||||
log.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func V(target int32) Verbose {
|
||||
return Verbose(target <= level)
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
# errors [](https://travis-ci.org/pkg/errors) [](https://ci.appveyor.com/project/davecheney/errors/branch/master) [](http://godoc.org/github.com/pkg/errors) [](https://goreportcard.com/report/github.com/pkg/errors) [](https://sourcegraph.com/github.com/pkg/errors?badge)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
BSD-2-Clause
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// and the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||
// functions destructure errors.Wrap into its component operations of annotating
|
||||
// an error with a stack trace and an a message, respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error which does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// causer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface.
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// Where errors.StackTrace is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// stackTracer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is call, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s function name and path of source file relative to the compile time
|
||||
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s lists source files for each Frame in the stack
|
||||
// %v lists the source file and line number for each Frame in the stack
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
func trimGOPATH(name, file string) string {
|
||||
// Here we want to get the source file path relative to the compile time
|
||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired output. We count separators from the end of the file
|
||||
// path until it finds two more than in the function name and then move
|
||||
// one character forward to preserve the initial path segment without a
|
||||
// leading separator.
|
||||
const sep = "/"
|
||||
goal := strings.Count(name, sep) + 2
|
||||
i := len(file)
|
||||
for n := 0; n < goal; n++ {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
// not enough separators found, set i so that the slice expression
|
||||
// below leaves file unmodified
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
file = file[i+len(sep):]
|
||||
return file
|
||||
}
|
||||
Reference in New Issue
Block a user