1
0
mirror of https://github.com/strongdm/comply synced 2024-11-24 16:44:53 +00:00

Update go.mod and adjust pandoc and watch usability (#64)

This commit is contained in:
wallrony 2021-10-06 14:33:14 -03:00
parent 34c0105b47
commit 3c19f849d7
987 changed files with 103235 additions and 35518 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ output
dist dist
.envrc .envrc
bindata.go bindata.go
.idea/

84
go.mod
View File

@ -1,50 +1,72 @@
module github.com/strongdm/comply module github.com/strongdm/comply
go 1.12 go 1.17
require ( require (
github.com/Clever/gitsem v1.0.4 github.com/Clever/gitsem v1.1.0
github.com/Microsoft/go-winio v0.4.14 // indirect github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/aktau/github-release v0.8.1 github.com/aktau/github-release v0.10.0
github.com/andygrunwald/go-jira v1.12.0 github.com/andygrunwald/go-jira v1.14.0
github.com/containerd/containerd v1.5.7 // indirect
github.com/containous/go-bindata v1.0.0 github.com/containous/go-bindata v1.0.0
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker v20.10.9+incompatible
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/elazarl/go-bindata-assetfs v1.0.1
github.com/fatih/color v1.9.0 github.com/fatih/color v1.13.0
github.com/fatih/structs v1.1.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/github-release/github-release v0.8.1 // indirect github.com/github-release/github-release v0.10.0 // indirect
github.com/gohugoio/hugo v0.75.0 github.com/gohugoio/hugo v0.88.1
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/go-github v17.0.0+incompatible github.com/google/go-github v17.0.0+incompatible
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/hashicorp/go-retryablehttp v0.6.7 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 // indirect github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect
github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c // indirect
github.com/manifoldco/promptui v0.7.0 github.com/manifoldco/promptui v0.8.0
github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/nanobox-io/golang-scribble v0.0.0-20190309225732-aa3e7c118975 github.com/nanobox-io/golang-scribble v0.0.0-20190309225732-aa3e7c118975
github.com/olekukonko/tablewriter v0.0.4 github.com/olekukonko/tablewriter v0.0.5
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/robfig/cron v1.2.0 github.com/robfig/cron v1.2.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/trivago/tgo v1.0.7 // indirect github.com/urfave/cli v1.22.5
github.com/urfave/cli v1.22.4
github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 // indirect github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 // indirect
github.com/xanzy/go-gitlab v0.30.1 github.com/xanzy/go-gitlab v0.51.1
github.com/yosssi/ace v0.0.5 github.com/yosssi/ace v0.0.5
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
gopkg.in/blang/semver.v1 v1.1.0 // indirect google.golang.org/genproto v0.0.0-20211005153810-c76a74d43a8e // indirect
gopkg.in/yaml.v2 v2.3.0 google.golang.org/grpc v1.41.0 // indirect
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/lunixbochs/vtclean v1.0.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/trivago/tgo v1.0.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/blang/semver.v1 v1.1.0 // indirect
) )

1009
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -46,8 +46,8 @@ func (g *gitlabPlugin) api() *gitlab.Client {
defer g.clientMu.Unlock() defer g.clientMu.Unlock()
if g.client == nil { if g.client == nil {
// get go-gitlab client // get go-gitlab client
gl := gitlab.NewClient(nil, g.token) // TODO: see if it's necessary to verify the error
gl.SetBaseURL(g.domain) gl, _ := gitlab.NewClient(g.token, gitlab.WithBaseURL(g.domain))
g.client = gl g.client = gl
} }
return g.client return g.client
@ -160,7 +160,7 @@ func (g *gitlabPlugin) Create(ticket *model.Ticket, labels []string) error {
options := &gitlab.CreateIssueOptions{ options := &gitlab.CreateIssueOptions{
Title: gitlab.String(ticket.Name), Title: gitlab.String(ticket.Name),
Description: gitlab.String(ticket.Body), Description: gitlab.String(ticket.Body),
Labels: &l, Labels: l,
} }
_, _, err := g.api().Issues.CreateIssue(g.reponame, options) _, _, err := g.api().Issues.CreateIssue(g.reponame, options)
return err return err

View File

@ -49,7 +49,7 @@ func dockerPandoc(outputFilename string, errOutputCh chan error) {
resp, err := cli.ContainerCreate(ctx, &container.Config{ resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "strongdm/pandoc", Image: "strongdm/pandoc",
Cmd: pandocCmd}, Cmd: pandocCmd},
hc, nil, "") hc, nil, nil, "")
if err != nil { if err != nil {
errOutputCh <- errors.Wrap(err, "unable to create Docker container") errOutputCh <- errors.Wrap(err, "unable to create Docker container")
@ -71,9 +71,12 @@ func dockerPandoc(outputFilename string, errOutputCh chan error) {
return return
} }
_, err = cli.ContainerWait(ctx, resp.ID) chanResult, chanErr := cli.ContainerWait(ctx, resp.ID, "not-running")
if err != nil { resultValue := <-chanResult
errOutputCh <- errors.Wrap(err, "error awaiting Docker container")
if resultValue.StatusCode != 0 {
err = <-chanErr
errOutputCh <-errors.Wrap(err, "error awaiting Docker container")
return return
} }

View File

@ -8,7 +8,8 @@ import (
) )
func watch(errCh chan error) { func watch(errCh chan error) {
b, err := watcher.New(300 * time.Millisecond) // TODO: study about the poll duration
b, err := watcher.New(300 * time.Millisecond, 0, false)
if err != nil { if err != nil {
errCh <- err errCh <- err
return return
@ -25,7 +26,7 @@ func watch(errCh chan error) {
go func() { go func() {
for { for {
select { select {
case e := <-b.Errors: case e := <-b.Errors():
errCh <- e errCh <- e
case <-b.Events: case <-b.Events:
broadcast() broadcast()

View File

@ -1,24 +0,0 @@
image: bradrydzewski/go:1.3
script:
- make test
notify:
email:
recipients:
- drone@clever.com
hipchat:
room: Clever-Dev-CI
token: {{hipchat_token}}
on_started: true
on_success: true
on_failure: true
publish:
github:
branch: master
script:
- make release
artifacts:
- release
tag: v$(cat VERSION)
token: {{github_token}}
user: Clever
repo: gitsem

190
vendor/github.com/Clever/gitsem/LICENSE generated vendored Normal file
View File

@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Clever, Inc.
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.

View File

@ -1,8 +1,11 @@
include golang.mk
.DEFAULT_GOAL := test # override default goal set in library makefile
SHELL := /bin/bash SHELL := /bin/bash
PKG = github.com/Clever/gitsem PKG := github.com/Clever/gitsem
PKGS = $(PKG) PKGS := $(shell go list ./... | grep -v /vendor)
VERSION := $(shell cat VERSION)
EXECUTABLE := gitsem EXECUTABLE := gitsem
VERSION := $(shell cat VERSION)
BUILDS := \ BUILDS := \
build/$(EXECUTABLE)-v$(VERSION)-darwin-amd64 \ build/$(EXECUTABLE)-v$(VERSION)-darwin-amd64 \
build/$(EXECUTABLE)-v$(VERSION)-linux-amd64 \ build/$(EXECUTABLE)-v$(VERSION)-linux-amd64 \
@ -10,32 +13,22 @@ BUILDS := \
COMPRESSED_BUILDS := $(BUILDS:%=%.tar.gz) COMPRESSED_BUILDS := $(BUILDS:%=%.tar.gz)
RELEASE_ARTIFACTS := $(COMPRESSED_BUILDS:build/%=release/%) RELEASE_ARTIFACTS := $(COMPRESSED_BUILDS:build/%=release/%)
.PHONY: test golint .PHONY: test golint build vendor
golint: $(eval $(call golang-version-check,1.13))
@go get github.com/golang/lint/golint
test: $(PKGS) test: $(PKGS)
$(PKGS): golint $(PKGS): golang-test-all-strict-deps
@go get -d -t $@ $(call golang-test-all-strict,$@)
@gofmt -w=true $(GOPATH)/src/$@*/**.go
ifneq ($(NOLINT),1)
@echo "LINTING..."
@PATH=$(PATH):$(GOPATH)/bin golint $(GOPATH)/src/$@*/**.go
@echo ""
endif
ifeq ($(COVERAGE),1)
@go test -cover -coverprofile=$(GOPATH)/src/$@/c.out $@ -test.v
@go tool cover -html=$(GOPATH)/src/$@/c.out
else
@echo "TESTING..."
@go test $@ -test.v
endif
run: run:
@go run main.go @go run main.go
build:
go build -o bin/$(EXECUTABLE) $(PKG)
build/$(EXECUTABLE)-v$(VERSION)-darwin-amd64: build/$(EXECUTABLE)-v$(VERSION)-darwin-amd64:
GOARCH=amd64 GOOS=darwin go build -o "$@/$(EXECUTABLE)" GOARCH=amd64 GOOS=darwin go build -o "$@/$(EXECUTABLE)"
build/$(EXECUTABLE)-v$(VERSION)-linux-amd64: build/$(EXECUTABLE)-v$(VERSION)-linux-amd64:
@ -52,3 +45,7 @@ release: $(RELEASE_ARTIFACTS)
clean: clean:
rm -rf build release rm -rf build release
install_deps:
go mod vendor

View File

@ -32,3 +32,8 @@ In the second case, the existing version will be incremented by 1 in the specifi
### Options ### Options
- `m=%s` specifies a commit message to use when bumping the version. If %s appears, it will be replaced with the new version number. - `m=%s` specifies a commit message to use when bumping the version. If %s appears, it will be replaced with the new version number.
- `tag=true` whether or not to create a tag at the version commit - `tag=true` whether or not to create a tag at the version commit
## Vendoring
Please view the [dev-handbook for instructions](https://github.com/Clever/dev-handbook/blob/master/golang/godep.md).

View File

@ -1 +1 @@
1.0.4 1.1.0

View File

@ -7,7 +7,7 @@ import (
) )
func isRepoClean() (bool, error) { func isRepoClean() (bool, error) {
cmd := exec.Command("git", "status", "-s") cmd := exec.Command("git", "status", "--porcelain")
result := &bytes.Buffer{} result := &bytes.Buffer{}
cmd.Stdout = result cmd.Stdout = result
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {

156
vendor/github.com/Clever/gitsem/golang.mk generated vendored Normal file
View File

@ -0,0 +1,156 @@
# This is the default Clever Golang Makefile.
# It is stored in the dev-handbook repo, github.com/Clever/dev-handbook
# Please do not alter this file directly.
GOLANG_MK_VERSION := 1.0.0
SHELL := /bin/bash
SYSTEM := $(shell uname -a | cut -d" " -f1 | tr '[:upper:]' '[:lower:]')
.PHONY: golang-test-deps golang-ensure-curl-installed
# set timezone to UTC for golang to match circle and deploys
export TZ=UTC
# go build flags for use across all commands which accept them
GO_BUILD_FLAGS := "-mod=vendor"
# if the gopath includes several directories, use only the first
GOPATH=$(shell echo $$GOPATH | cut -d: -f1)
# This block checks and confirms that the proper Go toolchain version is installed.
# It uses ^ matching in the semver sense -- you can be ahead by a minor
# version, but not a major version (patch is ignored).
# arg1: golang version
define golang-version-check
_ := $(if \
$(shell \
expr >/dev/null \
`go version | cut -d" " -f3 | cut -c3- | cut -d. -f2 | sed -E 's/beta[0-9]+//'` \
\>= `echo $(1) | cut -d. -f2` \
\& \
`go version | cut -d" " -f3 | cut -c3- | cut -d. -f1` \
= `echo $(1) | cut -d. -f1` \
&& echo 1), \
@echo "", \
$(error must be running Go version ^$(1) - you are running $(shell go version | cut -d" " -f3 | cut -c3-)))
endef
# FGT is a utility that exits with 1 whenever any stderr/stdout output is recieved.
# We pin its version since its a simple tool that does its job as-is;
# so we're defended against it breaking or changing in the future.
FGT := $(GOPATH)/bin/fgt
$(FGT):
go get github.com/GeertJohan/fgt@262f7b11eec07dc7b147c44641236f3212fee89d
golang-ensure-curl-installed:
@command -v curl >/dev/null 2>&1 || { echo >&2 "curl not installed. Please install curl."; exit 1; }
# Golint is a tool for linting Golang code for common errors.
# We pin its version because an update could add a new lint check which would make
# previously passing tests start failing without changing our code.
GOLINT := $(GOPATH)/bin/golint
$(GOLINT):
go get golang.org/x/lint/golint@738671d3881b9731cc63024d5d88cf28db875626
# golang-fmt-deps requires the FGT tool for checking output
golang-fmt-deps: $(FGT)
# golang-fmt checks that all golang files in the pkg are formatted correctly.
# arg1: pkg path
define golang-fmt
@echo "FORMATTING $(1)..."
@PKG_PATH=$$(go list -f '{{.Dir}}' $(1)); $(FGT) gofmt -l=true $${PKG_PATH}/*.go
endef
# golang-lint-deps requires the golint tool for golang linting.
golang-lint-deps: $(GOLINT)
# golang-lint calls golint on all golang files in the pkg.
# arg1: pkg path
define golang-lint
@echo "LINTING $(1)..."
@PKG_PATH=$$(go list -f '{{.Dir}}' $(1)); find $${PKG_PATH}/*.go -type f | grep -v gen_ | xargs $(GOLINT)
endef
# golang-lint-deps-strict requires the golint tool for golang linting.
golang-lint-deps-strict: $(GOLINT) $(FGT)
# golang-lint-strict calls golint on all golang files in the pkg and fails if any lint
# errors are found.
# arg1: pkg path
define golang-lint-strict
@echo "LINTING $(1)..."
@PKG_PATH=$$(go list -f '{{.Dir}}' $(1)); find $${PKG_PATH}/*.go -type f | grep -v gen_ | xargs $(FGT) $(GOLINT)
endef
# golang-test-deps is here for consistency
golang-test-deps:
# golang-test uses the Go toolchain to run all tests in the pkg.
# arg1: pkg path
define golang-test
@echo "TESTING $(1)..."
@go test $(GO_BUILD_FLAGS) -v $(1)
endef
# golang-test-strict-deps is here for consistency
golang-test-strict-deps:
# golang-test-strict uses the Go toolchain to run all tests in the pkg with the race flag
# arg1: pkg path
define golang-test-strict
@echo "TESTING $(1)..."
@go test -v $(GO_BUILD_FLAGS) -race $(1)
endef
# golang-vet-deps is here for consistency
golang-vet-deps:
# golang-vet uses the Go toolchain to vet all the pkg for common mistakes.
# arg1: pkg path
define golang-vet
@echo "VETTING $(1)..."
@go vet $(GO_BUILD_FLAGS) $(1)
endef
# golang-test-all-deps installs all dependencies needed for different test cases.
golang-test-all-deps: golang-fmt-deps golang-lint-deps golang-test-deps golang-vet-deps
# golang-test-all calls fmt, lint, vet and test on the specified pkg.
# arg1: pkg path
define golang-test-all
$(call golang-fmt,$(1))
$(call golang-lint,$(1))
$(call golang-vet,$(1))
$(call golang-test,$(1))
endef
# golang-test-all-strict-deps: installs all dependencies needed for different test cases.
golang-test-all-strict-deps: golang-fmt-deps golang-lint-deps-strict golang-test-strict-deps golang-vet-deps
# golang-test-all-strict calls fmt, lint, vet and test on the specified pkg with strict
# requirements that no errors are thrown while linting.
# arg1: pkg path
define golang-test-all-strict
$(call golang-fmt,$(1))
$(call golang-lint-strict,$(1))
$(call golang-vet,$(1))
$(call golang-test-strict,$(1))
endef
# golang-build: builds a golang binary. ensures CGO build is done during CI. This is needed to make a binary that works with a Docker alpine image.
# arg1: pkg path
# arg2: executable name
define golang-build
@echo "BUILDING..."
@if [ -z "$$CI" ]; then \
go build $(GO_BUILD_FLAGS) -o bin/$(2) $(1); \
else \
echo "-> Building CGO binary"; \
CGO_ENABLED=0 go build $(GO_BUILD_FLAGS) -installsuffix cgo -o bin/$(2) $(1); \
fi;
endef
# golang-update-makefile downloads latest version of golang.mk
golang-update-makefile:
@wget https://raw.githubusercontent.com/Clever/dev-handbook/master/make/golang-v1.mk -O /tmp/golang.mk 2>/dev/null
@if ! grep -q $(GOLANG_MK_VERSION) /tmp/golang.mk; then cp /tmp/golang.mk golang.mk && echo "golang.mk updated"; else echo "golang.mk is up-to-date"; fi

1
vendor/github.com/Microsoft/go-winio/CODEOWNERS generated vendored Normal file
View File

@ -0,0 +1 @@
* @microsoft/containerplat

View File

@ -1,4 +1,4 @@
# go-winio # go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml)
This repository contains utilities for efficiently performing Win32 IO operations in This repository contains utilities for efficiently performing Win32 IO operations in
Go. Currently, this is focused on accessing named pipes and other file handles, and Go. Currently, this is focused on accessing named pipes and other file handles, and

View File

@ -5,21 +5,14 @@ package winio
import ( import (
"os" "os"
"runtime" "runtime"
"syscall"
"unsafe" "unsafe"
)
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx "golang.org/x/sys/windows"
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
const (
fileBasicInfo = 0
fileIDInfo = 0x12
) )
// FileBasicInfo contains file access time and file attributes information. // FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct { type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
FileAttributes uint32 FileAttributes uint32
pad uint32 // padding pad uint32 // padding
} }
@ -27,7 +20,7 @@ type FileBasicInfo struct {
// GetFileBasicInfo retrieves times and attributes for a file. // GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{} bi := &FileBasicInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f) runtime.KeepAlive(f)
@ -36,13 +29,32 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
// SetFileBasicInfo sets times and attributes for a file. // SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f) runtime.KeepAlive(f)
return nil return nil
} }
// FileStandardInfo contains extended information for the file.
// FILE_STANDARD_INFO in WinBase.h
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
type FileStandardInfo struct {
AllocationSize, EndOfFile int64
NumberOfLinks uint32
DeletePending, Directory bool
}
// GetFileStandardInfo retrieves ended information for the file.
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
si := &FileStandardInfo{}
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return si, nil
}
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be // FileIDInfo contains the volume serial number and file ID for a file. This pair should be
// unique on a system. // unique on a system.
type FileIDInfo struct { type FileIDInfo struct {
@ -53,7 +65,7 @@ type FileIDInfo struct {
// GetFileID retrieves the unique (volume, file ID) pair for a file. // GetFileID retrieves the unique (volume, file ID) pair for a file.
func GetFileID(f *os.File) (*FileIDInfo, error) { func GetFileID(f *os.File) (*FileIDInfo, error) {
fileID := &FileIDInfo{} fileID := &FileIDInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil { if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f) runtime.KeepAlive(f)

View File

@ -1,9 +0,0 @@
module github.com/Microsoft/go-winio
go 1.12
require (
github.com/pkg/errors v0.8.1
github.com/sirupsen/logrus v1.4.1
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b
)

View File

@ -1,16 +0,0 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -1,3 +1,5 @@
// +build windows
package winio package winio
import ( import (

View File

@ -182,13 +182,14 @@ func (s pipeAddress) String() string {
} }
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. // tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) { func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return syscall.Handle(0), ctx.Err() return syscall.Handle(0), ctx.Err()
default: default:
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err == nil { if err == nil {
return h, nil return h, nil
} }
@ -197,7 +198,7 @@ func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
} }
// Wait 10 msec and try again. This is a rather simplistic // Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds. // view, as we always try each 10 milliseconds.
time.Sleep(time.Millisecond * 10) time.Sleep(10 * time.Millisecond)
} }
} }
} }
@ -210,7 +211,7 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
if timeout != nil { if timeout != nil {
absTimeout = time.Now().Add(*timeout) absTimeout = time.Now().Add(*timeout)
} else { } else {
absTimeout = time.Now().Add(time.Second * 2) absTimeout = time.Now().Add(2 * time.Second)
} }
ctx, _ := context.WithDeadline(context.Background(), absTimeout) ctx, _ := context.WithDeadline(context.Background(), absTimeout)
conn, err := DialPipeContext(ctx, path) conn, err := DialPipeContext(ctx, path)
@ -223,9 +224,15 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx` // DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
// cancellation or timeout. // cancellation or timeout.
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE)
}
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
// cancellation or timeout.
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
var err error var err error
var h syscall.Handle var h syscall.Handle
h, err = tryDialPipe(ctx, &path) h, err = tryDialPipe(ctx, &path, access)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -422,10 +429,10 @@ type PipeConfig struct {
// when the pipe is in message mode. // when the pipe is in message mode.
MessageMode bool MessageMode bool
// InputBufferSize specifies the size the input buffer, in bytes. // InputBufferSize specifies the size of the input buffer, in bytes.
InputBufferSize int32 InputBufferSize int32
// OutputBufferSize specifies the size the input buffer, in bytes. // OutputBufferSize specifies the size of the output buffer, in bytes.
OutputBufferSize int32 OutputBufferSize int32
} }

View File

@ -1,3 +1,5 @@
// +build windows
// Package guid provides a GUID type. The backing structure for a GUID is // Package guid provides a GUID type. The backing structure for a GUID is
// identical to that used by the golang.org/x/sys/windows GUID type. // identical to that used by the golang.org/x/sys/windows GUID type.
// There are two main binary encodings used for a GUID, the big-endian encoding, // There are two main binary encodings used for a GUID, the big-endian encoding,

View File

@ -30,6 +30,7 @@ const (
SeBackupPrivilege = "SeBackupPrivilege" SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege" SeRestorePrivilege = "SeRestorePrivilege"
SeSecurityPrivilege = "SeSecurityPrivilege"
) )
const ( const (

View File

@ -1,3 +1,3 @@
package winio package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go

File diff suppressed because it is too large Load Diff

View File

@ -10,44 +10,15 @@
version = "v1.0.0" version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d" digest = "1:7462b19f7e4adb24e966b77213c693dad09c04d58ac8d29ddf449ff1b1c91d11"
name = "github.com/go-stack/stack"
packages = ["."]
pruneopts = "UT"
revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a"
version = "v1.8.0"
[[projects]]
digest = "1:5c56664d98f37f0ee54bf572b0b189a3910c34c31052fc7d58b282c449b079fb"
name = "github.com/inconshreveable/log15"
packages = ["."]
pruneopts = "UT"
revision = "b30bc20e4fd12cec79a9aae62e91cfcf458bd253"
version = "v2.15"
[[projects]]
digest = "1:de0adde670b2119824a1252b61a0e989669f8b24af874e399bec0e0538b2f928"
name = "github.com/kevinburke/rest" name = "github.com/kevinburke/rest"
packages = ["."] packages = [
"restclient",
"resterror",
]
pruneopts = "UT" pruneopts = "UT"
revision = "0d2892b400f81cdfb979e2f718e6070fae17a507" revision = "22cd0577e450f2fa21313f7eaf42b41a178291c1"
version = "2.2" version = "2.5"
[[projects]]
digest = "1:0109cf4321a15313ec895f42e723e1f76121c6975ea006abfa20012272ec0937"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "68e95eba382c972aafde02ead2cd2426a8a92480"
version = "v0.1.6"
[[projects]]
digest = "1:0c58d31abe2a2ccb429c559b6292e7df89dcda675456fecc282fa90aa08273eb"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "7b513a986450394f7bbf1476909911b3aa3a55ce"
version = "v0.0.12"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -65,23 +36,12 @@
revision = "26cb8b04692384f4dc269de3b5fcf3e2ef78573e" revision = "26cb8b04692384f4dc269de3b5fcf3e2ef78573e"
version = "2.5.11" version = "2.5.11"
[[projects]]
branch = "master"
digest = "1:8cab10971112233c82c83683a517378038eba1c20e71b29c592b73fa212437b3"
name = "golang.org/x/sys"
packages = [
"internal/unsafeheader",
"unix",
]
pruneopts = "UT"
revision = "bc7a7d42d5c30f4d0fe808715c002826ce2c624e"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
input-imports = [ input-imports = [
"github.com/dustin/go-humanize", "github.com/dustin/go-humanize",
"github.com/kevinburke/rest", "github.com/kevinburke/rest/restclient",
"github.com/tomnomnom/linkheader", "github.com/tomnomnom/linkheader",
"github.com/voxelbrain/goptions", "github.com/voxelbrain/goptions",
] ]

View File

@ -9,7 +9,7 @@
[[constraint]] [[constraint]]
name = "github.com/kevinburke/rest" name = "github.com/kevinburke/rest"
version = "2.2.0" version = "2.5.0"
[[constraint]] [[constraint]]
name = "github.com/tomnomnom/linkheader" name = "github.com/tomnomnom/linkheader"

View File

@ -91,6 +91,9 @@ clean:
rm $(EXECUTABLE) || true rm $(EXECUTABLE) || true
rm -rf bin/ rm -rf bin/
lint:
go vet ./...
test: test:
go test ./... go test ./...

View File

@ -101,6 +101,13 @@ $ github-release delete \
--tag v0.1.0 --tag v0.1.0
``` ```
Errata
======
The `release` command does not have an `--auth-user` flag because in practice,
Github ignores the `--auth-user` flag when validating releases. The only thing
that matters is passing a token that has permission to create the release.
GitHub Enterprise Support GitHub Enterprise Support
========================= =========================
You can point to a different GitHub API endpoint via the environment variable ```GITHUB_API```: You can point to a different GitHub API endpoint via the environment variable ```GITHUB_API```:

View File

@ -354,10 +354,17 @@ func releasecmd(opt Options) error {
} }
reader := bytes.NewReader(payload) reader := bytes.NewReader(payload)
URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf("/repos/%s/%s/releases", user, repo) // NB: Github appears to ignore the user here - the only thing that seems to
resp, err := github.DoAuthRequest("POST", URL, "application/json", token, nil, reader) // matter is that the token is valid.
client := github.NewClient(user, token, nil)
client.SetBaseURL(EnvApiEndpoint)
req, err := client.NewRequest("POST", fmt.Sprintf("/repos/%s/%s/releases", user, repo), reader)
if err != nil { if err != nil {
return fmt.Errorf("while submitting %v, %v", string(payload), err) return fmt.Errorf("while submitting %v: %w", string(payload), err)
}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("while submitting %v: %w", string(payload), err)
} }
defer resp.Body.Close() defer resp.Body.Close()

View File

@ -1,20 +0,0 @@
language: go
sudo: false
go:
- "1.9.x"
- "1.10.x"
- "1.11.x"
- "1.12.x"
- "1.13.x"
before_install:
- go get -t ./...
matrix:
allow_failures:
- go: 1.13.x
script:
- GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...

View File

@ -2,6 +2,32 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.13.0](https://github.com/andygrunwald/go-jira/compare/v1.11.1...v1.13.0) (2020-10-25)
### Features
* add AddRemoteLink method ([f200e15](https://github.com/andygrunwald/go-jira/commit/f200e158b997a303db081cbbc5a9d8ad5d89566d)), closes [/developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2](https://github.com/andygrunwald//developer.atlassian.com/cloud/jira/platform/rest/v2//issues/api-rest-api-2)
* Add Names support on Issue struct ([#278](https://github.com/andygrunwald/go-jira/issues/278)) ([1fc10e0](https://github.com/andygrunwald/go-jira/commit/1fc10e0606784f745673ccc4d8d706c36f385a7a))
* Extend Makefile for more source code quality targets ([5e52236](https://github.com/andygrunwald/go-jira/commit/5e5223631a29d10a13e598318a6abe47384e2982))
* **context:** Add support for context package ([e1f4265](https://github.com/andygrunwald/go-jira/commit/e1f4265e2b467b938fe0c095caf6d36f3136d2ff))
* **issues:** Add GetEditMeta on issue ([a783764](https://github.com/andygrunwald/go-jira/commit/a783764b52dc890773658ddd0483a9d0393e385d)), closes [/docs.atlassian.com/DAC/rest/jira/6.1.html#d2e1364](https://github.com/andygrunwald//docs.atlassian.com/DAC/rest/jira/6.1.html/issues/d2e1364)
* **IssueService:** allow empty JQL ([#268](https://github.com/andygrunwald/go-jira/issues/268)) ([4b91cf2](https://github.com/andygrunwald/go-jira/commit/4b91cf2b135355de7ecee41727c3e65f4e7067bc))
* **project:** Add cronjob to check for stale issues ([#287](https://github.com/andygrunwald/go-jira/issues/287)) ([2096b04](https://github.com/andygrunwald/go-jira/commit/2096b04e52b434c1fb1c841bab487a94674a271e))
* **project:** Add GitHub Actions testing workflow ([#289](https://github.com/andygrunwald/go-jira/issues/289)) ([80c0282](https://github.com/andygrunwald/go-jira/commit/80c02828ca9e4eb0e4a1877275baae14d330a2d9)), closes [#290](https://github.com/andygrunwald/go-jira/issues/290)
* **project:** Add workflow to greet new contributors ([#288](https://github.com/andygrunwald/go-jira/issues/288)) ([c357b61](https://github.com/andygrunwald/go-jira/commit/c357b61a40f62a919ebd94a555390958f99c8db7))
### Bug Fixes
* change millisecond time format ([8c77107](https://github.com/andygrunwald/go-jira/commit/8c77107df3757c4ec5eae6e9d7c018618e708bfa))
* paging with load balancer going to endless loop ([19d3fc0](https://github.com/andygrunwald/go-jira/commit/19d3fc0aecde547ffe1ab547c5ffb6c7972d387c)), closes [#260](https://github.com/andygrunwald/go-jira/issues/260)
* **issue:** IssueService.Search() with a not empty JQL triggers 400 bad request ([#292](https://github.com/andygrunwald/go-jira/issues/292)) ([8b64c7f](https://github.com/andygrunwald/go-jira/commit/8b64c7f005fbceb11fa43a7aff3de61eb3166fca)), closes [#291](https://github.com/andygrunwald/go-jira/issues/291)
* **IssueService.GetWatchers:** UserService.GetByAccountID support accountId params ([436469b](https://github.com/andygrunwald/go-jira/commit/436469b62d4d62037f380b38c918a13f4a5f0ab2))
* **product:** Make product naming consistent, rename JIRA to Jira ([#286](https://github.com/andygrunwald/go-jira/issues/286)) ([146229d](https://github.com/andygrunwald/go-jira/commit/146229d2ab58a3fb128ddc8dcbe03aff72e20857)), closes [#284](https://github.com/andygrunwald/go-jira/issues/284)
* **tests:** Fix TestIssueService_PostAttachment unit test ([f6b1dca](https://github.com/andygrunwald/go-jira/commit/f6b1dcafcfdd8fe69f842b1053c4030da6c97c7f))
* removing the use of username field in searching for users ([#297](https://github.com/andygrunwald/go-jira/issues/297)) ([f50cb07](https://github.com/andygrunwald/go-jira/commit/f50cb07b297d79138b13e5ab49ea33965d32f5c1))
## [1.12.0](https://github.com/andygrunwald/go-jira/compare/v1.11.1...v1.12.0) (2019-12-14) ## [1.12.0](https://github.com/andygrunwald/go-jira/compare/v1.11.1...v1.12.0) (2019-12-14)
@ -76,6 +102,3 @@ All notable changes to this project will be documented in this file. See [standa
* Add ResolutionService to retrieve resolutions ([fb1ce22](https://github.com/andygrunwald/go-jira/commit/fb1ce22)) * Add ResolutionService to retrieve resolutions ([fb1ce22](https://github.com/andygrunwald/go-jira/commit/fb1ce22))
* Add status category constants ([6223ddd](https://github.com/andygrunwald/go-jira/commit/6223ddd)) * Add status category constants ([6223ddd](https://github.com/andygrunwald/go-jira/commit/6223ddd))
* Add StatusCategory GetList ([049a756](https://github.com/andygrunwald/go-jira/commit/049a756)) * Add StatusCategory GetList ([049a756](https://github.com/andygrunwald/go-jira/commit/049a756))

View File

@ -1,2 +1,25 @@
test: .DEFAULT_GOAL := help
go test -v ./...
.PHONY: help
help: ## Outputs the help.
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: test
test: ## Runs all unit, integration and example tests.
go test -race -v ./...
.PHONY: vet
vet: ## Runs go vet (to detect suspicious constructs).
go vet ./...
.PHONY: fmt
fmt: ## Runs go fmt (to check for go coding guidelines).
gofmt -d -s .
.PHONY: staticcheck
staticcheck: ## Runs static analysis to prevend bugs, foster code simplicity, performance and editor integration.
go get -u honnef.co/go/tools/cmd/staticcheck
staticcheck ./...
.PHONY: all
all: test vet fmt staticcheck ## Runs all source code quality targets (like test, vet, fmt, staticcheck)

View File

@ -1,15 +0,0 @@
# PR Description
_What does this fix or add?_
# Checklist
* [ ] Tests added
* [ ] Good Path
* [ ] Error Path
* [ ] Commits follow conventions described here:
* [ ] [https://conventionalcommits.org/en/v1.0.0-beta.4/#summary](https://conventionalcommits.org/en/v1.0.0-beta.4/#summary)
* [ ] [https://chris.beams.io/posts/git-commit/#seven-rules](https://chris.beams.io/posts/git-commit/#seven-rules)
* [ ] Commits are squashed such that
* [ ] There is 1 commit per isolated change
* [ ] I've not made extraneous commits/changes that are unrelated to my change.

View File

@ -1,26 +1,29 @@
# go-jira # go-jira
[![GoDoc](https://godoc.org/github.com/andygrunwald/go-jira?status.svg)](https://godoc.org/github.com/andygrunwald/go-jira) [![GoDoc](https://godoc.org/github.com/andygrunwald/go-jira?status.svg)](https://godoc.org/github.com/andygrunwald/go-jira)
[![Build Status](https://travis-ci.org/andygrunwald/go-jira.svg?branch=master)](https://travis-ci.org/andygrunwald/go-jira) [![Build Status](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml/badge.svg)](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/andygrunwald/go-jira)](https://goreportcard.com/report/github.com/andygrunwald/go-jira) [![Go Report Card](https://goreportcard.com/badge/github.com/andygrunwald/go-jira)](https://goreportcard.com/report/github.com/andygrunwald/go-jira)
[Go](https://golang.org/) client library for [Atlassian JIRA](https://www.atlassian.com/software/jira). [Go](https://golang.org/) client library for [Atlassian Jira](https://www.atlassian.com/software/jira).
![Go client library for Atlassian JIRA](./img/logo_small.png "Go client library for Atlassian JIRA.") ![Go client library for Atlassian Jira](./img/logo_small.png "Go client library for Atlassian Jira.")
## Features ## Features
* Authentication (HTTP Basic, OAuth, Session Cookie) * Authentication (HTTP Basic, OAuth, Session Cookie)
* Create and retrieve issues * Create and retrieve issues
* Create and retrieve issue transitions (status updates) * Create and retrieve issue transitions (status updates)
* Call every API endpoint of the JIRA, even if it is not directly implemented in this library * Call every API endpoint of the Jira, even if it is not directly implemented in this library
This package is not JIRA API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of JIRA have a look at [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/). This package is not Jira API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of Jira have a look at [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/).
## Requirements ## Requirements
* Go >= 1.8 * Go >= 1.14
* JIRA v6.3.4 & v7.1.2. * Jira v6.3.4 & v7.1.2.
Note that we also run our tests against 1.13, though only the last two versions
of Go are officially supported.
## Installation ## Installation
@ -52,7 +55,7 @@ go test -v ./...
Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-jira) for a detailed API description. Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-jira) for a detailed API description.
The [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package. The [latest Jira REST API documentation](https://docs.atlassian.com/jira/REST/latest/) was the base document for this package.
## Examples ## Examples
@ -92,9 +95,11 @@ an `http.Client`. That client can then be passed into the `NewClient` function
For convenience, capability for basic and cookie-based authentication is included in the main library. For convenience, capability for basic and cookie-based authentication is included in the main library.
#### Basic auth example #### Token (Jira on Atlassian Cloud)
A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory. **It's worth noting that using passwords in basic auth is now deprecated and will be removed.** Jira gives you the ability to [create tokens now.](https://confluence.atlassian.com/cloud/api-tokens-938839638.html) Token-based authentication uses the basic authentication scheme, with a user-generated API token in place of a user's password. You can generate a token for your user [here](https://id.atlassian.com/manage-profile/security/api-tokens). Additional information about Atlassian Cloud API tokens can be found [here](https://confluence.atlassian.com/cloud/api-tokens-938839638.html).
A more thorough, [runnable example](examples/basicauth/main.go) is provided in the examples directory.
```go ```go
func main() { func main() {
@ -111,14 +116,15 @@ func main() {
} }
``` ```
#### Authenticate with session cookie [DEPRECATED] #### Basic (self-hosted Jira)
JIRA [deprecated this authentication method.](https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/) It's not longer available for use. Password-based API authentication works for self-hosted Jira **only**, and has been [deprecated for users of Atlassian Cloud](https://developer.atlassian.com/cloud/jira/platform/deprecation-notice-basic-auth-and-cookie-based-auth/).
The above token authentication example may be used, substituting a user's password for a generated token.
#### Authenticate with OAuth #### Authenticate with OAuth
If you want to connect via OAuth to your JIRA Cloud instance checkout the [example of using OAuth authentication with JIRA in Go](https://gist.github.com/Lupus/edafe9a7c5c6b13407293d795442fe67) by [@Lupus](https://github.com/Lupus). If you want to connect via OAuth to your Jira Cloud instance checkout the [example of using OAuth authentication with Jira in Go](https://gist.github.com/Lupus/edafe9a7c5c6b13407293d795442fe67) by [@Lupus](https://github.com/Lupus).
For more details have a look at the [issue #56](https://github.com/andygrunwald/go-jira/issues/56). For more details have a look at the [issue #56](https://github.com/andygrunwald/go-jira/issues/56).
@ -173,11 +179,62 @@ func main() {
} }
``` ```
### Change an issue status
This is how one can change an issue status. In this example, we change the issue from "To Do" to "In Progress."
```go
package main
import (
"fmt"
"github.com/andygrunwald/go-jira"
)
func main() {
base := "https://my.jira.com"
tp := jira.BasicAuthTransport{
Username: "username",
Password: "token",
}
jiraClient, err := jira.NewClient(tp.Client(), base)
if err != nil {
panic(err)
}
issue, _, _ := jiraClient.Issue.Get("FART-1", nil)
currentStatus := issue.Fields.Status.Name
fmt.Printf("Current status: %s\n", currentStatus)
var transitionID string
possibleTransitions, _, _ := jiraClient.Issue.GetTransitions("FART-1")
for _, v := range possibleTransitions {
if v.Name == "In Progress" {
transitionID = v.ID
break
}
}
jiraClient.Issue.DoTransition("FART-1", transitionID)
issue, _, _ = jiraClient.Issue.Get(testIssueID, nil)
fmt.Printf("Status after transition: %+v\n", issue.Fields.Status.Name)
}
```
### Get all the issues for JQL with Pagination
Jira API has limit on maxResults it can return. You may have a usecase where you need to get all issues for given JQL.
This example shows reference implementation of GetAllIssues function which does pagination on Jira API to get all the issues for given JQL
please look at [Pagination Example](https://github.com/andygrunwald/go-jira/blob/master/examples/pagination/main.go)
### Call a not implemented API endpoint ### Call a not implemented API endpoint
Not all API endpoints of the JIRA API are implemented into *go-jira*. Not all API endpoints of the Jira API are implemented into *go-jira*.
But you can call them anyway: But you can call them anyway:
Lets get all public projects of [Atlassian`s JIRA instance](https://jira.atlassian.com/). Lets get all public projects of [Atlassian`s Jira instance](https://jira.atlassian.com/).
```go ```go
package main package main
@ -209,7 +266,7 @@ func main() {
// ... // ...
// BAM: Bamboo // BAM: Bamboo
// BAMJ: Bamboo JIRA Plugin // BAMJ: Bamboo Jira Plugin
// CLOV: Clover // CLOV: Clover
// CONF: Confluence // CONF: Confluence
// ... // ...
@ -218,7 +275,7 @@ func main() {
## Implementations ## Implementations
* [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The JIRA Ticket Checker * [andygrunwald/jitic](https://github.com/andygrunwald/jitic) - The Jira Ticket Checker
## Code structure ## Code structure
@ -226,7 +283,7 @@ The code structure of this package was inspired by [google/go-github](https://gi
There is one main part (the client). There is one main part (the client).
Based on this main client the other endpoints, like Issues or Authentication are extracted in services. E.g. `IssueService` or `AuthenticationService`. Based on this main client the other endpoints, like Issues or Authentication are extracted in services. E.g. `IssueService` or `AuthenticationService`.
These services own a responsibility of the single endpoints / usecases of JIRA. These services own a responsibility of the single endpoints / usecases of Jira.
## Contribution ## Contribution
@ -258,7 +315,7 @@ You can read more about them at https://developer.atlassian.com/blog/2016/04/clo
## Releasing ## Releasing
Install `standard-version` Install [standard-version](https://github.com/conventional-changelog/standard-version)
```bash ```bash
npm i -g standard-version npm i -g standard-version
``` ```

View File

@ -1,6 +1,7 @@
package jira package jira
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -14,9 +15,9 @@ const (
authTypeSession = 2 authTypeSession = 2
) )
// AuthenticationService handles authentication for the JIRA instance / API. // AuthenticationService handles authentication for the Jira instance / API.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#authentication // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#authentication
type AuthenticationService struct { type AuthenticationService struct {
client *Client client *Client
@ -30,7 +31,7 @@ type AuthenticationService struct {
password string password string
} }
// Session represents a Session JSON response by the JIRA API. // Session represents a Session JSON response by the Jira API.
type Session struct { type Session struct {
Self string `json:"self,omitempty"` Self string `json:"self,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
@ -47,16 +48,16 @@ type Session struct {
Cookies []*http.Cookie Cookies []*http.Cookie
} }
// AcquireSessionCookie creates a new session for a user in JIRA. // AcquireSessionCookieWithContext creates a new session for a user in Jira.
// Once a session has been successfully created it can be used to access any of JIRA's remote APIs and also the web UI by passing the appropriate HTTP Cookie header. // Once a session has been successfully created it can be used to access any of Jira's remote APIs and also the web UI by passing the appropriate HTTP Cookie header.
// The header will by automatically applied to every API request. // The header will by automatically applied to every API request.
// Note that it is generally preferrable to use HTTP BASIC authentication with the REST API. // Note that it is generally preferrable to use HTTP BASIC authentication with the REST API.
// However, this resource may be used to mimic the behaviour of JIRA's log-in page (e.g. to display log-in errors to a user). // However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user).
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
// //
// Deprecated: Use CookieAuthTransport instead // Deprecated: Use CookieAuthTransport instead
func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) { func (s *AuthenticationService) AcquireSessionCookieWithContext(ctx context.Context, username, password string) (bool, error) {
apiEndpoint := "rest/auth/1/session" apiEndpoint := "rest/auth/1/session"
body := struct { body := struct {
Username string `json:"username"` Username string `json:"username"`
@ -66,7 +67,7 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
password, password,
} }
req, err := s.client.NewRequest("POST", apiEndpoint, body) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, body)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -79,10 +80,10 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
} }
if err != nil { if err != nil {
return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). %s", err) return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). %s", err)
} }
if resp != nil && resp.StatusCode != 200 { if resp != nil && resp.StatusCode != 200 {
return false, fmt.Errorf("Auth at JIRA instance failed (HTTP(S) request). Status code: %d", resp.StatusCode) return false, fmt.Errorf("auth at Jira instance failed (HTTP(S) request). Status code: %d", resp.StatusCode)
} }
s.client.session = session s.client.session = session
@ -91,7 +92,14 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
return true, nil return true, nil
} }
// SetBasicAuth sets username and password for the basic auth against the JIRA instance. // AcquireSessionCookie wraps AcquireSessionCookieWithContext using the background context.
//
// Deprecated: Use CookieAuthTransport instead
func (s *AuthenticationService) AcquireSessionCookie(username, password string) (bool, error) {
return s.AcquireSessionCookieWithContext(context.Background(), username, password)
}
// SetBasicAuth sets username and password for the basic auth against the Jira instance.
// //
// Deprecated: Use BasicAuthTransport instead // Deprecated: Use BasicAuthTransport instead
func (s *AuthenticationService) SetBasicAuth(username, password string) { func (s *AuthenticationService) SetBasicAuth(username, password string) {
@ -100,7 +108,7 @@ func (s *AuthenticationService) SetBasicAuth(username, password string) {
s.authType = authTypeBasic s.authType = authTypeBasic
} }
// Authenticated reports if the current Client has authentication details for JIRA // Authenticated reports if the current Client has authentication details for Jira
func (s *AuthenticationService) Authenticated() bool { func (s *AuthenticationService) Authenticated() bool {
if s != nil { if s != nil {
if s.authType == authTypeSession { if s.authType == authTypeSession {
@ -113,29 +121,29 @@ func (s *AuthenticationService) Authenticated() bool {
return false return false
} }
// Logout logs out the current user that has been authenticated and the session in the client is destroyed. // LogoutWithContext logs out the current user that has been authenticated and the session in the client is destroyed.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
// //
// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the // Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the
// client anymore // client anymore
func (s *AuthenticationService) Logout() error { func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error {
if s.authType != authTypeSession || s.client.session == nil { if s.authType != authTypeSession || s.client.session == nil {
return fmt.Errorf("no user is authenticated") return fmt.Errorf("no user is authenticated")
} }
apiEndpoint := "rest/auth/1/session" apiEndpoint := "rest/auth/1/session"
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
if err != nil { if err != nil {
return fmt.Errorf("Creating the request to log the user out failed : %s", err) return fmt.Errorf("creating the request to log the user out failed : %s", err)
} }
resp, err := s.client.Do(req, nil) resp, err := s.client.Do(req, nil)
if err != nil { if err != nil {
return fmt.Errorf("Error sending the logout request: %s", err) return fmt.Errorf("error sending the logout request: %s", err)
} }
if resp.StatusCode != 204 { if resp.StatusCode != 204 {
return fmt.Errorf("The logout was unsuccessful with status %d", resp.StatusCode) return fmt.Errorf("the logout was unsuccessful with status %d", resp.StatusCode)
} }
// If logout successful, delete session // If logout successful, delete session
@ -145,43 +153,56 @@ func (s *AuthenticationService) Logout() error {
} }
// GetCurrentUser gets the details of the current user. // Logout wraps LogoutWithContext using the background context.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the
func (s *AuthenticationService) GetCurrentUser() (*Session, error) { // client anymore
func (s *AuthenticationService) Logout() error {
return s.LogoutWithContext(context.Background())
}
// GetCurrentUserWithContext gets the details of the current user.
//
// Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
func (s *AuthenticationService) GetCurrentUserWithContext(ctx context.Context) (*Session, error) {
if s == nil { if s == nil {
return nil, fmt.Errorf("AUthenticaiton Service is not instantiated") return nil, fmt.Errorf("authentication Service is not instantiated")
} }
if s.authType != authTypeSession || s.client.session == nil { if s.authType != authTypeSession || s.client.session == nil {
return nil, fmt.Errorf("No user is authenticated yet") return nil, fmt.Errorf("no user is authenticated yet")
} }
apiEndpoint := "rest/auth/1/session" apiEndpoint := "rest/auth/1/session"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not create request for getting user info : %s", err) return nil, fmt.Errorf("could not create request for getting user info : %s", err)
} }
resp, err := s.client.Do(req, nil) resp, err := s.client.Do(req, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error sending request to get user info : %s", err) return nil, fmt.Errorf("error sending request to get user info : %s", err)
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return nil, fmt.Errorf("Getting user info failed with status : %d", resp.StatusCode) return nil, fmt.Errorf("getting user info failed with status : %d", resp.StatusCode)
} }
defer resp.Body.Close() defer resp.Body.Close()
ret := new(Session) ret := new(Session)
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("Couldn't read body from the response : %s", err) return nil, fmt.Errorf("couldn't read body from the response : %s", err)
} }
err = json.Unmarshal(data, &ret) err = json.Unmarshal(data, &ret)
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not unmarshall received user info : %s", err) return nil, fmt.Errorf("could not unmarshall received user info : %s", err)
} }
return ret, nil return ret, nil
} }
// GetCurrentUser wraps GetCurrentUserWithContext using the background context.
func (s *AuthenticationService) GetCurrentUser() (*Session, error) {
return s.GetCurrentUserWithContext(context.Background())
}

View File

@ -1,14 +1,15 @@
package jira package jira
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
) )
// BoardService handles Agile Boards for the JIRA instance / API. // BoardService handles Agile Boards for the Jira instance / API.
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/server/ // Jira API docs: https://docs.atlassian.com/jira-software/REST/server/
type BoardService struct { type BoardService struct {
client *Client client *Client
} }
@ -22,7 +23,7 @@ type BoardsList struct {
Values []Board `json:"values" structs:"values"` Values []Board `json:"values" structs:"values"`
} }
// Board represents a JIRA agile board // Board represents a Jira agile board
type Board struct { type Board struct {
ID int `json:"id,omitempty" structs:"id,omitempty"` ID int `json:"id,omitempty" structs:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"` Self string `json:"self,omitempty" structs:"self,omitempty"`
@ -62,7 +63,7 @@ type SprintsList struct {
Values []Sprint `json:"values" structs:"values"` Values []Sprint `json:"values" structs:"values"`
} }
// Sprint represents a sprint on JIRA agile board // Sprint represents a sprint on Jira agile board
type Sprint struct { type Sprint struct {
ID int `json:"id" structs:"id"` ID int `json:"id" structs:"id"`
Name string `json:"name" structs:"name"` Name string `json:"name" structs:"name"`
@ -124,16 +125,16 @@ type BoardConfigurationColumnStatus struct {
Self string `json:"self"` Self string `json:"self"`
} }
// GetAllBoards will returns all boards. This only includes boards that the user has permission to view. // GetAllBoardsWithContext will returns all boards. This only includes boards that the user has permission to view.
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards
func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) { func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) {
apiEndpoint := "rest/agile/1.0/board" apiEndpoint := "rest/agile/1.0/board"
url, err := addOptions(apiEndpoint, opt) url, err := addOptions(apiEndpoint, opt)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
req, err := s.client.NewRequest("GET", url, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -148,13 +149,18 @@ func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Respon
return boards, resp, err return boards, resp, err
} }
// GetBoard will returns the board for the given boardID. // GetAllBoards wraps GetAllBoardsWithContext using the background context.
func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) {
return s.GetAllBoardsWithContext(context.Background(), opt)
}
// GetBoardWithContext will returns the board for the given boardID.
// This board will only be returned if the user has permission to view it. // This board will only be returned if the user has permission to view it.
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard
func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) { func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -169,17 +175,22 @@ func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) {
return board, resp, nil return board, resp, nil
} }
// CreateBoard creates a new board. Board name, type and filter Id is required. // GetBoard wraps GetBoardWithContext using the background context.
func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) {
return s.GetBoardWithContext(context.Background(), boardID)
}
// CreateBoardWithContext creates a new board. Board name, type and filter Id is required.
// name - Must be less than 255 characters. // name - Must be less than 255 characters.
// type - Valid values: scrum, kanban // type - Valid values: scrum, kanban
// filterId - Id of a filter that the user has permissions to view. // filterId - Id of a filter that the user has permissions to view.
// Note, if the user does not have the 'Create shared objects' permission and tries to create a shared board, a private // Note, if the user does not have the 'Create shared objects' permission and tries to create a shared board, a private
// board will be created instead (remember that board sharing depends on the filter sharing). // board will be created instead (remember that board sharing depends on the filter sharing).
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard
func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) { func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) {
apiEndpoint := "rest/agile/1.0/board" apiEndpoint := "rest/agile/1.0/board"
req, err := s.client.NewRequest("POST", apiEndpoint, board) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, board)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -194,12 +205,17 @@ func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
return responseBoard, resp, nil return responseBoard, resp, nil
} }
// DeleteBoard will delete an agile board. // CreateBoard wraps CreateBoardWithContext using the background context.
func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
return s.CreateBoardWithContext(context.Background(), board)
}
// DeleteBoardWithContext will delete an agile board.
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard
func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) { func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -211,11 +227,16 @@ func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
return nil, resp, err return nil, resp, err
} }
// GetAllSprints will return all sprints from a board, for a given board Id. // DeleteBoard wraps DeleteBoardWithContext using the background context.
func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
return s.DeleteBoardWithContext(context.Background(), boardID)
}
// GetAllSprintsWithContext will return all sprints from a board, for a given board Id.
// This only includes sprints that the user has permission to view. // This only includes sprints that the user has permission to view.
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) { func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID string) ([]Sprint, *Response, error) {
id, err := strconv.Atoi(boardID) id, err := strconv.Atoi(boardID)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -229,17 +250,22 @@ func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error
return result.Values, response, nil return result.Values, response, nil
} }
// GetAllSprintsWithOptions will return sprints from a board, for a given board Id and filtering options // GetAllSprints wraps GetAllSprintsWithContext using the background context.
func (s *BoardService) GetAllSprints(boardID string) ([]Sprint, *Response, error) {
return s.GetAllSprintsWithContext(context.Background(), boardID)
}
// GetAllSprintsWithOptionsWithContext will return sprints from a board, for a given board Id and filtering options
// This only includes sprints that the user has permission to view. // This only includes sprints that the user has permission to view.
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) { func (s *BoardService) GetAllSprintsWithOptionsWithContext(ctx context.Context, boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID) apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/sprint", boardID)
url, err := addOptions(apiEndpoint, options) url, err := addOptions(apiEndpoint, options)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
req, err := s.client.NewRequest("GET", url, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -253,12 +279,17 @@ func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSpri
return result, resp, err return result, resp, err
} }
// GetBoardConfiguration will return a board configuration for a given board Id // GetAllSprintsWithOptions wraps GetAllSprintsWithOptionsWithContext using the background context.
func (s *BoardService) GetAllSprintsWithOptions(boardID int, options *GetAllSprintsOptions) (*SprintsList, *Response, error) {
return s.GetAllSprintsWithOptionsWithContext(context.Background(), boardID, options)
}
// GetBoardConfigurationWithContext will return a board configuration for a given board Id
// Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get // Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get
func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration, *Response, error) { func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID) apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -273,3 +304,8 @@ func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration,
return result, resp, err return result, resp, err
} }
// GetBoardConfiguration wraps GetBoardConfigurationWithContext using the background context.
func (s *BoardService) GetBoardConfiguration(boardID int) (*BoardConfiguration, *Response, error) {
return s.GetBoardConfigurationWithContext(context.Background(), boardID)
}

View File

@ -1,13 +1,14 @@
package jira package jira
// ComponentService handles components for the JIRA instance / API. import "context"
//
// JIRA API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component // ComponentService handles components for the Jira instance / API.//
// Jira API docs: https://docs.atlassian.com/software/jira/docs/api/REST/7.10.1/#api/2/component
type ComponentService struct { type ComponentService struct {
client *Client client *Client
} }
// CreateComponentOptions are passed to the ComponentService.Create function to create a new JIRA component // CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component
type CreateComponentOptions struct { type CreateComponentOptions struct {
Name string `json:"name,omitempty" structs:"name,omitempty"` Name string `json:"name,omitempty" structs:"name,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"` Description string `json:"description,omitempty" structs:"description,omitempty"`
@ -19,10 +20,10 @@ type CreateComponentOptions struct {
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"`
} }
// Create creates a new JIRA component based on the given options. // CreateWithContext creates a new Jira component based on the given options.
func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) { func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
apiEndpoint := "rest/api/2/component" apiEndpoint := "rest/api/2/component"
req, err := s.client.NewRequest("POST", apiEndpoint, options) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, options)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -36,3 +37,8 @@ func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComp
return component, resp, nil return component, resp, nil
} }
// Create wraps CreateWithContext using the background context.
func (s *ComponentService) Create(options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
return s.CreateWithContext(context.Background(), options)
}

View File

@ -10,7 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// Error message from JIRA // Error message from Jira
// See https://docs.atlassian.com/jira/REST/cloud/#error-responses // See https://docs.atlassian.com/jira/REST/cloud/#error-responses
type Error struct { type Error struct {
HTTPError error HTTPError error
@ -34,12 +34,12 @@ func NewJiraError(resp *Response, httpError error) error {
if strings.HasPrefix(contentType, "application/json") { if strings.HasPrefix(contentType, "application/json") {
err = json.Unmarshal(body, &jerr) err = json.Unmarshal(body, &jerr)
if err != nil { if err != nil {
httpError = errors.Wrap(errors.New("Could not parse JSON"), httpError.Error()) httpError = errors.Wrap(errors.New("could not parse JSON"), httpError.Error())
return errors.Wrap(err, httpError.Error()) return errors.Wrap(err, httpError.Error())
} }
} else { } else {
if httpError == nil { if httpError == nil {
return fmt.Errorf("Got Response Status %s:%s", resp.Status, string(body)) return fmt.Errorf("got response status %s:%s", resp.Status, string(body))
} }
return errors.Wrap(httpError, fmt.Sprintf("%s: %s", resp.Status, string(body))) return errors.Wrap(httpError, fmt.Sprintf("%s: %s", resp.Status, string(body)))
} }

View File

@ -1,13 +1,15 @@
package jira package jira
// FieldService handles fields for the JIRA instance / API. import "context"
// FieldService handles fields for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field
type FieldService struct { type FieldService struct {
client *Client client *Client
} }
// Field represents a field of a JIRA issue. // Field represents a field of a Jira issue.
type Field struct { type Field struct {
ID string `json:"id,omitempty" structs:"id,omitempty"` ID string `json:"id,omitempty" structs:"id,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"` Key string `json:"key,omitempty" structs:"key,omitempty"`
@ -19,17 +21,22 @@ type Field struct {
Schema FieldSchema `json:"schema,omitempty" structs:"schema,omitempty"` Schema FieldSchema `json:"schema,omitempty" structs:"schema,omitempty"`
} }
// FieldSchema represents a schema of a Jira field.
// Documentation: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-fields/#api-rest-api-2-field-get
type FieldSchema struct { type FieldSchema struct {
Type string `json:"type,omitempty" structs:"type,omitempty"` Type string `json:"type,omitempty" structs:"type,omitempty"`
Items string `json:"items,omitempty" structs:"items,omitempty"`
Custom string `json:"custom,omitempty" structs:"custom,omitempty"`
System string `json:"system,omitempty" structs:"system,omitempty"` System string `json:"system,omitempty" structs:"system,omitempty"`
CustomID int64 `json:"customId,omitempty" structs:"customId,omitempty"`
} }
// GetList gets all fields from JIRA // GetListWithContext gets all fields from Jira
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get
func (s *FieldService) GetList() ([]Field, *Response, error) { func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) {
apiEndpoint := "rest/api/2/field" apiEndpoint := "rest/api/2/field"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -41,3 +48,8 @@ func (s *FieldService) GetList() ([]Field, *Response, error) {
} }
return fieldList, resp, nil return fieldList, resp, nil
} }
// GetList wraps GetListWithContext using the background context.
func (s *FieldService) GetList() ([]Field, *Response, error) {
return s.GetListWithContext(context.Background())
}

View File

@ -1,11 +1,15 @@
package jira package jira
import "github.com/google/go-querystring/query" import (
import "fmt" "context"
"fmt"
// FilterService handles fields for the JIRA instance / API. "github.com/google/go-querystring/query"
)
// FilterService handles fields for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Filter // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Filter
type FilterService struct { type FilterService struct {
client *Client client *Client
} }
@ -116,23 +120,21 @@ type FilterSearchOptions struct {
Expand string `url:"expand,omitempty"` Expand string `url:"expand,omitempty"`
} }
// GetList retrieves all filters from Jira // GetListWithContext retrieves all filters from Jira
func (fs *FilterService) GetList() ([]*Filter, *Response, error) { func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Response, error) {
options := &GetQueryOptions{} options := &GetQueryOptions{}
apiEndpoint := "rest/api/2/filter" apiEndpoint := "rest/api/2/filter"
req, err := fs.client.NewRequest("GET", apiEndpoint, nil) req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if options != nil {
q, err := query.Values(options) q, err := query.Values(options)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
req.URL.RawQuery = q.Encode() req.URL.RawQuery = q.Encode()
}
filters := []*Filter{} filters := []*Filter{}
resp, err := fs.client.Do(req, &filters) resp, err := fs.client.Do(req, &filters)
@ -143,10 +145,15 @@ func (fs *FilterService) GetList() ([]*Filter, *Response, error) {
return filters, resp, err return filters, resp, err
} }
// GetFavouriteList retrieves the user's favourited filters from Jira // GetList wraps GetListWithContext using the background context.
func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) { func (fs *FilterService) GetList() ([]*Filter, *Response, error) {
return fs.GetListWithContext(context.Background())
}
// GetFavouriteListWithContext retrieves the user's favourited filters from Jira
func (fs *FilterService) GetFavouriteListWithContext(ctx context.Context) ([]*Filter, *Response, error) {
apiEndpoint := "rest/api/2/filter/favourite" apiEndpoint := "rest/api/2/filter/favourite"
req, err := fs.client.NewRequest("GET", apiEndpoint, nil) req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -159,10 +166,15 @@ func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) {
return filters, resp, err return filters, resp, err
} }
// Get retrieves a single Filter from Jira // GetFavouriteList wraps GetFavouriteListWithContext using the background context.
func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) { func (fs *FilterService) GetFavouriteList() ([]*Filter, *Response, error) {
return fs.GetFavouriteListWithContext(context.Background())
}
// GetWithContext retrieves a single Filter from Jira
func (fs *FilterService) GetWithContext(ctx context.Context, filterID int) (*Filter, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID) apiEndpoint := fmt.Sprintf("rest/api/2/filter/%d", filterID)
req, err := fs.client.NewRequest("GET", apiEndpoint, nil) req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -176,16 +188,21 @@ func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) {
return filter, resp, err return filter, resp, err
} }
// GetMyFilters retrieves the my Filters. // Get wraps GetWithContext using the background context.
func (fs *FilterService) Get(filterID int) (*Filter, *Response, error) {
return fs.GetWithContext(context.Background(), filterID)
}
// GetMyFiltersWithContext retrieves the my Filters.
// //
// https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get // https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-my-get
func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) { func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) {
apiEndpoint := "rest/api/3/filter/my" apiEndpoint := "rest/api/3/filter/my"
url, err := addOptions(apiEndpoint, opts) url, err := addOptions(apiEndpoint, opts)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
req, err := fs.client.NewRequest("GET", url, nil) req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -199,16 +216,21 @@ func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter
return filters, resp, nil return filters, resp, nil
} }
// Search will search for filter according to the search options // GetMyFilters wraps GetMyFiltersWithContext using the background context.
func (fs *FilterService) GetMyFilters(opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) {
return fs.GetMyFiltersWithContext(context.Background(), opts)
}
// SearchWithContext will search for filter according to the search options
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get
func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Response, error) { func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) {
apiEndpoint := "rest/api/3/filter/search" apiEndpoint := "rest/api/3/filter/search"
url, err := addOptions(apiEndpoint, opt) url, err := addOptions(apiEndpoint, opt)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
req, err := fs.client.NewRequest("GET", url, nil) req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -222,3 +244,8 @@ func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Respon
return filters, resp, err return filters, resp, err
} }
// Search wraps SearchWithContext using the background context.
func (fs *FilterService) Search(opt *FilterSearchOptions) (*FiltersList, *Response, error) {
return fs.SearchWithContext(context.Background(), opt)
}

View File

@ -1,13 +0,0 @@
module github.com/andygrunwald/go-jira
go 1.12
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fatih/structs v1.0.0
github.com/google/go-cmp v0.3.0
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135
github.com/pkg/errors v0.8.0
github.com/trivago/tgo v1.0.1
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
)

View File

@ -1,20 +0,0 @@
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU=
github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/trivago/tgo v1.0.1 h1:bxatjJIXNIpV18bucU4Uk/LaoxvxuOlp/oowRHyncLQ=
github.com/trivago/tgo v1.0.1/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -1,13 +1,14 @@
package jira package jira
import ( import (
"context"
"fmt" "fmt"
"net/url" "net/url"
) )
// GroupService handles Groups for the JIRA instance / API. // GroupService handles Groups for the Jira instance / API.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group
type GroupService struct { type GroupService struct {
client *Client client *Client
} }
@ -21,7 +22,7 @@ type groupMembersResult struct {
Members []GroupMember `json:"values"` Members []GroupMember `json:"values"`
} }
// Group represents a JIRA group // Group represents a Jira group
type Group struct { type Group struct {
ID string `json:"id"` ID string `json:"id"`
Title string `json:"title"` Title string `json:"title"`
@ -58,16 +59,16 @@ type GroupSearchOptions struct {
IncludeInactiveUsers bool IncludeInactiveUsers bool
} }
// Get returns a paginated list of users who are members of the specified group and its subgroups. // GetWithContext returns a paginated list of users who are members of the specified group and its subgroups.
// Users in the page are ordered by user names. // Users in the page are ordered by user names.
// User of this resource is required to have sysadmin or admin permissions. // User of this resource is required to have sysadmin or admin permissions.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
// //
// WARNING: This API only returns the first page of group members // WARNING: This API only returns the first page of group members
func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) { func (s *GroupService) GetWithContext(ctx context.Context, name string) ([]GroupMember, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) apiEndpoint := fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -81,12 +82,17 @@ func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
return group.Members, resp, nil return group.Members, resp, nil
} }
// GetWithOptions returns a paginated list of members of the specified group and its subgroups. // Get wraps GetWithContext using the background context.
func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
return s.GetWithContext(context.Background(), name)
}
// GetWithOptionsWithContext returns a paginated list of members of the specified group and its subgroups.
// Users in the page are ordered by user names. // Users in the page are ordered by user names.
// User of this resource is required to have sysadmin or admin permissions. // User of this resource is required to have sysadmin or admin permissions.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup // Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) { func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
var apiEndpoint string var apiEndpoint string
if options == nil { if options == nil {
apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name)) apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
@ -99,7 +105,7 @@ func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions)
options.IncludeInactiveUsers, options.IncludeInactiveUsers,
) )
} }
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -112,16 +118,21 @@ func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions)
return group.Members, resp, nil return group.Members, resp, nil
} }
// Add adds user to group // GetWithOptions wraps GetWithOptionsWithContext using the background context.
func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
return s.GetWithOptionsWithContext(context.Background(), name, options)
}
// AddWithContext adds user to group
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup
func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) { func (s *GroupService) AddWithContext(ctx context.Context, groupname string, username string) (*Group, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname) apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s", groupname)
var user struct { var user struct {
Name string `json:"name"` Name string `json:"name"`
} }
user.Name = username user.Name = username
req, err := s.client.NewRequest("POST", apiEndpoint, &user) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, &user)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -136,12 +147,17 @@ func (s *GroupService) Add(groupname string, username string) (*Group, *Response
return responseGroup, resp, nil return responseGroup, resp, nil
} }
// Remove removes user from group // Add wraps AddWithContext using the background context.
func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) {
return s.AddWithContext(context.Background(), groupname, username)
}
// RemoveWithContext removes user from group
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup
func (s *GroupService) Remove(groupname string, username string) (*Response, error) { func (s *GroupService) RemoveWithContext(ctx context.Context, groupname string, username string) (*Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username) apiEndpoint := fmt.Sprintf("/rest/api/2/group/user?groupname=%s&username=%s", groupname, username)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,3 +170,8 @@ func (s *GroupService) Remove(groupname string, username string) (*Response, err
return resp, nil return resp, nil
} }
// Remove wraps RemoveWithContext using the background context.
func (s *GroupService) Remove(groupname string, username string) (*Response, error) {
return s.RemoveWithContext(context.Background(), groupname, username)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,25 @@
package jira package jira
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
) )
// IssueLinkTypeService handles issue link types for the JIRA instance / API. // IssueLinkTypeService handles issue link types for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Issue-link-types
type IssueLinkTypeService struct { type IssueLinkTypeService struct {
client *Client client *Client
} }
// GetList gets all of the issue link types from JIRA. // GetListWithContext gets all of the issue link types from Jira.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-get
func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) { func (s *IssueLinkTypeService) GetListWithContext(ctx context.Context) ([]IssueLinkType, *Response, error) {
apiEndpoint := "rest/api/2/issueLinkType" apiEndpoint := "rest/api/2/issueLinkType"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -31,12 +32,17 @@ func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) {
return linkTypeList, resp, nil return linkTypeList, resp, nil
} }
// Get gets info of a specific issue link type from JIRA. // GetList wraps GetListWithContext using the background context.
func (s *IssueLinkTypeService) GetList() ([]IssueLinkType, *Response, error) {
return s.GetListWithContext(context.Background())
}
// GetWithContext gets info of a specific issue link type from Jira.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-get
func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) { func (s *IssueLinkTypeService) GetWithContext(ctx context.Context, ID string) (*IssueLinkType, *Response, error) {
apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) apiEndPoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID)
req, err := s.client.NewRequest("GET", apiEndPoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -49,12 +55,17 @@ func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error)
return linkType, resp, nil return linkType, resp, nil
} }
// Create creates an issue link type in JIRA. // Get wraps GetWithContext using the background context.
func (s *IssueLinkTypeService) Get(ID string) (*IssueLinkType, *Response, error) {
return s.GetWithContext(context.Background(), ID)
}
// CreateWithContext creates an issue link type in Jira.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-post
func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { func (s *IssueLinkTypeService) CreateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
apiEndpoint := "/rest/api/2/issueLinkType" apiEndpoint := "/rest/api/2/issueLinkType"
req, err := s.client.NewRequest("POST", apiEndpoint, linkType) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, linkType)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -68,23 +79,28 @@ func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType,
defer resp.Body.Close() defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
e := fmt.Errorf("Could not read the returned data") e := fmt.Errorf("could not read the returned data")
return nil, resp, NewJiraError(resp, e) return nil, resp, NewJiraError(resp, e)
} }
err = json.Unmarshal(data, responseLinkType) err = json.Unmarshal(data, responseLinkType)
if err != nil { if err != nil {
e := fmt.Errorf("Could no unmarshal the data into struct") e := fmt.Errorf("could no unmarshal the data into struct")
return nil, resp, NewJiraError(resp, e) return nil, resp, NewJiraError(resp, e)
} }
return linkType, resp, nil return linkType, resp, nil
} }
// Update updates an issue link type. The issue is found by key. // Create wraps CreateWithContext using the background context.
func (s *IssueLinkTypeService) Create(linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
return s.CreateWithContext(context.Background(), linkType)
}
// UpdateWithContext updates an issue link type. The issue is found by key.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-put
func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, *Response, error) { func (s *IssueLinkTypeService) UpdateWithContext(ctx context.Context, linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID) apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", linkType.ID)
req, err := s.client.NewRequest("PUT", apiEndpoint, linkType) req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, linkType)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -96,12 +112,17 @@ func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType,
return &ret, resp, nil return &ret, resp, nil
} }
// Delete deletes an issue link type based on provided ID. // Update wraps UpdateWithContext using the background context.
func (s *IssueLinkTypeService) Update(linkType *IssueLinkType) (*IssueLinkType, *Response, error) {
return s.UpdateWithContext(context.Background(), linkType)
}
// DeleteWithContext deletes an issue link type based on provided ID.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issueLinkType-issueLinkTypeId-delete
func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) { func (s *IssueLinkTypeService) DeleteWithContext(ctx context.Context, ID string) (*Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID) apiEndpoint := fmt.Sprintf("rest/api/2/issueLinkType/%s", ID)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -109,3 +130,8 @@ func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) {
resp, err := s.client.Do(req, nil) resp, err := s.client.Do(req, nil)
return resp, err return resp, err
} }
// Delete wraps DeleteWithContext using the background context.
func (s *IssueLinkTypeService) Delete(ID string) (*Response, error) {
return s.DeleteWithContext(context.Background(), ID)
}

View File

@ -2,6 +2,7 @@ package jira
import ( import (
"bytes" "bytes"
"context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -14,7 +15,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/dgrijalva/jwt-go" "github.com/golang-jwt/jwt"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -25,7 +26,7 @@ type httpClient interface {
Do(request *http.Request) (response *http.Response, err error) Do(request *http.Request) (response *http.Response, err error)
} }
// A Client manages communication with the JIRA API. // A Client manages communication with the Jira API.
type Client struct { type Client struct {
// HTTP client used to communicate with the API. // HTTP client used to communicate with the API.
client httpClient client httpClient
@ -36,7 +37,7 @@ type Client struct {
// Session storage if the user authenticates with a Session cookie // Session storage if the user authenticates with a Session cookie
session *Session session *Session
// Services used for talking to different parts of the JIRA API. // Services used for talking to different parts of the Jira API.
Authentication *AuthenticationService Authentication *AuthenticationService
Issue *IssueService Issue *IssueService
Project *ProjectService Project *ProjectService
@ -55,15 +56,17 @@ type Client struct {
PermissionScheme *PermissionSchemeService PermissionScheme *PermissionSchemeService
Status *StatusService Status *StatusService
IssueLinkType *IssueLinkTypeService IssueLinkType *IssueLinkTypeService
Organization *OrganizationService
ServiceDesk *ServiceDeskService
} }
// NewClient returns a new JIRA API client. // NewClient returns a new Jira API client.
// If a nil httpClient is provided, http.DefaultClient will be used. // If a nil httpClient is provided, http.DefaultClient will be used.
// To use API methods which require authentication you can follow the preferred solution and // To use API methods which require authentication you can follow the preferred solution and
// provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library). // provide an http.Client that will perform the authentication for you with OAuth and HTTP Basic (such as that provided by the golang.org/x/oauth2 library).
// As an alternative you can use Session Cookie based authentication provided by this package as well. // As an alternative you can use Session Cookie based authentication provided by this package as well.
// See https://docs.atlassian.com/jira/REST/latest/#authentication // See https://docs.atlassian.com/jira/REST/latest/#authentication
// baseURL is the HTTP endpoint of your JIRA instance and should always be specified with a trailing slash. // baseURL is the HTTP endpoint of your Jira instance and should always be specified with a trailing slash.
func NewClient(httpClient httpClient, baseURL string) (*Client, error) { func NewClient(httpClient httpClient, baseURL string) (*Client, error) {
if httpClient == nil { if httpClient == nil {
httpClient = http.DefaultClient httpClient = http.DefaultClient
@ -101,14 +104,16 @@ func NewClient(httpClient httpClient, baseURL string) (*Client, error) {
c.PermissionScheme = &PermissionSchemeService{client: c} c.PermissionScheme = &PermissionSchemeService{client: c}
c.Status = &StatusService{client: c} c.Status = &StatusService{client: c}
c.IssueLinkType = &IssueLinkTypeService{client: c} c.IssueLinkType = &IssueLinkTypeService{client: c}
c.Organization = &OrganizationService{client: c}
c.ServiceDesk = &ServiceDeskService{client: c}
return c, nil return c, nil
} }
// NewRawRequest creates an API request. // NewRawRequestWithContext creates an API request.
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
// Allows using an optional native io.Reader for sourcing the request body. // Allows using an optional native io.Reader for sourcing the request body.
func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) { func (c *Client) NewRawRequestWithContext(ctx context.Context, method, urlStr string, body io.Reader) (*http.Request, error) {
rel, err := url.Parse(urlStr) rel, err := url.Parse(urlStr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -118,7 +123,7 @@ func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Req
u := c.baseURL.ResolveReference(rel) u := c.baseURL.ResolveReference(rel)
req, err := http.NewRequest(method, u.String(), body) req, err := newRequestWithContext(ctx, method, u.String(), body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -143,10 +148,15 @@ func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Req
return req, nil return req, nil
} }
// NewRequest creates an API request. // NewRawRequest wraps NewRawRequestWithContext using the background context.
func (c *Client) NewRawRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
return c.NewRawRequestWithContext(context.Background(), method, urlStr, body)
}
// NewRequestWithContext creates an API request.
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
// If specified, the value pointed to by body is JSON encoded and included as the request body. // If specified, the value pointed to by body is JSON encoded and included as the request body.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { func (c *Client) NewRequestWithContext(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(urlStr) rel, err := url.Parse(urlStr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -165,7 +175,7 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
} }
} }
req, err := http.NewRequest(method, u.String(), buf) req, err := newRequestWithContext(ctx, method, u.String(), buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -190,6 +200,11 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
return req, nil return req, nil
} }
// NewRequest wraps NewRequestWithContext using the background context.
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
return c.NewRequestWithContext(context.Background(), method, urlStr, body)
}
// addOptions adds the parameters in opt as URL query parameters to s. opt // addOptions adds the parameters in opt as URL query parameters to s. opt
// must be a struct whose fields may contain "url" tags. // must be a struct whose fields may contain "url" tags.
func addOptions(s string, opt interface{}) (string, error) { func addOptions(s string, opt interface{}) (string, error) {
@ -212,10 +227,10 @@ func addOptions(s string, opt interface{}) (string, error) {
return u.String(), nil return u.String(), nil
} }
// NewMultiPartRequest creates an API request including a multi-part file. // NewMultiPartRequestWithContext creates an API request including a multi-part file.
// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client.
// If specified, the value pointed to by buf is a multipart form. // If specified, the value pointed to by buf is a multipart form.
func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) { func (c *Client) NewMultiPartRequestWithContext(ctx context.Context, method, urlStr string, buf *bytes.Buffer) (*http.Request, error) {
rel, err := url.Parse(urlStr) rel, err := url.Parse(urlStr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -225,7 +240,7 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (
u := c.baseURL.ResolveReference(rel) u := c.baseURL.ResolveReference(rel)
req, err := http.NewRequest(method, u.String(), buf) req, err := newRequestWithContext(ctx, method, u.String(), buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -251,6 +266,11 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (
return req, nil return req, nil
} }
// NewMultiPartRequest wraps NewMultiPartRequestWithContext using the background context.
func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (*http.Request, error) {
return c.NewMultiPartRequestWithContext(context.Background(), method, urlStr, buf)
}
// Do sends an API request and returns the API response. // Do sends an API request and returns the API response.
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred. // The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
@ -279,13 +299,13 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
// CheckResponse checks the API response for errors, and returns them if present. // CheckResponse checks the API response for errors, and returns them if present.
// A response is considered an error if it has a status code outside the 200 range. // A response is considered an error if it has a status code outside the 200 range.
// The caller is responsible to analyze the response body. // The caller is responsible to analyze the response body.
// The body can contain JSON (if the error is intended) or xml (sometimes JIRA just failes). // The body can contain JSON (if the error is intended) or xml (sometimes Jira just failes).
func CheckResponse(r *http.Response) error { func CheckResponse(r *http.Response) error {
if c := r.StatusCode; 200 <= c && c <= 299 { if c := r.StatusCode; 200 <= c && c <= 299 {
return nil return nil
} }
err := fmt.Errorf("Request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode) err := fmt.Errorf("request failed. Please analyze the request body for more details. Status code: %d", r.StatusCode)
return err return err
} }
@ -295,7 +315,7 @@ func (c *Client) GetBaseURL() url.URL {
return *c.baseURL return *c.baseURL
} }
// Response represents JIRA API response. It wraps http.Response returned from // Response represents Jira API response. It wraps http.Response returned from
// API and provides information about paging. // API and provides information about paging.
type Response struct { type Response struct {
*http.Response *http.Response
@ -324,7 +344,6 @@ func (r *Response) populatePageValues(v interface{}) {
r.MaxResults = value.MaxResults r.MaxResults = value.MaxResults
r.Total = value.Total r.Total = value.Total
} }
return
} }
// BasicAuthTransport is an http.RoundTripper that authenticates all requests // BasicAuthTransport is an http.RoundTripper that authenticates all requests
@ -367,9 +386,9 @@ func (t *BasicAuthTransport) transport() http.RoundTripper {
// using Jira's cookie-based authentication. // using Jira's cookie-based authentication.
// //
// Note that it is generally preferable to use HTTP BASIC authentication with the REST API. // Note that it is generally preferable to use HTTP BASIC authentication with the REST API.
// However, this resource may be used to mimic the behaviour of JIRA's log-in page (e.g. to display log-in errors to a user). // However, this resource may be used to mimic the behaviour of Jira's log-in page (e.g. to display log-in errors to a user).
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#auth/1/session
type CookieAuthTransport struct { type CookieAuthTransport struct {
Username string Username string
Password string Password string
@ -464,7 +483,7 @@ func (t *CookieAuthTransport) transport() http.RoundTripper {
// //
// NOTE: this form of auth should be used by add-ons installed from the Atlassian marketplace. // NOTE: this form of auth should be used by add-ons installed from the Atlassian marketplace.
// //
// JIRA docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt // Jira docs: https://developer.atlassian.com/cloud/jira/platform/understanding-jwt
// Examples in other languages: // Examples in other languages:
// https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb // https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/d44a8e7a4649e4f23edaa784402655fda7c816ea/lib/atlassian/jwt.rb
// https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py // https://bitbucket.org/atlassian/atlassian-jwt-py/src/master/atlassian_jwt/url_utils.py

View File

@ -1,6 +1,7 @@
package jira package jira
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
@ -14,6 +15,11 @@ type CreateMetaInfo struct {
Projects []*MetaProject `json:"projects,omitempty"` Projects []*MetaProject `json:"projects,omitempty"`
} }
// EditMetaInfo contains information about fields and their attributed to edit a ticket.
type EditMetaInfo struct {
Fields tcontainer.MarshalMap `json:"fields,omitempty"`
}
// MetaProject is the meta information about a project returned from createmeta api // MetaProject is the meta information about a project returned from createmeta api
type MetaProject struct { type MetaProject struct {
Expand string `json:"expand,omitempty"` Expand string `json:"expand,omitempty"`
@ -42,16 +48,21 @@ type MetaIssueType struct {
Fields tcontainer.MarshalMap `json:"fields,omitempty"` Fields tcontainer.MarshalMap `json:"fields,omitempty"`
} }
// GetCreateMeta makes the api call to get the meta information required to create a ticket // GetCreateMetaWithContext makes the api call to get the meta information required to create a ticket
func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) { func (s *IssueService) GetCreateMetaWithContext(ctx context.Context, projectkeys string) (*CreateMetaInfo, *Response, error) {
return s.GetCreateMetaWithOptions(&GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"}) return s.GetCreateMetaWithOptionsWithContext(ctx, &GetQueryOptions{ProjectKeys: projectkeys, Expand: "projects.issuetypes.fields"})
} }
// GetCreateMetaWithOptions makes the api call to get the meta information without requiring to have a projectKey // GetCreateMeta wraps GetCreateMetaWithContext using the background context.
func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) { func (s *IssueService) GetCreateMeta(projectkeys string) (*CreateMetaInfo, *Response, error) {
return s.GetCreateMetaWithContext(context.Background(), projectkeys)
}
// GetCreateMetaWithOptionsWithContext makes the api call to get the meta information without requiring to have a projectKey
func (s *IssueService) GetCreateMetaWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*CreateMetaInfo, *Response, error) {
apiEndpoint := "rest/api/2/issue/createmeta" apiEndpoint := "rest/api/2/issue/createmeta"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -73,11 +84,40 @@ func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*Crea
return meta, resp, nil return meta, resp, nil
} }
// GetCreateMetaWithOptions wraps GetCreateMetaWithOptionsWithContext using the background context.
func (s *IssueService) GetCreateMetaWithOptions(options *GetQueryOptions) (*CreateMetaInfo, *Response, error) {
return s.GetCreateMetaWithOptionsWithContext(context.Background(), options)
}
// GetEditMetaWithContext makes the api call to get the edit meta information for an issue
func (s *IssueService) GetEditMetaWithContext(ctx context.Context, issue *Issue) (*EditMetaInfo, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/issue/%s/editmeta", issue.Key)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
meta := new(EditMetaInfo)
resp, err := s.client.Do(req, meta)
if err != nil {
return nil, resp, err
}
return meta, resp, nil
}
// GetEditMeta wraps GetEditMetaWithContext using the background context.
func (s *IssueService) GetEditMeta(issue *Issue) (*EditMetaInfo, *Response, error) {
return s.GetEditMetaWithContext(context.Background(), issue)
}
// GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil. // GetProjectWithName returns a project with "name" from the meta information received. If not found, this returns nil.
// The comparison of the name is case insensitive. // The comparison of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject { func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject {
for _, m := range m.Projects { for _, m := range m.Projects {
if strings.ToLower(m.Name) == strings.ToLower(name) { if strings.EqualFold(m.Name, name) {
return m return m
} }
} }
@ -88,7 +128,7 @@ func (m *CreateMetaInfo) GetProjectWithName(name string) *MetaProject {
// The comparison of the name is case insensitive. // The comparison of the name is case insensitive.
func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject { func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject {
for _, m := range m.Projects { for _, m := range m.Projects {
if strings.ToLower(m.Key) == strings.ToLower(key) { if strings.EqualFold(m.Key, key) {
return m return m
} }
} }
@ -99,7 +139,7 @@ func (m *CreateMetaInfo) GetProjectWithKey(key string) *MetaProject {
// The comparison of the name is case insensitive // The comparison of the name is case insensitive
func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType { func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueType {
for _, m := range p.IssueTypes { for _, m := range p.IssueTypes {
if strings.ToLower(m.Name) == strings.ToLower(name) { if strings.EqualFold(m.Name, name) {
return m return m
} }
} }
@ -175,7 +215,7 @@ func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (boo
for name := range mandatory { for name := range mandatory {
requiredFields = append(requiredFields, name) requiredFields = append(requiredFields, name)
} }
return false, fmt.Errorf("Required field not found in provided jira.fields. Required are: %#v", requiredFields) return false, fmt.Errorf("required field not found in provided jira.fields. Required are: %#v", requiredFields)
} }
} }
@ -186,7 +226,7 @@ func (t *MetaIssueType) CheckCompleteAndAvailable(config map[string]string) (boo
for name := range all { for name := range all {
availableFields = append(availableFields, name) availableFields = append(availableFields, name)
} }
return false, fmt.Errorf("Fields in jira.fields are not available in jira. Available are: %#v", availableFields) return false, fmt.Errorf("fields in jira.fields are not available in jira. Available are: %#v", availableFields)
} }
} }

387
vendor/github.com/andygrunwald/go-jira/organization.go generated vendored Normal file
View File

@ -0,0 +1,387 @@
package jira
import (
"context"
"fmt"
)
// OrganizationService handles Organizations for the Jira instance / API.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/
type OrganizationService struct {
client *Client
}
// OrganizationCreationDTO is DTO for creat organization API
type OrganizationCreationDTO struct {
Name string `json:"name,omitempty" structs:"name,omitempty"`
}
// SelfLink Stores REST API URL to the organization.
type SelfLink struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
}
// Organization contains Organization data
type Organization struct {
ID string `json:"id,omitempty" structs:"id,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
}
// OrganizationUsersDTO contains organization user ids
type OrganizationUsersDTO struct {
AccountIds []string `json:"accountIds,omitempty" structs:"accountIds,omitempty"`
}
// PagedDTO is response of a paged list
type PagedDTO struct {
Size int `json:"size,omitempty" structs:"size,omitempty"`
Start int `json:"start,omitempty" structs:"start,omitempty"`
Limit int `limit:"size,omitempty" structs:"limit,omitempty"`
IsLastPage bool `json:"isLastPage,omitempty" structs:"isLastPage,omitempty"`
Values []interface{} `values:"isLastPage,omitempty" structs:"values,omitempty"`
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
}
// PropertyKey contains Property key details.
type PropertyKey struct {
Self string `json:"self,omitempty" structs:"self,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
}
// PropertyKeys contains an array of PropertyKey
type PropertyKeys struct {
Keys []PropertyKey `json:"keys,omitempty" structs:"keys,omitempty"`
}
// GetAllOrganizationsWithContext returns a list of organizations in
// the Jira Service Management instance.
// Use this method when you want to present a list
// of organizations or want to locate an organization
// by name.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-group-organization
func (s *OrganizationService) GetAllOrganizationsWithContext(ctx context.Context, start int, limit int, accountID string) (*PagedDTO, *Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization?start=%d&limit=%d", start, limit)
if accountID != "" {
apiEndPoint += fmt.Sprintf("&accountId=%s", accountID)
}
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, nil, err
}
v := new(PagedDTO)
resp, err := s.client.Do(req, v)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return v, resp, nil
}
// GetAllOrganizations wraps GetAllOrganizationsWithContext using the background context.
func (s *OrganizationService) GetAllOrganizations(start int, limit int, accountID string) (*PagedDTO, *Response, error) {
return s.GetAllOrganizationsWithContext(context.Background(), start, limit, accountID)
}
// CreateOrganizationWithContext creates an organization by
// passing the name of the organization.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-post
func (s *OrganizationService) CreateOrganizationWithContext(ctx context.Context, name string) (*Organization, *Response, error) {
apiEndPoint := "rest/servicedeskapi/organization"
organization := OrganizationCreationDTO{
Name: name,
}
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, nil, err
}
o := new(Organization)
resp, err := s.client.Do(req, &o)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return o, resp, nil
}
// CreateOrganization wraps CreateOrganizationWithContext using the background context.
func (s *OrganizationService) CreateOrganization(name string) (*Organization, *Response, error) {
return s.CreateOrganizationWithContext(context.Background(), name)
}
// GetOrganizationWithContext returns details of an
// organization. Use this method to get organization
// details whenever your application component is
// passed an organization ID but needs to display
// other organization details.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-get
func (s *OrganizationService) GetOrganizationWithContext(ctx context.Context, organizationID int) (*Organization, *Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, nil, err
}
o := new(Organization)
resp, err := s.client.Do(req, &o)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return o, resp, nil
}
// GetOrganization wraps GetOrganizationWithContext using the background context.
func (s *OrganizationService) GetOrganization(organizationID int) (*Organization, *Response, error) {
return s.GetOrganizationWithContext(context.Background(), organizationID)
}
// DeleteOrganizationWithContext deletes an organization. Note that
// the organization is deleted regardless
// of other associations it may have.
// For example, associations with service desks.
//
// Jira API docs: https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-delete
func (s *OrganizationService) DeleteOrganizationWithContext(ctx context.Context, organizationID int) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d", organizationID)
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// DeleteOrganization wraps DeleteOrganizationWithContext using the background context.
func (s *OrganizationService) DeleteOrganization(organizationID int) (*Response, error) {
return s.DeleteOrganizationWithContext(context.Background(), organizationID)
}
// GetPropertiesKeysWithContext returns the keys of
// all properties for an organization. Use this resource
// when you need to find out what additional properties
// items have been added to an organization.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-get
func (s *OrganizationService) GetPropertiesKeysWithContext(ctx context.Context, organizationID int) (*PropertyKeys, *Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property", organizationID)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, nil, err
}
pk := new(PropertyKeys)
resp, err := s.client.Do(req, &pk)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return pk, resp, nil
}
// GetPropertiesKeys wraps GetPropertiesKeysWithContext using the background context.
func (s *OrganizationService) GetPropertiesKeys(organizationID int) (*PropertyKeys, *Response, error) {
return s.GetPropertiesKeysWithContext(context.Background(), organizationID)
}
// GetPropertyWithContext returns the value of a property
// from an organization. Use this method to obtain the JSON
// content for an organization's property.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-get
func (s *OrganizationService) GetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*EntityProperty, *Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, nil, err
}
ep := new(EntityProperty)
resp, err := s.client.Do(req, &ep)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return ep, resp, nil
}
// GetProperty wraps GetPropertyWithContext using the background context.
func (s *OrganizationService) GetProperty(organizationID int, propertyKey string) (*EntityProperty, *Response, error) {
return s.GetPropertyWithContext(context.Background(), organizationID, propertyKey)
}
// SetPropertyWithContext sets the value of a
// property for an organization. Use this
// resource to store custom data against an organization.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-put
func (s *OrganizationService) SetPropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey)
req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// SetProperty wraps SetPropertyWithContext using the background context.
func (s *OrganizationService) SetProperty(organizationID int, propertyKey string) (*Response, error) {
return s.SetPropertyWithContext(context.Background(), organizationID, propertyKey)
}
// DeletePropertyWithContext removes a property from an organization.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-property-propertykey-delete
func (s *OrganizationService) DeletePropertyWithContext(ctx context.Context, organizationID int, propertyKey string) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/property/%s", organizationID, propertyKey)
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// DeleteProperty wraps DeletePropertyWithContext using the background context.
func (s *OrganizationService) DeleteProperty(organizationID int, propertyKey string) (*Response, error) {
return s.DeletePropertyWithContext(context.Background(), organizationID, propertyKey)
}
// GetUsersWithContext returns all the users
// associated with an organization. Use this
// method where you want to provide a list of
// users for an organization or determine if
// a user is associated with an organization.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-get
func (s *OrganizationService) GetUsersWithContext(ctx context.Context, organizationID int, start int, limit int) (*PagedDTO, *Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user?start=%d&limit=%d", organizationID, start, limit)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, nil, err
}
users := new(PagedDTO)
resp, err := s.client.Do(req, &users)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return users, resp, nil
}
// GetUsers wraps GetUsersWithContext using the background context.
func (s *OrganizationService) GetUsers(organizationID int, start int, limit int) (*PagedDTO, *Response, error) {
return s.GetUsersWithContext(context.Background(), organizationID, start, limit)
}
// AddUsersWithContext adds users to an organization.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-post
func (s *OrganizationService) AddUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID)
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, users)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// AddUsers wraps AddUsersWithContext using the background context.
func (s *OrganizationService) AddUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) {
return s.AddUsersWithContext(context.Background(), organizationID, users)
}
// RemoveUsersWithContext removes users from an organization.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-organizationid-user-delete
func (s *OrganizationService) RemoveUsersWithContext(ctx context.Context, organizationID int, users OrganizationUsersDTO) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/organization/%d/user", organizationID)
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// RemoveUsers wraps RemoveUsersWithContext using the background context.
func (s *OrganizationService) RemoveUsers(organizationID int, users OrganizationUsersDTO) (*Response, error) {
return s.RemoveUsersWithContext(context.Background(), organizationID, users)
}

View File

@ -1,10 +1,13 @@
package jira package jira
import "fmt" import (
"context"
"fmt"
)
// PermissionSchemeService handles permissionschemes for the JIRA instance / API. // PermissionSchemeService handles permissionschemes for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Permissionscheme // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Permissionscheme
type PermissionSchemeService struct { type PermissionSchemeService struct {
client *Client client *Client
} }
@ -25,12 +28,12 @@ type Holder struct {
Expand string `json:"expand" structs:"expand"` Expand string `json:"expand" structs:"expand"`
} }
// GetList returns a list of all permission schemes // GetListWithContext returns a list of all permission schemes
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-get
func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, error) { func (s *PermissionSchemeService) GetListWithContext(ctx context.Context) (*PermissionSchemes, *Response, error) {
apiEndpoint := "/rest/api/3/permissionscheme" apiEndpoint := "/rest/api/3/permissionscheme"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -45,12 +48,17 @@ func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, erro
return pss, resp, nil return pss, resp, nil
} }
// Get returns a full representation of the permission scheme for the schemeID // GetList wraps GetListWithContext using the background context.
func (s *PermissionSchemeService) GetList() (*PermissionSchemes, *Response, error) {
return s.GetListWithContext(context.Background())
}
// GetWithContext returns a full representation of the permission scheme for the schemeID
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-permissionscheme-schemeId-get
func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Response, error) { func (s *PermissionSchemeService) GetWithContext(ctx context.Context, schemeID int) (*PermissionScheme, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID) apiEndpoint := fmt.Sprintf("/rest/api/3/permissionscheme/%d", schemeID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -62,8 +70,13 @@ func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Respons
return nil, resp, jerr return nil, resp, jerr
} }
if ps.Self == "" { if ps.Self == "" {
return nil, resp, fmt.Errorf("No permissionscheme with ID %d found", schemeID) return nil, resp, fmt.Errorf("no permissionscheme with ID %d found", schemeID)
} }
return ps, resp, nil return ps, resp, nil
} }
// Get wraps GetWithContext using the background context.
func (s *PermissionSchemeService) Get(schemeID int) (*PermissionScheme, *Response, error) {
return s.GetWithContext(context.Background(), schemeID)
}

View File

@ -1,13 +1,15 @@
package jira package jira
// PriorityService handles priorities for the JIRA instance / API. import "context"
// PriorityService handles priorities for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Priority
type PriorityService struct { type PriorityService struct {
client *Client client *Client
} }
// Priority represents a priority of a JIRA issue. // Priority represents a priority of a Jira issue.
// Typical types are "Normal", "Moderate", "Urgent", ... // Typical types are "Normal", "Moderate", "Urgent", ...
type Priority struct { type Priority struct {
Self string `json:"self,omitempty" structs:"self,omitempty"` Self string `json:"self,omitempty" structs:"self,omitempty"`
@ -18,12 +20,12 @@ type Priority struct {
Description string `json:"description,omitempty" structs:"description,omitempty"` Description string `json:"description,omitempty" structs:"description,omitempty"`
} }
// GetList gets all priorities from JIRA // GetListWithContext gets all priorities from Jira
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-priority-get
func (s *PriorityService) GetList() ([]Priority, *Response, error) { func (s *PriorityService) GetListWithContext(ctx context.Context) ([]Priority, *Response, error) {
apiEndpoint := "rest/api/2/priority" apiEndpoint := "rest/api/2/priority"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -35,3 +37,8 @@ func (s *PriorityService) GetList() ([]Priority, *Response, error) {
} }
return priorityList, resp, nil return priorityList, resp, nil
} }
// GetList wraps GetListWithContext using the background context.
func (s *PriorityService) GetList() ([]Priority, *Response, error) {
return s.GetListWithContext(context.Background())
}

View File

@ -1,14 +1,15 @@
package jira package jira
import ( import (
"context"
"fmt" "fmt"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
) )
// ProjectService handles projects for the JIRA instance / API. // ProjectService handles projects for the Jira instance / API.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project
type ProjectService struct { type ProjectService struct {
client *Client client *Client
} }
@ -34,7 +35,7 @@ type ProjectCategory struct {
Description string `json:"description" structs:"description,omitempty"` Description string `json:"description" structs:"description,omitempty"`
} }
// Project represents a JIRA Project. // Project represents a Jira Project.
type Project struct { type Project struct {
Expand string `json:"expand,omitempty" structs:"expand,omitempty"` Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"` Self string `json:"self,omitempty" structs:"self,omitempty"`
@ -80,20 +81,25 @@ type PermissionScheme struct {
Permissions []Permission `json:"permissions" structs:"permissions,omitempty"` Permissions []Permission `json:"permissions" structs:"permissions,omitempty"`
} }
// GetList gets all projects form JIRA // GetListWithContext gets all projects form Jira
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
func (s *ProjectService) GetList() (*ProjectList, *Response, error) { func (s *ProjectService) GetListWithContext(ctx context.Context) (*ProjectList, *Response, error) {
return s.ListWithOptions(&GetQueryOptions{}) return s.ListWithOptionsWithContext(ctx, &GetQueryOptions{})
} }
// ListWithOptions gets all projects form JIRA with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get // GetList wraps GetListWithContext using the background context.
func (s *ProjectService) GetList() (*ProjectList, *Response, error) {
return s.GetListWithContext(context.Background())
}
// ListWithOptionsWithContext gets all projects form Jira with optional query params, like &GetQueryOptions{Expand: "issueTypes"} to get
// a list of all projects and their supported issuetypes // a list of all projects and their supported issuetypes
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) { func (s *ProjectService) ListWithOptionsWithContext(ctx context.Context, options *GetQueryOptions) (*ProjectList, *Response, error) {
apiEndpoint := "rest/api/2/project" apiEndpoint := "rest/api/2/project"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -116,14 +122,19 @@ func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList
return projectList, resp, nil return projectList, resp, nil
} }
// Get returns a full representation of the project for the given issue key. // ListWithOptions wraps ListWithOptionsWithContext using the background context.
// JIRA will attempt to identify the project by the projectIdOrKey path parameter. func (s *ProjectService) ListWithOptions(options *GetQueryOptions) (*ProjectList, *Response, error) {
return s.ListWithOptionsWithContext(context.Background(), options)
}
// GetWithContext returns a full representation of the project for the given issue key.
// Jira will attempt to identify the project by the projectIdOrKey path parameter.
// This can be an project id, or an project key. // This can be an project id, or an project key.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
func (s *ProjectService) Get(projectID string) (*Project, *Response, error) { func (s *ProjectService) GetWithContext(ctx context.Context, projectID string) (*Project, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID) apiEndpoint := fmt.Sprintf("rest/api/2/project/%s", projectID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -138,14 +149,19 @@ func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
return project, resp, nil return project, resp, nil
} }
// GetPermissionScheme returns a full representation of the permission scheme for the project // Get wraps GetWithContext using the background context.
// JIRA will attempt to identify the project by the projectIdOrKey path parameter. func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
return s.GetWithContext(context.Background(), projectID)
}
// GetPermissionSchemeWithContext returns a full representation of the permission scheme for the project
// Jira will attempt to identify the project by the projectIdOrKey path parameter.
// This can be an project id, or an project key. // This can be an project id, or an project key.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) { func (s *ProjectService) GetPermissionSchemeWithContext(ctx context.Context, projectID string) (*PermissionScheme, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID) apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s/permissionscheme", projectID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -159,3 +175,8 @@ func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionSchem
return ps, resp, nil return ps, resp, nil
} }
// GetPermissionScheme wraps GetPermissionSchemeWithContext using the background context.
func (s *ProjectService) GetPermissionScheme(projectID string) (*PermissionScheme, *Response, error) {
return s.GetPermissionSchemeWithContext(context.Background(), projectID)
}

View File

@ -0,0 +1,23 @@
// +build go1.13
// This file provides glue to use Context in `http.Request` with
// Go version 1.13 and higher.
// The function `http.NewRequestWithContext` has been added in Go 1.13.
// Before the release 1.13, to use Context we need creat `http.Request`
// then use the method `WithContext` to create a new `http.Request`
// with Context from the existing `http.Request`.
//
// Doc: https://golang.org/doc/go1.13#net/http
package jira
import (
"context"
"io"
"net/http"
)
func newRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) {
return http.NewRequestWithContext(ctx, method, url, body)
}

View File

@ -0,0 +1,28 @@
// +build !go1.13
// This file provides glue to use Context in `http.Request` with
// Go version before 1.13.
// The function `http.NewRequestWithContext` has been added in Go 1.13.
// Before the release 1.13, to use Context we need creat `http.Request`
// then use the method `WithContext` to create a new `http.Request`
// with Context from the existing `http.Request`.
//
// Doc: https://golang.org/doc/go1.13#net/http
package jira
import (
"context"
"io"
"net/http"
)
func newRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) {
r, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
return r.WithContext(ctx), nil
}

View File

@ -1,13 +1,15 @@
package jira package jira
// ResolutionService handles resolutions for the JIRA instance / API. import "context"
// ResolutionService handles resolutions for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Resolution
type ResolutionService struct { type ResolutionService struct {
client *Client client *Client
} }
// Resolution represents a resolution of a JIRA issue. // Resolution represents a resolution of a Jira issue.
// Typical types are "Fixed", "Suspended", "Won't Fix", ... // Typical types are "Fixed", "Suspended", "Won't Fix", ...
type Resolution struct { type Resolution struct {
Self string `json:"self" structs:"self"` Self string `json:"self" structs:"self"`
@ -16,12 +18,12 @@ type Resolution struct {
Name string `json:"name" structs:"name"` Name string `json:"name" structs:"name"`
} }
// GetList gets all resolutions from JIRA // GetListWithContext gets all resolutions from Jira
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-resolution-get
func (s *ResolutionService) GetList() ([]Resolution, *Response, error) { func (s *ResolutionService) GetListWithContext(ctx context.Context) ([]Resolution, *Response, error) {
apiEndpoint := "rest/api/2/resolution" apiEndpoint := "rest/api/2/resolution"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -33,3 +35,8 @@ func (s *ResolutionService) GetList() ([]Resolution, *Response, error) {
} }
return resolutionList, resp, nil return resolutionList, resp, nil
} }
// GetList wraps GetListWithContext using the background context.
func (s *ResolutionService) GetList() ([]Resolution, *Response, error) {
return s.GetListWithContext(context.Background())
}

View File

@ -1,17 +1,18 @@
package jira package jira
import ( import (
"context"
"fmt" "fmt"
) )
// RoleService handles roles for the JIRA instance / API. // RoleService handles roles for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-group-Role
type RoleService struct { type RoleService struct {
client *Client client *Client
} }
// Role represents a JIRA product role // Role represents a Jira product role
type Role struct { type Role struct {
Self string `json:"self" structs:"self"` Self string `json:"self" structs:"self"`
Name string `json:"name" structs:"name"` Name string `json:"name" structs:"name"`
@ -20,7 +21,7 @@ type Role struct {
Actors []*Actor `json:"actors" structs:"actors"` Actors []*Actor `json:"actors" structs:"actors"`
} }
// Actor represents a JIRA actor // Actor represents a Jira actor
type Actor struct { type Actor struct {
ID int `json:"id" structs:"id"` ID int `json:"id" structs:"id"`
DisplayName string `json:"displayName" structs:"displayName"` DisplayName string `json:"displayName" structs:"displayName"`
@ -35,12 +36,12 @@ type ActorUser struct {
AccountID string `json:"accountId" structs:"accountId"` AccountID string `json:"accountId" structs:"accountId"`
} }
// GetList returns a list of all available project roles // GetListWithContext returns a list of all available project roles
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-get
func (s *RoleService) GetList() (*[]Role, *Response, error) { func (s *RoleService) GetListWithContext(ctx context.Context) (*[]Role, *Response, error) {
apiEndpoint := "rest/api/3/role" apiEndpoint := "rest/api/3/role"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -53,12 +54,17 @@ func (s *RoleService) GetList() (*[]Role, *Response, error) {
return roles, resp, err return roles, resp, err
} }
// Get retreives a single Role from Jira // GetList wraps GetListWithContext using the background context.
func (s *RoleService) GetList() (*[]Role, *Response, error) {
return s.GetListWithContext(context.Background())
}
// GetWithContext retreives a single Role from Jira
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-api-3-role-id-get
func (s *RoleService) Get(roleID int) (*Role, *Response, error) { func (s *RoleService) GetWithContext(ctx context.Context, roleID int) (*Role, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID) apiEndpoint := fmt.Sprintf("rest/api/3/role/%d", roleID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -69,8 +75,13 @@ func (s *RoleService) Get(roleID int) (*Role, *Response, error) {
return nil, resp, jerr return nil, resp, jerr
} }
if role.Self == "" { if role.Self == "" {
return nil, resp, fmt.Errorf("No role with ID %d found", roleID) return nil, resp, fmt.Errorf("no role with ID %d found", roleID)
} }
return role, resp, err return role, resp, err
} }
// Get wraps GetWithContext using the background context.
func (s *RoleService) Get(roleID int) (*Role, *Response, error) {
return s.GetWithContext(context.Background(), roleID)
}

114
vendor/github.com/andygrunwald/go-jira/servicedesk.go generated vendored Normal file
View File

@ -0,0 +1,114 @@
package jira
import (
"context"
"fmt"
)
// ServiceDeskService handles ServiceDesk for the Jira instance / API.
type ServiceDeskService struct {
client *Client
}
// ServiceDeskOrganizationDTO is a DTO for ServiceDesk organizations
type ServiceDeskOrganizationDTO struct {
OrganizationID int `json:"organizationId,omitempty" structs:"organizationId,omitempty"`
}
// GetOrganizationsWithContext returns a list of
// all organizations associated with a service desk.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-get
func (s *ServiceDeskService) GetOrganizationsWithContext(ctx context.Context, serviceDeskID int, start int, limit int, accountID string) (*PagedDTO, *Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%d/organization?start=%d&limit=%d", serviceDeskID, start, limit)
if accountID != "" {
apiEndPoint += fmt.Sprintf("&accountId=%s", accountID)
}
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndPoint, nil)
req.Header.Set("Accept", "application/json")
if err != nil {
return nil, nil, err
}
orgs := new(PagedDTO)
resp, err := s.client.Do(req, &orgs)
if err != nil {
jerr := NewJiraError(resp, err)
return nil, resp, jerr
}
return orgs, resp, nil
}
// GetOrganizations wraps GetOrganizationsWithContext using the background context.
func (s *ServiceDeskService) GetOrganizations(serviceDeskID int, start int, limit int, accountID string) (*PagedDTO, *Response, error) {
return s.GetOrganizationsWithContext(context.Background(), serviceDeskID, start, limit, accountID)
}
// AddOrganizationWithContext adds an organization to
// a service desk. If the organization ID is already
// associated with the service desk, no change is made
// and the resource returns a 204 success code.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-post
func (s *ServiceDeskService) AddOrganizationWithContext(ctx context.Context, serviceDeskID int, organizationID int) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%d/organization", serviceDeskID)
organization := ServiceDeskOrganizationDTO{
OrganizationID: organizationID,
}
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndPoint, organization)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// AddOrganization wraps AddOrganizationWithContext using the background context.
func (s *ServiceDeskService) AddOrganization(serviceDeskID int, organizationID int) (*Response, error) {
return s.AddOrganizationWithContext(context.Background(), serviceDeskID, organizationID)
}
// RemoveOrganizationWithContext removes an organization
// from a service desk. If the organization ID does not
// match an organization associated with the service desk,
// no change is made and the resource returns a 204 success code.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-servicedesk-servicedeskid-organization-delete
func (s *ServiceDeskService) RemoveOrganizationWithContext(ctx context.Context, serviceDeskID int, organizationID int) (*Response, error) {
apiEndPoint := fmt.Sprintf("rest/servicedeskapi/servicedesk/%d/organization", serviceDeskID)
organization := ServiceDeskOrganizationDTO{
OrganizationID: organizationID,
}
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndPoint, organization)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
if err != nil {
jerr := NewJiraError(resp, err)
return resp, jerr
}
return resp, nil
}
// RemoveOrganization wraps RemoveOrganizationWithContext using the background context.
func (s *ServiceDeskService) RemoveOrganization(serviceDeskID int, organizationID int) (*Response, error) {
return s.RemoveOrganizationWithContext(context.Background(), serviceDeskID, organizationID)
}

View File

@ -1,12 +1,13 @@
package jira package jira
import ( import (
"context"
"fmt" "fmt"
"github.com/google/go-querystring/query" "github.com/google/go-querystring/query"
) )
// SprintService handles sprints in JIRA Agile API. // SprintService handles sprints in Jira Agile API.
// See https://docs.atlassian.com/jira-software/REST/cloud/ // See https://docs.atlassian.com/jira-software/REST/cloud/
type SprintService struct { type SprintService struct {
client *Client client *Client
@ -22,17 +23,17 @@ type IssuesInSprintResult struct {
Issues []Issue `json:"issues"` Issues []Issue `json:"issues"`
} }
// MoveIssuesToSprint moves issues to a sprint, for a given sprint Id. // MoveIssuesToSprintWithContext moves issues to a sprint, for a given sprint Id.
// Issues can only be moved to open or active sprints. // Issues can only be moved to open or active sprints.
// The maximum number of issues that can be moved in one operation is 50. // The maximum number of issues that can be moved in one operation is 50.
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-moveIssuesToSprint
func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) { func (s *SprintService) MoveIssuesToSprintWithContext(ctx context.Context, sprintID int, issueIDs []string) (*Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID)
payload := IssuesWrapper{Issues: issueIDs} payload := IssuesWrapper{Issues: issueIDs}
req, err := s.client.NewRequest("POST", apiEndpoint, payload) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload)
if err != nil { if err != nil {
return nil, err return nil, err
@ -45,15 +46,20 @@ func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Re
return resp, err return resp, err
} }
// GetIssuesForSprint returns all issues in a sprint, for a given sprint Id. // MoveIssuesToSprint wraps MoveIssuesToSprintWithContext using the background context.
func (s *SprintService) MoveIssuesToSprint(sprintID int, issueIDs []string) (*Response, error) {
return s.MoveIssuesToSprintWithContext(context.Background(), sprintID, issueIDs)
}
// GetIssuesForSprintWithContext returns all issues in a sprint, for a given sprint Id.
// This only includes issues that the user has permission to view. // This only includes issues that the user has permission to view.
// By default, the returned issues are ordered by rank. // By default, the returned issues are ordered by rank.
// //
// JIRA API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint // Jira API Docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/sprint-getIssuesForSprint
func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) { func (s *SprintService) GetIssuesForSprintWithContext(ctx context.Context, sprintID int) ([]Issue, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID) apiEndpoint := fmt.Sprintf("rest/agile/1.0/sprint/%d/issue", sprintID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -68,20 +74,25 @@ func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, er
return result.Issues, resp, err return result.Issues, resp, err
} }
// GetIssue returns a full representation of the issue for the given issue key. // GetIssuesForSprint wraps GetIssuesForSprintWithContext using the background context.
// JIRA will attempt to identify the issue by the issueIdOrKey path parameter. func (s *SprintService) GetIssuesForSprint(sprintID int) ([]Issue, *Response, error) {
return s.GetIssuesForSprintWithContext(context.Background(), sprintID)
}
// GetIssueWithContext returns a full representation of the issue for the given issue key.
// Jira will attempt to identify the issue by the issueIdOrKey path parameter.
// This can be an issue id, or an issue key. // This can be an issue id, or an issue key.
// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved. // If the issue cannot be found via an exact match, Jira will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
// //
// The given options will be appended to the query string // The given options will be appended to the query string
// //
// JIRA API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue // Jira API docs: https://docs.atlassian.com/jira-software/REST/7.3.1/#agile/1.0/issue-getIssue
// //
// TODO: create agile service for holding all agile apis' implementation // TODO: create agile service for holding all agile apis' implementation
func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) { func (s *SprintService) GetIssueWithContext(ctx context.Context, issueID string, options *GetQueryOptions) (*Issue, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID) apiEndpoint := fmt.Sprintf("rest/agile/1.0/issue/%s", issueID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -105,3 +116,8 @@ func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Iss
return issue, resp, nil return issue, resp, nil
} }
// GetIssue wraps GetIssueWithContext using the background context.
func (s *SprintService) GetIssue(issueID string, options *GetQueryOptions) (*Issue, *Response, error) {
return s.GetIssueWithContext(context.Background(), issueID, options)
}

View File

@ -1,15 +1,17 @@
package jira package jira
// StatusService handles staties for the JIRA instance / API. import "context"
// StatusService handles staties for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Workflow-statuses // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Workflow-statuses
type StatusService struct { type StatusService struct {
client *Client client *Client
} }
// Status represents the current status of a JIRA issue. // Status represents the current status of a Jira issue.
// Typical status are "Open", "In Progress", "Closed", ... // Typical status are "Open", "In Progress", "Closed", ...
// Status can be user defined in every JIRA instance. // Status can be user defined in every Jira instance.
type Status struct { type Status struct {
Self string `json:"self" structs:"self"` Self string `json:"self" structs:"self"`
Description string `json:"description" structs:"description"` Description string `json:"description" structs:"description"`
@ -19,12 +21,12 @@ type Status struct {
StatusCategory StatusCategory `json:"statusCategory" structs:"statusCategory"` StatusCategory StatusCategory `json:"statusCategory" structs:"statusCategory"`
} }
// GetAllStatuses returns a list of all statuses associated with workflows. // GetAllStatusesWithContext returns a list of all statuses associated with workflows.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-status-get
func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) { func (s *StatusService) GetAllStatusesWithContext(ctx context.Context) ([]Status, *Response, error) {
apiEndpoint := "rest/api/2/status" apiEndpoint := "rest/api/2/status"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -38,3 +40,8 @@ func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) {
return statusList, resp, nil return statusList, resp, nil
} }
// GetAllStatuses wraps GetAllStatusesWithContext using the background context.
func (s *StatusService) GetAllStatuses() ([]Status, *Response, error) {
return s.GetAllStatusesWithContext(context.Background())
}

View File

@ -1,14 +1,16 @@
package jira package jira
// StatusCategoryService handles status categories for the JIRA instance / API. import "context"
// StatusCategoryService handles status categories for the Jira instance / API.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Statuscategory
type StatusCategoryService struct { type StatusCategoryService struct {
client *Client client *Client
} }
// StatusCategory represents the category a status belongs to. // StatusCategory represents the category a status belongs to.
// Those categories can be user defined in every JIRA instance. // Those categories can be user defined in every Jira instance.
type StatusCategory struct { type StatusCategory struct {
Self string `json:"self" structs:"self"` Self string `json:"self" structs:"self"`
ID int `json:"id" structs:"id"` ID int `json:"id" structs:"id"`
@ -17,7 +19,7 @@ type StatusCategory struct {
ColorName string `json:"colorName" structs:"colorName"` ColorName string `json:"colorName" structs:"colorName"`
} }
// These constants are the keys of the default JIRA status categories // These constants are the keys of the default Jira status categories
const ( const (
StatusCategoryComplete = "done" StatusCategoryComplete = "done"
StatusCategoryInProgress = "indeterminate" StatusCategoryInProgress = "indeterminate"
@ -25,12 +27,12 @@ const (
StatusCategoryUndefined = "undefined" StatusCategoryUndefined = "undefined"
) )
// GetList gets all status categories from JIRA // GetListWithContext gets all status categories from Jira
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-statuscategory-get
func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) { func (s *StatusCategoryService) GetListWithContext(ctx context.Context) ([]StatusCategory, *Response, error) {
apiEndpoint := "rest/api/2/statuscategory" apiEndpoint := "rest/api/2/statuscategory"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -42,3 +44,8 @@ func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) {
} }
return statusCategoryList, resp, nil return statusCategoryList, resp, nil
} }
// GetList wraps GetListWithContext using the background context.
func (s *StatusCategoryService) GetList() ([]StatusCategory, *Response, error) {
return s.GetListWithContext(context.Background())
}

9
vendor/github.com/andygrunwald/go-jira/types.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package jira
// Bool is a helper routine that allocates a new bool value
// to store v and returns a pointer to it.
func Bool(v bool) *bool {
p := new(bool)
*p = v
return p
}

View File

@ -1,25 +1,24 @@
package jira package jira
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
) )
// UserService handles users for the JIRA instance / API. // UserService handles users for the Jira instance / API.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-group-Users
type UserService struct { type UserService struct {
client *Client client *Client
} }
// User represents a JIRA user. // User represents a Jira user.
type User struct { type User struct {
Self string `json:"self,omitempty" structs:"self,omitempty"` Self string `json:"self,omitempty" structs:"self,omitempty"`
AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"` AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"`
AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"` AccountType string `json:"accountType,omitempty" structs:"accountType,omitempty"`
// TODO: name & key are deprecated, see:
// https://developer.atlassian.com/cloud/jira/platform/api-changes-for-user-privacy-announcement/
Name string `json:"name,omitempty" structs:"name,omitempty"` Name string `json:"name,omitempty" structs:"name,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"` Key string `json:"key,omitempty" structs:"key,omitempty"`
Password string `json:"-"` Password string `json:"-"`
@ -47,12 +46,12 @@ type userSearch []userSearchParam
type userSearchF func(userSearch) userSearch type userSearchF func(userSearch) userSearch
// Get gets user info from JIRA // GetWithContext gets user info from Jira using its Account Id
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-get
func (s *UserService) Get(username string) (*User, *Response, error) { func (s *UserService) GetWithContext(ctx context.Context, accountId string) (*User, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?username=%s", username) apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -65,12 +64,41 @@ func (s *UserService) Get(username string) (*User, *Response, error) {
return user, resp, nil return user, resp, nil
} }
// Create creates an user in JIRA. // Get wraps GetWithContext using the background context.
func (s *UserService) Get(accountId string) (*User, *Response, error) {
return s.GetWithContext(context.Background(), accountId)
}
// GetByAccountIDWithContext gets user info from Jira
// Searching by another parameter that is not accountId is deprecated,
// but this method is kept for backwards compatibility
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUser
func (s *UserService) GetByAccountIDWithContext(ctx context.Context, accountID string) (*User, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountID)
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil {
return nil, nil, err
}
user := new(User)
resp, err := s.client.Do(req, user)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
return user, resp, nil
}
// GetByAccountID wraps GetByAccountIDWithContext using the background context.
func (s *UserService) GetByAccountID(accountID string) (*User, *Response, error) {
return s.GetByAccountIDWithContext(context.Background(), accountID)
}
// CreateWithContext creates an user in Jira.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser // Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-createUser
func (s *UserService) Create(user *User) (*User, *Response, error) { func (s *UserService) CreateWithContext(ctx context.Context, user *User) (*User, *Response, error) {
apiEndpoint := "/rest/api/2/user" apiEndpoint := "/rest/api/2/user"
req, err := s.client.NewRequest("POST", apiEndpoint, user) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, user)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -84,24 +112,29 @@ func (s *UserService) Create(user *User) (*User, *Response, error) {
defer resp.Body.Close() defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
e := fmt.Errorf("Could not read the returned data") e := fmt.Errorf("could not read the returned data")
return nil, resp, NewJiraError(resp, e) return nil, resp, NewJiraError(resp, e)
} }
err = json.Unmarshal(data, responseUser) err = json.Unmarshal(data, responseUser)
if err != nil { if err != nil {
e := fmt.Errorf("Could not unmarshall the data into struct") e := fmt.Errorf("could not unmarshall the data into struct")
return nil, resp, NewJiraError(resp, e) return nil, resp, NewJiraError(resp, e)
} }
return responseUser, resp, nil return responseUser, resp, nil
} }
// Delete deletes an user from JIRA. // Create wraps CreateWithContext using the background context.
func (s *UserService) Create(user *User) (*User, *Response, error) {
return s.CreateWithContext(context.Background(), user)
}
// DeleteWithContext deletes an user from Jira.
// Returns http.StatusNoContent on success. // Returns http.StatusNoContent on success.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-user-delete // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-delete
func (s *UserService) Delete(username string) (*Response, error) { func (s *UserService) DeleteWithContext(ctx context.Context, accountId string) (*Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user?username=%s", username) apiEndpoint := fmt.Sprintf("/rest/api/2/user?accountId=%s", accountId)
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,12 +146,17 @@ func (s *UserService) Delete(username string) (*Response, error) {
return resp, nil return resp, nil
} }
// GetGroups returns the groups which the user belongs to // Delete wraps DeleteWithContext using the background context.
func (s *UserService) Delete(accountId string) (*Response, error) {
return s.DeleteWithContext(context.Background(), accountId)
}
// GetGroupsWithContext returns the groups which the user belongs to
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-getUserGroups // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-groups-get
func (s *UserService) GetGroups(username string) (*[]UserGroup, *Response, error) { func (s *UserService) GetGroupsWithContext(ctx context.Context, accountId string) (*[]UserGroup, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?username=%s", username) apiEndpoint := fmt.Sprintf("/rest/api/2/user/groups?accountId=%s", accountId)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -131,12 +169,17 @@ func (s *UserService) GetGroups(username string) (*[]UserGroup, *Response, error
return userGroups, resp, nil return userGroups, resp, nil
} }
// Get information about the current logged-in user // GetGroups wraps GetGroupsWithContext using the background context.
func (s *UserService) GetGroups(accountId string) (*[]UserGroup, *Response, error) {
return s.GetGroupsWithContext(context.Background(), accountId)
}
// GetSelfWithContext information about the current logged-in user
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-myself-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-myself-get
func (s *UserService) GetSelf() (*User, *Response, error) { func (s *UserService) GetSelfWithContext(ctx context.Context) (*User, *Response, error) {
const apiEndpoint = "rest/api/2/myself" const apiEndpoint = "rest/api/2/myself"
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -148,6 +191,11 @@ func (s *UserService) GetSelf() (*User, *Response, error) {
return &user, resp, nil return &user, resp, nil
} }
// GetSelf wraps GetSelfWithContext using the background context.
func (s *UserService) GetSelf() (*User, *Response, error) {
return s.GetSelfWithContext(context.Background())
}
// WithMaxResults sets the max results to return // WithMaxResults sets the max results to return
func WithMaxResults(maxResults int) userSearchF { func WithMaxResults(maxResults int) userSearchF {
return func(s userSearch) userSearch { return func(s userSearch) userSearch {
@ -180,14 +228,14 @@ func WithInactive(inactive bool) userSearchF {
} }
} }
// Find searches for user info from JIRA: // FindWithContext searches for user info from Jira:
// It can find users by email, username or name // It can find users by email or display name using the query parameter
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/user-findUsers // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-get
func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Response, error) { func (s *UserService) FindWithContext(ctx context.Context, property string, tweaks ...userSearchF) ([]User, *Response, error) {
search := []userSearchParam{ search := []userSearchParam{
{ {
name: "username", name: "query",
value: property, value: property,
}, },
} }
@ -201,7 +249,7 @@ func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Res
} }
apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1]) apiEndpoint := fmt.Sprintf("/rest/api/2/user/search?%s", queryString[:len(queryString)-1])
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -213,3 +261,8 @@ func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Res
} }
return users, resp, nil return users, resp, nil
} }
// Find wraps FindWithContext using the background context.
func (s *UserService) Find(property string, tweaks ...userSearchF) ([]User, *Response, error) {
return s.FindWithContext(context.Background(), property, tweaks...)
}

View File

@ -1,14 +1,15 @@
package jira package jira
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
) )
// VersionService handles Versions for the JIRA instance / API. // VersionService handles Versions for the Jira instance / API.
// //
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version // Jira API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/version
type VersionService struct { type VersionService struct {
client *Client client *Client
} }
@ -19,20 +20,20 @@ type Version struct {
ID string `json:"id,omitempty" structs:"id,omitempty"` ID string `json:"id,omitempty" structs:"id,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"` Name string `json:"name,omitempty" structs:"name,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"` Description string `json:"description,omitempty" structs:"description,omitempty"`
Archived bool `json:"archived,omitempty" structs:"archived,omitempty"` Archived *bool `json:"archived,omitempty" structs:"archived,omitempty"`
Released bool `json:"released,omitempty" structs:"released,omitempty"` Released *bool `json:"released,omitempty" structs:"released,omitempty"`
ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"` ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"`
UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"` UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"`
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` // Unlike other IDs, this is returned as a number ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"` // Unlike other IDs, this is returned as a number
StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"` StartDate string `json:"startDate,omitempty" structs:"startDate,omitempty"`
} }
// Get gets version info from JIRA // GetWithContext gets version info from Jira
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-get
func (s *VersionService) Get(versionID int) (*Version, *Response, error) { func (s *VersionService) GetWithContext(ctx context.Context, versionID int) (*Version, *Response, error) {
apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID) apiEndpoint := fmt.Sprintf("/rest/api/2/version/%v", versionID)
req, err := s.client.NewRequest("GET", apiEndpoint, nil) req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -45,12 +46,17 @@ func (s *VersionService) Get(versionID int) (*Version, *Response, error) {
return version, resp, nil return version, resp, nil
} }
// Create creates a version in JIRA. // Get wraps GetWithContext using the background context.
func (s *VersionService) Get(versionID int) (*Version, *Response, error) {
return s.GetWithContext(context.Background(), versionID)
}
// CreateWithContext creates a version in Jira.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-post
func (s *VersionService) Create(version *Version) (*Version, *Response, error) { func (s *VersionService) CreateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) {
apiEndpoint := "/rest/api/2/version" apiEndpoint := "/rest/api/2/version"
req, err := s.client.NewRequest("POST", apiEndpoint, version) req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, version)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -64,23 +70,28 @@ func (s *VersionService) Create(version *Version) (*Version, *Response, error) {
defer resp.Body.Close() defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
e := fmt.Errorf("Could not read the returned data") e := fmt.Errorf("could not read the returned data")
return nil, resp, NewJiraError(resp, e) return nil, resp, NewJiraError(resp, e)
} }
err = json.Unmarshal(data, responseVersion) err = json.Unmarshal(data, responseVersion)
if err != nil { if err != nil {
e := fmt.Errorf("Could not unmarshall the data into struct") e := fmt.Errorf("could not unmarshall the data into struct")
return nil, resp, NewJiraError(resp, e) return nil, resp, NewJiraError(resp, e)
} }
return responseVersion, resp, nil return responseVersion, resp, nil
} }
// Update updates a version from a JSON representation. // Create wraps CreateWithContext using the background context.
func (s *VersionService) Create(version *Version) (*Version, *Response, error) {
return s.CreateWithContext(context.Background(), version)
}
// UpdateWithContext updates a version from a JSON representation.
// //
// JIRA API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put // Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-version-id-put
func (s *VersionService) Update(version *Version) (*Version, *Response, error) { func (s *VersionService) UpdateWithContext(ctx context.Context, version *Version) (*Version, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID) apiEndpoint := fmt.Sprintf("rest/api/2/version/%v", version.ID)
req, err := s.client.NewRequest("PUT", apiEndpoint, version) req, err := s.client.NewRequestWithContext(ctx, "PUT", apiEndpoint, version)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -95,3 +106,8 @@ func (s *VersionService) Update(version *Version) (*Version, *Response, error) {
ret := *version ret := *version
return &ret, resp, nil return &ret, resp, nil
} }
// Update wraps UpdateWithContext using the background context.
func (s *VersionService) Update(version *Version) (*Version, *Response, error) {
return s.UpdateWithContext(context.Background(), version)
}

191
vendor/github.com/containerd/containerd/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright The containerd Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://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.

16
vendor/github.com/containerd/containerd/NOTICE generated vendored Normal file
View File

@ -0,0 +1,16 @@
Docker
Copyright 2012-2015 Docker, Inc.
This product includes software developed at Docker, Inc. (https://www.docker.com).
The following is courtesy of our legal counsel:
Use and transfer of Docker may be subject to certain restrictions by the
United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not
violate applicable laws.
For more information, please see https://www.bis.doc.gov
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.

View File

@ -0,0 +1,93 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package errdefs defines the common errors used throughout containerd
// packages.
//
// Use with errors.Wrap and error.Wrapf to add context to an error.
//
// To detect an error class, use the IsXXX functions to tell whether an error
// is of a certain type.
//
// The functions ToGRPC and FromGRPC can be used to map server-side and
// client-side errors to the correct types.
package errdefs
import (
"context"
"github.com/pkg/errors"
)
// Definitions of common error types used throughout containerd. All containerd
// errors returned by most packages will map into one of these errors classes.
// Packages should return errors of these types when they want to instruct a
// client to take a particular action.
//
// For the most part, we just try to provide local grpc errors. Most conditions
// map very well to those defined by grpc.
var (
ErrUnknown = errors.New("unknown") // used internally to represent a missed mapping.
ErrInvalidArgument = errors.New("invalid argument")
ErrNotFound = errors.New("not found")
ErrAlreadyExists = errors.New("already exists")
ErrFailedPrecondition = errors.New("failed precondition")
ErrUnavailable = errors.New("unavailable")
ErrNotImplemented = errors.New("not implemented") // represents not supported and unimplemented
)
// IsInvalidArgument returns true if the error is due to an invalid argument
func IsInvalidArgument(err error) bool {
return errors.Is(err, ErrInvalidArgument)
}
// IsNotFound returns true if the error is due to a missing object
func IsNotFound(err error) bool {
return errors.Is(err, ErrNotFound)
}
// IsAlreadyExists returns true if the error is due to an already existing
// metadata item
func IsAlreadyExists(err error) bool {
return errors.Is(err, ErrAlreadyExists)
}
// IsFailedPrecondition returns true if an operation could not proceed to the
// lack of a particular condition
func IsFailedPrecondition(err error) bool {
return errors.Is(err, ErrFailedPrecondition)
}
// IsUnavailable returns true if the error is due to a resource being unavailable
func IsUnavailable(err error) bool {
return errors.Is(err, ErrUnavailable)
}
// IsNotImplemented returns true if the error is due to not being implemented
func IsNotImplemented(err error) bool {
return errors.Is(err, ErrNotImplemented)
}
// IsCanceled returns true if the error is due to `context.Canceled`.
func IsCanceled(err error) bool {
return errors.Is(err, context.Canceled)
}
// IsDeadlineExceeded returns true if the error is due to
// `context.DeadlineExceeded`.
func IsDeadlineExceeded(err error) bool {
return errors.Is(err, context.DeadlineExceeded)
}

147
vendor/github.com/containerd/containerd/errdefs/grpc.go generated vendored Normal file
View File

@ -0,0 +1,147 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errdefs
import (
"context"
"strings"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ToGRPC will attempt to map the backend containerd error into a grpc error,
// using the original error message as a description.
//
// Further information may be extracted from certain errors depending on their
// type.
//
// If the error is unmapped, the original error will be returned to be handled
// by the regular grpc error handling stack.
func ToGRPC(err error) error {
if err == nil {
return nil
}
if isGRPCError(err) {
// error has already been mapped to grpc
return err
}
switch {
case IsInvalidArgument(err):
return status.Errorf(codes.InvalidArgument, err.Error())
case IsNotFound(err):
return status.Errorf(codes.NotFound, err.Error())
case IsAlreadyExists(err):
return status.Errorf(codes.AlreadyExists, err.Error())
case IsFailedPrecondition(err):
return status.Errorf(codes.FailedPrecondition, err.Error())
case IsUnavailable(err):
return status.Errorf(codes.Unavailable, err.Error())
case IsNotImplemented(err):
return status.Errorf(codes.Unimplemented, err.Error())
case IsCanceled(err):
return status.Errorf(codes.Canceled, err.Error())
case IsDeadlineExceeded(err):
return status.Errorf(codes.DeadlineExceeded, err.Error())
}
return err
}
// ToGRPCf maps the error to grpc error codes, assembling the formatting string
// and combining it with the target error string.
//
// This is equivalent to errors.ToGRPC(errors.Wrapf(err, format, args...))
func ToGRPCf(err error, format string, args ...interface{}) error {
return ToGRPC(errors.Wrapf(err, format, args...))
}
// FromGRPC returns the underlying error from a grpc service based on the grpc error code
func FromGRPC(err error) error {
if err == nil {
return nil
}
var cls error // divide these into error classes, becomes the cause
switch code(err) {
case codes.InvalidArgument:
cls = ErrInvalidArgument
case codes.AlreadyExists:
cls = ErrAlreadyExists
case codes.NotFound:
cls = ErrNotFound
case codes.Unavailable:
cls = ErrUnavailable
case codes.FailedPrecondition:
cls = ErrFailedPrecondition
case codes.Unimplemented:
cls = ErrNotImplemented
case codes.Canceled:
cls = context.Canceled
case codes.DeadlineExceeded:
cls = context.DeadlineExceeded
default:
cls = ErrUnknown
}
msg := rebaseMessage(cls, err)
if msg != "" {
err = errors.Wrap(cls, msg)
} else {
err = errors.WithStack(cls)
}
return err
}
// rebaseMessage removes the repeats for an error at the end of an error
// string. This will happen when taking an error over grpc then remapping it.
//
// Effectively, we just remove the string of cls from the end of err if it
// appears there.
func rebaseMessage(cls error, err error) string {
desc := errDesc(err)
clss := cls.Error()
if desc == clss {
return ""
}
return strings.TrimSuffix(desc, ": "+clss)
}
func isGRPCError(err error) bool {
_, ok := status.FromError(err)
return ok
}
func code(err error) codes.Code {
if s, ok := status.FromError(err); ok {
return s.Code()
}
return codes.Unknown
}
func errDesc(err error) string {
if s, ok := status.FromError(err); ok {
return s.Message()
}
return err.Error()
}

68
vendor/github.com/containerd/containerd/log/context.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"context"
"github.com/sirupsen/logrus"
)
var (
// G is an alias for GetLogger.
//
// We may want to define this locally to a package to get package tagged log
// messages.
G = GetLogger
// L is an alias for the standard logger.
L = logrus.NewEntry(logrus.StandardLogger())
)
type (
loggerKey struct{}
)
const (
// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
// ensure the formatted time is always the same number of characters.
RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
// TextFormat represents the text logging format
TextFormat = "text"
// JSONFormat represents the JSON logging format
JSONFormat = "json"
)
// WithLogger returns a new context with the provided logger. Use in
// combination with logger.WithField(s) for great effect.
func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context {
return context.WithValue(ctx, loggerKey{}, logger)
}
// GetLogger retrieves the current logger from the context. If no logger is
// available, the default logger is returned.
func GetLogger(ctx context.Context) *logrus.Entry {
logger := ctx.Value(loggerKey{})
if logger == nil {
return L
}
return logger.(*logrus.Entry)
}

View File

@ -0,0 +1,193 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package platforms
import (
"strconv"
"strings"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// MatchComparer is able to match and compare platforms to
// filter and sort platforms.
type MatchComparer interface {
Matcher
Less(specs.Platform, specs.Platform) bool
}
// platformVector returns an (ordered) vector of appropriate specs.Platform
// objects to try matching for the given platform object (see platforms.Only).
func platformVector(platform specs.Platform) []specs.Platform {
vector := []specs.Platform{platform}
switch platform.Architecture {
case "amd64":
vector = append(vector, specs.Platform{
Architecture: "386",
OS: platform.OS,
OSVersion: platform.OSVersion,
OSFeatures: platform.OSFeatures,
Variant: platform.Variant,
})
case "arm":
if armVersion, err := strconv.Atoi(strings.TrimPrefix(platform.Variant, "v")); err == nil && armVersion > 5 {
for armVersion--; armVersion >= 5; armVersion-- {
vector = append(vector, specs.Platform{
Architecture: platform.Architecture,
OS: platform.OS,
OSVersion: platform.OSVersion,
OSFeatures: platform.OSFeatures,
Variant: "v" + strconv.Itoa(armVersion),
})
}
}
case "arm64":
variant := platform.Variant
if variant == "" {
variant = "v8"
}
vector = append(vector, platformVector(specs.Platform{
Architecture: "arm",
OS: platform.OS,
OSVersion: platform.OSVersion,
OSFeatures: platform.OSFeatures,
Variant: variant,
})...)
}
return vector
}
// Only returns a match comparer for a single platform
// using default resolution logic for the platform.
//
// For arm/v8, will also match arm/v7, arm/v6 and arm/v5
// For arm/v7, will also match arm/v6 and arm/v5
// For arm/v6, will also match arm/v5
// For amd64, will also match 386
func Only(platform specs.Platform) MatchComparer {
return Ordered(platformVector(Normalize(platform))...)
}
// OnlyStrict returns a match comparer for a single platform.
//
// Unlike Only, OnlyStrict does not match sub platforms.
// So, "arm/vN" will not match "arm/vM" where M < N,
// and "amd64" will not also match "386".
//
// OnlyStrict matches non-canonical forms.
// So, "arm64" matches "arm/64/v8".
func OnlyStrict(platform specs.Platform) MatchComparer {
return Ordered(Normalize(platform))
}
// Ordered returns a platform MatchComparer which matches any of the platforms
// but orders them in order they are provided.
func Ordered(platforms ...specs.Platform) MatchComparer {
matchers := make([]Matcher, len(platforms))
for i := range platforms {
matchers[i] = NewMatcher(platforms[i])
}
return orderedPlatformComparer{
matchers: matchers,
}
}
// Any returns a platform MatchComparer which matches any of the platforms
// with no preference for ordering.
func Any(platforms ...specs.Platform) MatchComparer {
matchers := make([]Matcher, len(platforms))
for i := range platforms {
matchers[i] = NewMatcher(platforms[i])
}
return anyPlatformComparer{
matchers: matchers,
}
}
// All is a platform MatchComparer which matches all platforms
// with preference for ordering.
var All MatchComparer = allPlatformComparer{}
type orderedPlatformComparer struct {
matchers []Matcher
}
func (c orderedPlatformComparer) Match(platform specs.Platform) bool {
for _, m := range c.matchers {
if m.Match(platform) {
return true
}
}
return false
}
func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool {
for _, m := range c.matchers {
p1m := m.Match(p1)
p2m := m.Match(p2)
if p1m && !p2m {
return true
}
if p1m || p2m {
return false
}
}
return false
}
type anyPlatformComparer struct {
matchers []Matcher
}
func (c anyPlatformComparer) Match(platform specs.Platform) bool {
for _, m := range c.matchers {
if m.Match(platform) {
return true
}
}
return false
}
func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool {
var p1m, p2m bool
for _, m := range c.matchers {
if !p1m && m.Match(p1) {
p1m = true
}
if !p2m && m.Match(p2) {
p2m = true
}
if p1m && p2m {
return false
}
}
// If one matches, and the other does, sort match first
return p1m && !p2m
}
type allPlatformComparer struct{}
func (allPlatformComparer) Match(specs.Platform) bool {
return true
}
func (allPlatformComparer) Less(specs.Platform, specs.Platform) bool {
return false
}

View File

@ -0,0 +1,131 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package platforms
import (
"bufio"
"os"
"runtime"
"strings"
"sync"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
)
// Present the ARM instruction set architecture, eg: v7, v8
// Don't use this value directly; call cpuVariant() instead.
var cpuVariantValue string
var cpuVariantOnce sync.Once
func cpuVariant() string {
cpuVariantOnce.Do(func() {
if isArmArch(runtime.GOARCH) {
cpuVariantValue = getCPUVariant()
}
})
return cpuVariantValue
}
// For Linux, the kernel has already detected the ABI, ISA and Features.
// So we don't need to access the ARM registers to detect platform information
// by ourselves. We can just parse these information from /proc/cpuinfo
func getCPUInfo(pattern string) (info string, err error) {
if !isLinuxOS(runtime.GOOS) {
return "", errors.Wrapf(errdefs.ErrNotImplemented, "getCPUInfo for OS %s", runtime.GOOS)
}
cpuinfo, err := os.Open("/proc/cpuinfo")
if err != nil {
return "", err
}
defer cpuinfo.Close()
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
// the first core is enough.
scanner := bufio.NewScanner(cpuinfo)
for scanner.Scan() {
newline := scanner.Text()
list := strings.Split(newline, ":")
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
return strings.TrimSpace(list[1]), nil
}
}
// Check whether the scanner encountered errors
err = scanner.Err()
if err != nil {
return "", err
}
return "", errors.Wrapf(errdefs.ErrNotFound, "getCPUInfo for pattern: %s", pattern)
}
func getCPUVariant() string {
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
// runtime.GOARCH to determine the variants
var variant string
switch runtime.GOARCH {
case "arm64":
variant = "v8"
case "arm":
variant = "v7"
default:
variant = "unknown"
}
return variant
}
variant, err := getCPUInfo("Cpu architecture")
if err != nil {
log.L.WithError(err).Error("failure getting variant")
return ""
}
// handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7")
// https://www.raspberrypi.org/forums/viewtopic.php?t=12614
if runtime.GOARCH == "arm" && variant == "7" {
model, err := getCPUInfo("model name")
if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") {
variant = "6"
}
}
switch strings.ToLower(variant) {
case "8", "aarch64":
variant = "v8"
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
variant = "v7"
case "6", "6tej":
variant = "v6"
case "5", "5t", "5te", "5tej":
variant = "v5"
case "4", "4t":
variant = "v4"
case "3":
variant = "v3"
default:
variant = "unknown"
}
return variant
}

View File

@ -0,0 +1,114 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package platforms
import (
"runtime"
"strings"
)
// isLinuxOS returns true if the operating system is Linux.
//
// The OS value should be normalized before calling this function.
func isLinuxOS(os string) bool {
return os == "linux"
}
// These function are generated from https://golang.org/src/go/build/syslist.go.
//
// We use switch statements because they are slightly faster than map lookups
// and use a little less memory.
// isKnownOS returns true if we know about the operating system.
//
// The OS value should be normalized before calling this function.
func isKnownOS(os string) bool {
switch os {
case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos":
return true
}
return false
}
// isArmArch returns true if the architecture is ARM.
//
// The arch value should be normalized before being passed to this function.
func isArmArch(arch string) bool {
switch arch {
case "arm", "arm64":
return true
}
return false
}
// isKnownArch returns true if we know about the architecture.
//
// The arch value should be normalized before being passed to this function.
func isKnownArch(arch string) bool {
switch arch {
case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm":
return true
}
return false
}
func normalizeOS(os string) string {
if os == "" {
return runtime.GOOS
}
os = strings.ToLower(os)
switch os {
case "macos":
os = "darwin"
}
return os
}
// normalizeArch normalizes the architecture.
func normalizeArch(arch, variant string) (string, string) {
arch, variant = strings.ToLower(arch), strings.ToLower(variant)
switch arch {
case "i386":
arch = "386"
variant = ""
case "x86_64", "x86-64":
arch = "amd64"
variant = ""
case "aarch64", "arm64":
arch = "arm64"
switch variant {
case "8", "v8":
variant = ""
}
case "armhf":
arch = "arm"
variant = "v7"
case "armel":
arch = "arm"
variant = "v6"
case "arm":
switch variant {
case "", "7":
variant = "v7"
case "5", "6", "8":
variant = "v" + variant
}
}
return arch, variant
}

View File

@ -0,0 +1,43 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package platforms
import (
"runtime"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// DefaultString returns the default string specifier for the platform.
func DefaultString() string {
return Format(DefaultSpec())
}
// DefaultSpec returns the current platform's default platform specification.
func DefaultSpec() specs.Platform {
return specs.Platform{
OS: runtime.GOOS,
Architecture: runtime.GOARCH,
// The Variant field will be empty if arch != ARM.
Variant: cpuVariant(),
}
}
// DefaultStrict returns strict form of Default.
func DefaultStrict() MatchComparer {
return OnlyStrict(DefaultSpec())
}

View File

@ -0,0 +1,24 @@
// +build !windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package platforms
// Default returns the default matcher for the platform.
func Default() MatchComparer {
return Only(DefaultSpec())
}

View File

@ -0,0 +1,81 @@
// +build windows
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package platforms
import (
"fmt"
"runtime"
"strconv"
"strings"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/sys/windows"
)
type matchComparer struct {
defaults Matcher
osVersionPrefix string
}
// Match matches platform with the same windows major, minor
// and build version.
func (m matchComparer) Match(p imagespec.Platform) bool {
if m.defaults.Match(p) {
// TODO(windows): Figure out whether OSVersion is deprecated.
return strings.HasPrefix(p.OSVersion, m.osVersionPrefix)
}
return false
}
// Less sorts matched platforms in front of other platforms.
// For matched platforms, it puts platforms with larger revision
// number in front.
func (m matchComparer) Less(p1, p2 imagespec.Platform) bool {
m1, m2 := m.Match(p1), m.Match(p2)
if m1 && m2 {
r1, r2 := revision(p1.OSVersion), revision(p2.OSVersion)
return r1 > r2
}
return m1 && !m2
}
func revision(v string) int {
parts := strings.Split(v, ".")
if len(parts) < 4 {
return 0
}
r, err := strconv.Atoi(parts[3])
if err != nil {
return 0
}
return r
}
// Default returns the current platform's default platform specification.
func Default() MatchComparer {
major, minor, build := windows.RtlGetNtVersionNumbers()
return matchComparer{
defaults: Ordered(DefaultSpec(), specs.Platform{
OS: "linux",
Architecture: runtime.GOARCH,
}),
osVersionPrefix: fmt.Sprintf("%d.%d.%d", major, minor, build),
}
}

View File

@ -0,0 +1,278 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package platforms provides a toolkit for normalizing, matching and
// specifying container platforms.
//
// Centered around OCI platform specifications, we define a string-based
// specifier syntax that can be used for user input. With a specifier, users
// only need to specify the parts of the platform that are relevant to their
// context, providing an operating system or architecture or both.
//
// How do I use this package?
//
// The vast majority of use cases should simply use the match function with
// user input. The first step is to parse a specifier into a matcher:
//
// m, err := Parse("linux")
// if err != nil { ... }
//
// Once you have a matcher, use it to match against the platform declared by a
// component, typically from an image or runtime. Since extracting an images
// platform is a little more involved, we'll use an example against the
// platform default:
//
// if ok := m.Match(Default()); !ok { /* doesn't match */ }
//
// This can be composed in loops for resolving runtimes or used as a filter for
// fetch and select images.
//
// More details of the specifier syntax and platform spec follow.
//
// Declaring Platform Support
//
// Components that have strict platform requirements should use the OCI
// platform specification to declare their support. Typically, this will be
// images and runtimes that should make these declaring which platform they
// support specifically. This looks roughly as follows:
//
// type Platform struct {
// Architecture string
// OS string
// Variant string
// }
//
// Most images and runtimes should at least set Architecture and OS, according
// to their GOARCH and GOOS values, respectively (follow the OCI image
// specification when in doubt). ARM should set variant under certain
// discussions, which are outlined below.
//
// Platform Specifiers
//
// While the OCI platform specifications provide a tool for components to
// specify structured information, user input typically doesn't need the full
// context and much can be inferred. To solve this problem, we introduced
// "specifiers". A specifier has the format
// `<os>|<arch>|<os>/<arch>[/<variant>]`. The user can provide either the
// operating system or the architecture or both.
//
// An example of a common specifier is `linux/amd64`. If the host has a default
// of runtime that matches this, the user can simply provide the component that
// matters. For example, if a image provides amd64 and arm64 support, the
// operating system, `linux` can be inferred, so they only have to provide
// `arm64` or `amd64`. Similar behavior is implemented for operating systems,
// where the architecture may be known but a runtime may support images from
// different operating systems.
//
// Normalization
//
// Because not all users are familiar with the way the Go runtime represents
// platforms, several normalizations have been provided to make this package
// easier to user.
//
// The following are performed for architectures:
//
// Value Normalized
// aarch64 arm64
// armhf arm
// armel arm/v6
// i386 386
// x86_64 amd64
// x86-64 amd64
//
// We also normalize the operating system `macos` to `darwin`.
//
// ARM Support
//
// To qualify ARM architecture, the Variant field is used to qualify the arm
// version. The most common arm version, v7, is represented without the variant
// unless it is explicitly provided. This is treated as equivalent to armhf. A
// previous architecture, armel, will be normalized to arm/v6.
//
// While these normalizations are provided, their support on arm platforms has
// not yet been fully implemented and tested.
package platforms
import (
"regexp"
"runtime"
"strconv"
"strings"
"github.com/containerd/containerd/errdefs"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
var (
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
)
// Matcher matches platforms specifications, provided by an image or runtime.
type Matcher interface {
Match(platform specs.Platform) bool
}
// NewMatcher returns a simple matcher based on the provided platform
// specification. The returned matcher only looks for equality based on os,
// architecture and variant.
//
// One may implement their own matcher if this doesn't provide the required
// functionality.
//
// Applications should opt to use `Match` over directly parsing specifiers.
func NewMatcher(platform specs.Platform) Matcher {
return &matcher{
Platform: Normalize(platform),
}
}
type matcher struct {
specs.Platform
}
func (m *matcher) Match(platform specs.Platform) bool {
normalized := Normalize(platform)
return m.OS == normalized.OS &&
m.Architecture == normalized.Architecture &&
m.Variant == normalized.Variant
}
func (m *matcher) String() string {
return Format(m.Platform)
}
// Parse parses the platform specifier syntax into a platform declaration.
//
// Platform specifiers are in the format `<os>|<arch>|<os>/<arch>[/<variant>]`.
// The minimum required information for a platform specifier is the operating
// system or architecture. If there is only a single string (no slashes), the
// value will be matched against the known set of operating systems, then fall
// back to the known set of architectures. The missing component will be
// inferred based on the local environment.
func Parse(specifier string) (specs.Platform, error) {
if strings.Contains(specifier, "*") {
// TODO(stevvooe): need to work out exact wildcard handling
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: wildcards not yet supported", specifier)
}
parts := strings.Split(specifier, "/")
for _, part := range parts {
if !specifierRe.MatchString(part) {
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q is an invalid component of %q: platform specifier component must match %q", part, specifier, specifierRe.String())
}
}
var p specs.Platform
switch len(parts) {
case 1:
// in this case, we will test that the value might be an OS, then look
// it up. If it is not known, we'll treat it as an architecture. Since
// we have very little information about the platform here, we are
// going to be a little more strict if we don't know about the argument
// value.
p.OS = normalizeOS(parts[0])
if isKnownOS(p.OS) {
// picks a default architecture
p.Architecture = runtime.GOARCH
if p.Architecture == "arm" && cpuVariant() != "v7" {
p.Variant = cpuVariant()
}
return p, nil
}
p.Architecture, p.Variant = normalizeArch(parts[0], "")
if p.Architecture == "arm" && p.Variant == "v7" {
p.Variant = ""
}
if isKnownArch(p.Architecture) {
p.OS = runtime.GOOS
return p, nil
}
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: unknown operating system or architecture", specifier)
case 2:
// In this case, we treat as a regular os/arch pair. We don't care
// about whether or not we know of the platform.
p.OS = normalizeOS(parts[0])
p.Architecture, p.Variant = normalizeArch(parts[1], "")
if p.Architecture == "arm" && p.Variant == "v7" {
p.Variant = ""
}
return p, nil
case 3:
// we have a fully specified variant, this is rare
p.OS = normalizeOS(parts[0])
p.Architecture, p.Variant = normalizeArch(parts[1], parts[2])
if p.Architecture == "arm64" && p.Variant == "" {
p.Variant = "v8"
}
return p, nil
}
return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: cannot parse platform specifier", specifier)
}
// MustParse is like Parses but panics if the specifier cannot be parsed.
// Simplifies initialization of global variables.
func MustParse(specifier string) specs.Platform {
p, err := Parse(specifier)
if err != nil {
panic("platform: Parse(" + strconv.Quote(specifier) + "): " + err.Error())
}
return p
}
// Format returns a string specifier from the provided platform specification.
func Format(platform specs.Platform) string {
if platform.OS == "" {
return "unknown"
}
return joinNotEmpty(platform.OS, platform.Architecture, platform.Variant)
}
func joinNotEmpty(s ...string) string {
var ss []string
for _, s := range s {
if s == "" {
continue
}
ss = append(ss, s)
}
return strings.Join(ss, "/")
}
// Normalize validates and translate the platform to the canonical value.
//
// For example, if "Aarch64" is encountered, we change it to "arm64" or if
// "x86_64" is encountered, it becomes "amd64".
func Normalize(platform specs.Platform) specs.Platform {
platform.OS = normalizeOS(platform.OS)
platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant)
// these fields are deprecated, remove them
platform.OSFeatures = nil
platform.OSVersion = ""
return platform
}

View File

@ -15,7 +15,7 @@ type roffRenderer struct {
extensions blackfriday.Extensions extensions blackfriday.Extensions
listCounters []int listCounters []int
firstHeader bool firstHeader bool
defineTerm bool firstDD bool
listDepth int listDepth int
} }
@ -42,7 +42,8 @@ const (
quoteCloseTag = "\n.RE\n" quoteCloseTag = "\n.RE\n"
listTag = "\n.RS\n" listTag = "\n.RS\n"
listCloseTag = "\n.RE\n" listCloseTag = "\n.RE\n"
arglistTag = "\n.TP\n" dtTag = "\n.TP\n"
dd2Tag = "\n"
tableStart = "\n.TS\nallbox;\n" tableStart = "\n.TS\nallbox;\n"
tableEnd = ".TE\n" tableEnd = ".TE\n"
tableCellStart = "T{\n" tableCellStart = "T{\n"
@ -90,7 +91,7 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
switch node.Type { switch node.Type {
case blackfriday.Text: case blackfriday.Text:
r.handleText(w, node, entering) escapeSpecialChars(w, node.Literal)
case blackfriday.Softbreak: case blackfriday.Softbreak:
out(w, crTag) out(w, crTag)
case blackfriday.Hardbreak: case blackfriday.Hardbreak:
@ -150,40 +151,21 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
out(w, codeCloseTag) out(w, codeCloseTag)
case blackfriday.Table: case blackfriday.Table:
r.handleTable(w, node, entering) r.handleTable(w, node, entering)
case blackfriday.TableCell:
r.handleTableCell(w, node, entering)
case blackfriday.TableHead: case blackfriday.TableHead:
case blackfriday.TableBody: case blackfriday.TableBody:
case blackfriday.TableRow: case blackfriday.TableRow:
// no action as cell entries do all the nroff formatting // no action as cell entries do all the nroff formatting
return blackfriday.GoToNext return blackfriday.GoToNext
case blackfriday.TableCell:
r.handleTableCell(w, node, entering)
case blackfriday.HTMLSpan:
// ignore other HTML tags
default: default:
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String()) fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
} }
return walkAction return walkAction
} }
func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) {
var (
start, end string
)
// handle special roff table cell text encapsulation
if node.Parent.Type == blackfriday.TableCell {
if len(node.Literal) > 30 {
start = tableCellStart
end = tableCellEnd
} else {
// end rows that aren't terminated by "tableCellEnd" with a cr if end of row
if node.Parent.Next == nil && !node.Parent.IsHeader {
end = crTag
}
}
}
out(w, start)
escapeSpecialChars(w, node.Literal)
out(w, end)
}
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) { func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
if entering { if entering {
switch node.Level { switch node.Level {
@ -230,15 +212,20 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering
if node.ListFlags&blackfriday.ListTypeOrdered != 0 { if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1])) out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
r.listCounters[len(r.listCounters)-1]++ r.listCounters[len(r.listCounters)-1]++
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
// DT (definition term): line just before DD (see below).
out(w, dtTag)
r.firstDD = true
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// state machine for handling terms and following definitions // DD (definition description): line that starts with ": ".
// since blackfriday does not distinguish them properly, nor //
// does it seperate them into separate lists as it should // We have to distinguish between the first DD and the
if !r.defineTerm { // subsequent ones, as there should be no vertical
out(w, arglistTag) // whitespace between the DT and the first DD.
r.defineTerm = true if r.firstDD {
r.firstDD = false
} else { } else {
r.defineTerm = false out(w, dd2Tag)
} }
} else { } else {
out(w, ".IP \\(bu 2\n") out(w, ".IP \\(bu 2\n")
@ -251,7 +238,7 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) { func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
if entering { if entering {
out(w, tableStart) out(w, tableStart)
//call walker to count cells (and rows?) so format section can be produced // call walker to count cells (and rows?) so format section can be produced
columns := countColumns(node) columns := countColumns(node)
out(w, strings.Repeat("l ", columns)+"\n") out(w, strings.Repeat("l ", columns)+"\n")
out(w, strings.Repeat("l ", columns)+".\n") out(w, strings.Repeat("l ", columns)+".\n")
@ -261,28 +248,41 @@ func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering
} }
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) { func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
var (
start, end string
)
if node.IsHeader {
start = codespanTag
end = codespanCloseTag
}
if entering { if entering {
var start string
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell { if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
out(w, "\t"+start) start = "\t"
} else {
out(w, start)
} }
if node.IsHeader {
start += codespanTag
} else if nodeLiteralSize(node) > 30 {
start += tableCellStart
}
out(w, start)
} else { } else {
// need to carriage return if we are at the end of the header row var end string
if node.IsHeader && node.Next == nil { if node.IsHeader {
end = end + crTag end = codespanCloseTag
} else if nodeLiteralSize(node) > 30 {
end = tableCellEnd
}
if node.Next == nil && end != tableCellEnd {
// Last cell: need to carriage return if we are at the end of the
// header row and content isn't wrapped in a "tablecell"
end += crTag
} }
out(w, end) out(w, end)
} }
} }
func nodeLiteralSize(node *blackfriday.Node) int {
total := 0
for n := node.FirstChild; n != nil; n = n.FirstChild {
total += len(n.Literal)
}
return total
}
// because roff format requires knowing the column count before outputting any table // because roff format requires knowing the column count before outputting any table
// data we need to walk a table tree and count the columns // data we need to walk a table tree and count the columns
func countColumns(node *blackfriday.Node) int { func countColumns(node *blackfriday.Node) int {
@ -309,15 +309,6 @@ func out(w io.Writer, output string) {
io.WriteString(w, output) // nolint: errcheck io.WriteString(w, output) // nolint: errcheck
} }
func needsBackslash(c byte) bool {
for _, r := range []byte("-_&\\~") {
if c == r {
return true
}
}
return false
}
func escapeSpecialChars(w io.Writer, text []byte) { func escapeSpecialChars(w io.Writer, text []byte) {
for i := 0; i < len(text); i++ { for i := 0; i < len(text); i++ {
// escape initial apostrophe or period // escape initial apostrophe or period
@ -328,7 +319,7 @@ func escapeSpecialChars(w io.Writer, text []byte) {
// directly copy normal characters // directly copy normal characters
org := i org := i
for i < len(text) && !needsBackslash(text[i]) { for i < len(text) && text[i] != '\\' {
i++ i++
} }
if i > org { if i > org {

View File

@ -1,13 +0,0 @@
language: go
script:
- go vet ./...
- go test -v ./...
go:
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- tip

View File

@ -1,97 +0,0 @@
## Migration Guide from v2 -> v3
Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code.
### `Token.Claims` is now an interface type
The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`.
`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property.
The old example for parsing a token looked like this..
```go
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
}
```
is now directly mapped to...
```go
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
claims := token.Claims.(jwt.MapClaims)
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
}
```
`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type.
```go
type MyCustomClaims struct {
User string
*StandardClaims
}
if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil {
claims := token.Claims.(*MyCustomClaims)
fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt)
}
```
### `ParseFromRequest` has been moved
To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`.
`Extractors` do the work of picking the token string out of a request. The interface is simple and composable.
This simple parsing example:
```go
if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
}
```
is directly mapped to:
```go
if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil {
claims := token.Claims.(jwt.MapClaims)
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
}
```
There are several concrete `Extractor` types provided for your convenience:
* `HeaderExtractor` will search a list of headers until one contains content.
* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content.
* `MultiExtractor` will try a list of `Extractors` in order until one returns content.
* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token.
* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument
* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header
### RSA signing methods no longer accept `[]byte` keys
Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse.
To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types.
```go
func keyLookupFunc(*Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
// Look up key
key, err := lookupPublicKey(token.Header["kid"])
if err != nil {
return nil, err
}
// Unpack key from PEM encoded PKCS8
return jwt.ParseRSAPublicKeyFromPEM(key)
}
```

View File

@ -0,0 +1,267 @@
package errcode
import (
"encoding/json"
"fmt"
"strings"
)
// ErrorCoder is the base interface for ErrorCode and Error allowing
// users of each to just call ErrorCode to get the real ID of each
type ErrorCoder interface {
ErrorCode() ErrorCode
}
// ErrorCode represents the error type. The errors are serialized via strings
// and the integer format may change and should *never* be exported.
type ErrorCode int
var _ error = ErrorCode(0)
// ErrorCode just returns itself
func (ec ErrorCode) ErrorCode() ErrorCode {
return ec
}
// Error returns the ID/Value
func (ec ErrorCode) Error() string {
// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
}
// Descriptor returns the descriptor for the error code.
func (ec ErrorCode) Descriptor() ErrorDescriptor {
d, ok := errorCodeToDescriptors[ec]
if !ok {
return ErrorCodeUnknown.Descriptor()
}
return d
}
// String returns the canonical identifier for this error code.
func (ec ErrorCode) String() string {
return ec.Descriptor().Value
}
// Message returned the human-readable error message for this error code.
func (ec ErrorCode) Message() string {
return ec.Descriptor().Message
}
// MarshalText encodes the receiver into UTF-8-encoded text and returns the
// result.
func (ec ErrorCode) MarshalText() (text []byte, err error) {
return []byte(ec.String()), nil
}
// UnmarshalText decodes the form generated by MarshalText.
func (ec *ErrorCode) UnmarshalText(text []byte) error {
desc, ok := idToDescriptors[string(text)]
if !ok {
desc = ErrorCodeUnknown.Descriptor()
}
*ec = desc.Code
return nil
}
// WithMessage creates a new Error struct based on the passed-in info and
// overrides the Message property.
func (ec ErrorCode) WithMessage(message string) Error {
return Error{
Code: ec,
Message: message,
}
}
// WithDetail creates a new Error struct based on the passed-in info and
// set the Detail property appropriately
func (ec ErrorCode) WithDetail(detail interface{}) Error {
return Error{
Code: ec,
Message: ec.Message(),
}.WithDetail(detail)
}
// WithArgs creates a new Error struct and sets the Args slice
func (ec ErrorCode) WithArgs(args ...interface{}) Error {
return Error{
Code: ec,
Message: ec.Message(),
}.WithArgs(args...)
}
// Error provides a wrapper around ErrorCode with extra Details provided.
type Error struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Detail interface{} `json:"detail,omitempty"`
// TODO(duglin): See if we need an "args" property so we can do the
// variable substitution right before showing the message to the user
}
var _ error = Error{}
// ErrorCode returns the ID/Value of this Error
func (e Error) ErrorCode() ErrorCode {
return e.Code
}
// Error returns a human readable representation of the error.
func (e Error) Error() string {
return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
}
// WithDetail will return a new Error, based on the current one, but with
// some Detail info added
func (e Error) WithDetail(detail interface{}) Error {
return Error{
Code: e.Code,
Message: e.Message,
Detail: detail,
}
}
// WithArgs uses the passed-in list of interface{} as the substitution
// variables in the Error's Message string, but returns a new Error
func (e Error) WithArgs(args ...interface{}) Error {
return Error{
Code: e.Code,
Message: fmt.Sprintf(e.Code.Message(), args...),
Detail: e.Detail,
}
}
// ErrorDescriptor provides relevant information about a given error code.
type ErrorDescriptor struct {
// Code is the error code that this descriptor describes.
Code ErrorCode
// Value provides a unique, string key, often captilized with
// underscores, to identify the error code. This value is used as the
// keyed value when serializing api errors.
Value string
// Message is a short, human readable decription of the error condition
// included in API responses.
Message string
// Description provides a complete account of the errors purpose, suitable
// for use in documentation.
Description string
// HTTPStatusCode provides the http status code that is associated with
// this error condition.
HTTPStatusCode int
}
// ParseErrorCode returns the value by the string error code.
// `ErrorCodeUnknown` will be returned if the error is not known.
func ParseErrorCode(value string) ErrorCode {
ed, ok := idToDescriptors[value]
if ok {
return ed.Code
}
return ErrorCodeUnknown
}
// Errors provides the envelope for multiple errors and a few sugar methods
// for use within the application.
type Errors []error
var _ error = Errors{}
func (errs Errors) Error() string {
switch len(errs) {
case 0:
return "<nil>"
case 1:
return errs[0].Error()
default:
msg := "errors:\n"
for _, err := range errs {
msg += err.Error() + "\n"
}
return msg
}
}
// Len returns the current number of errors.
func (errs Errors) Len() int {
return len(errs)
}
// MarshalJSON converts slice of error, ErrorCode or Error into a
// slice of Error - then serializes
func (errs Errors) MarshalJSON() ([]byte, error) {
var tmpErrs struct {
Errors []Error `json:"errors,omitempty"`
}
for _, daErr := range errs {
var err Error
switch daErr.(type) {
case ErrorCode:
err = daErr.(ErrorCode).WithDetail(nil)
case Error:
err = daErr.(Error)
default:
err = ErrorCodeUnknown.WithDetail(daErr)
}
// If the Error struct was setup and they forgot to set the
// Message field (meaning its "") then grab it from the ErrCode
msg := err.Message
if msg == "" {
msg = err.Code.Message()
}
tmpErrs.Errors = append(tmpErrs.Errors, Error{
Code: err.Code,
Message: msg,
Detail: err.Detail,
})
}
return json.Marshal(tmpErrs)
}
// UnmarshalJSON deserializes []Error and then converts it into slice of
// Error or ErrorCode
func (errs *Errors) UnmarshalJSON(data []byte) error {
var tmpErrs struct {
Errors []Error
}
if err := json.Unmarshal(data, &tmpErrs); err != nil {
return err
}
var newErrs Errors
for _, daErr := range tmpErrs.Errors {
// If Message is empty or exactly matches the Code's message string
// then just use the Code, no need for a full Error struct
if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
// Error's w/o details get converted to ErrorCode
newErrs = append(newErrs, daErr.Code)
} else {
// Error's w/ details are untouched
newErrs = append(newErrs, Error{
Code: daErr.Code,
Message: daErr.Message,
Detail: daErr.Detail,
})
}
}
*errs = newErrs
return nil
}

View File

@ -0,0 +1,40 @@
package errcode
import (
"encoding/json"
"net/http"
)
// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err
// and sets the content-type header to 'application/json'. It will handle
// ErrorCoder and Errors, and if necessary will create an envelope.
func ServeJSON(w http.ResponseWriter, err error) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
var sc int
switch errs := err.(type) {
case Errors:
if len(errs) < 1 {
break
}
if err, ok := errs[0].(ErrorCoder); ok {
sc = err.ErrorCode().Descriptor().HTTPStatusCode
}
case ErrorCoder:
sc = errs.ErrorCode().Descriptor().HTTPStatusCode
err = Errors{err} // create an envelope.
default:
// We just have an unhandled error type, so just place in an envelope
// and move along.
err = Errors{err}
}
if sc == 0 {
sc = http.StatusInternalServerError
}
w.WriteHeader(sc)
return json.NewEncoder(w).Encode(err)
}

View File

@ -0,0 +1,138 @@
package errcode
import (
"fmt"
"net/http"
"sort"
"sync"
)
var (
errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{}
idToDescriptors = map[string]ErrorDescriptor{}
groupToDescriptors = map[string][]ErrorDescriptor{}
)
var (
// ErrorCodeUnknown is a generic error that can be used as a last
// resort if there is no situation-specific error message that can be used
ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
Value: "UNKNOWN",
Message: "unknown error",
Description: `Generic error returned when the error does not have an
API classification.`,
HTTPStatusCode: http.StatusInternalServerError,
})
// ErrorCodeUnsupported is returned when an operation is not supported.
ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{
Value: "UNSUPPORTED",
Message: "The operation is unsupported.",
Description: `The operation was unsupported due to a missing
implementation or invalid set of parameters.`,
HTTPStatusCode: http.StatusMethodNotAllowed,
})
// ErrorCodeUnauthorized is returned if a request requires
// authentication.
ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
Value: "UNAUTHORIZED",
Message: "authentication required",
Description: `The access controller was unable to authenticate
the client. Often this will be accompanied by a
Www-Authenticate HTTP response header indicating how to
authenticate.`,
HTTPStatusCode: http.StatusUnauthorized,
})
// ErrorCodeDenied is returned if a client does not have sufficient
// permission to perform an action.
ErrorCodeDenied = Register("errcode", ErrorDescriptor{
Value: "DENIED",
Message: "requested access to the resource is denied",
Description: `The access controller denied access for the
operation on a resource.`,
HTTPStatusCode: http.StatusForbidden,
})
// ErrorCodeUnavailable provides a common error to report unavailability
// of a service or endpoint.
ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{
Value: "UNAVAILABLE",
Message: "service unavailable",
Description: "Returned when a service is not available",
HTTPStatusCode: http.StatusServiceUnavailable,
})
// ErrorCodeTooManyRequests is returned if a client attempts too many
// times to contact a service endpoint.
ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{
Value: "TOOMANYREQUESTS",
Message: "too many requests",
Description: `Returned when a client attempts to contact a
service too many times`,
HTTPStatusCode: http.StatusTooManyRequests,
})
)
var nextCode = 1000
var registerLock sync.Mutex
// Register will make the passed-in error known to the environment and
// return a new ErrorCode
func Register(group string, descriptor ErrorDescriptor) ErrorCode {
registerLock.Lock()
defer registerLock.Unlock()
descriptor.Code = ErrorCode(nextCode)
if _, ok := idToDescriptors[descriptor.Value]; ok {
panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value))
}
if _, ok := errorCodeToDescriptors[descriptor.Code]; ok {
panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code))
}
groupToDescriptors[group] = append(groupToDescriptors[group], descriptor)
errorCodeToDescriptors[descriptor.Code] = descriptor
idToDescriptors[descriptor.Value] = descriptor
nextCode++
return descriptor.Code
}
type byValue []ErrorDescriptor
func (a byValue) Len() int { return len(a) }
func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value }
// GetGroupNames returns the list of Error group names that are registered
func GetGroupNames() []string {
keys := []string{}
for k := range groupToDescriptors {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
// GetErrorCodeGroup returns the named group of error descriptors
func GetErrorCodeGroup(name string) []ErrorDescriptor {
desc := groupToDescriptors[name]
sort.Sort(byValue(desc))
return desc
}
// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are
// registered, irrespective of what group they're in
func GetErrorAllDescriptors() []ErrorDescriptor {
result := []ErrorDescriptor{}
for _, group := range GetGroupNames() {
result = append(result, GetErrorCodeGroup(group)...)
}
sort.Sort(byValue(result))
return result
}

File diff suppressed because it is too large Load Diff

View File

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
Copyright 2013-2016 Docker, Inc. Copyright 2013-2018 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,9 +1,9 @@
Docker Docker
Copyright 2012-2016 Docker, Inc. Copyright 2012-2017 Docker, Inc.
This product includes software developed at Docker, Inc. (https://www.docker.com). This product includes software developed at Docker, Inc. (https://www.docker.com).
This product contains software (https://github.com/kr/pty) developed This product contains software (https://github.com/creack/pty) developed
by Keith Rarick, licensed under the MIT License. by Keith Rarick, licensed under the MIT License.
The following is courtesy of our legal counsel: The following is courtesy of our legal counsel:

42
vendor/github.com/docker/docker/api/README.md generated vendored Normal file
View File

@ -0,0 +1,42 @@
# Working on the Engine API
The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon.
It consists of various components in this repository:
- `api/swagger.yaml` A Swagger definition of the API.
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this.
- `cli/` The command-line client.
- `client/` The Go client used by the command-line client. It can also be used by third-party Go programs.
- `daemon/` The daemon, which serves the API.
## Swagger definition
The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to:
1. Automatically generate documentation.
2. Automatically generate the Go server and client. (A work-in-progress.)
3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc.
## Updating the API documentation
The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, edit this file to represent the change in the documentation.
The file is split into two main sections:
- `definitions`, which defines re-usable objects used in requests and responses
- `paths`, which defines the API endpoints (and some inline objects which don't need to be reusable)
To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section.
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919).
`swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful when making edits to ensure you are doing the right thing.
## Viewing the API documentation
When you make edits to `swagger.yaml`, you may want to check the generated API documentation to ensure it renders correctly.
Run `make swagger-docs` and a preview will be running at `http://localhost`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation.
The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io).

11
vendor/github.com/docker/docker/api/common.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
package api // import "github.com/docker/docker/api"
// Common constants for daemon and client.
const (
// DefaultVersion of Current REST API
DefaultVersion = "1.41"
// NoBaseImageSpecifier is the symbol used by the FROM
// command to specify that no base image is to be used.
NoBaseImageSpecifier = "scratch"
)

6
vendor/github.com/docker/docker/api/common_unix.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
// +build !windows
package api // import "github.com/docker/docker/api"
// MinVersion represents Minimum REST API version supported
const MinVersion = "1.12"

View File

@ -0,0 +1,8 @@
package api // import "github.com/docker/docker/api"
// MinVersion represents Minimum REST API version supported
// Technically the first daemon API version released on Windows is v1.25 in
// engine version 1.13. However, some clients are explicitly using downlevel
// APIs (e.g. docker-compose v2.1 file format) and that is just too restrictive.
// Hence also allowing 1.24 on Windows.
const MinVersion string = "1.24"

12
vendor/github.com/docker/docker/api/swagger-gen.yaml generated vendored Normal file
View File

@ -0,0 +1,12 @@
layout:
models:
- name: definition
source: asset:model
target: "{{ joinFilePath .Target .ModelPackage }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
operations:
- name: handler
source: asset:serverOperation
target: "{{ joinFilePath .Target .APIPackage .Package }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"

11425
vendor/github.com/docker/docker/api/swagger.yaml generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package types package types // import "github.com/docker/docker/api/types"
// AuthConfig contains authorization information for connecting to a Registry // AuthConfig contains authorization information for connecting to a Registry
type AuthConfig struct { type AuthConfig struct {

View File

@ -1,4 +1,4 @@
package blkiodev package blkiodev // import "github.com/docker/docker/api/types/blkiodev"
import "fmt" import "fmt"

View File

@ -1,14 +1,13 @@
package types package types // import "github.com/docker/docker/api/types"
import ( import (
"bufio" "bufio"
"io" "io"
"net" "net"
"os"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/go-units" units "github.com/docker/go-units"
) )
// CheckpointCreateOptions holds parameters to create a checkpoint from a container // CheckpointCreateOptions holds parameters to create a checkpoint from a container
@ -51,7 +50,7 @@ type ContainerCommitOptions struct {
// ContainerExecInspect holds information returned by exec inspect. // ContainerExecInspect holds information returned by exec inspect.
type ContainerExecInspect struct { type ContainerExecInspect struct {
ExecID string ExecID string `json:"ID"`
ContainerID string ContainerID string
Running bool Running bool
ExitCode int ExitCode int
@ -75,6 +74,7 @@ type ContainerLogsOptions struct {
ShowStdout bool ShowStdout bool
ShowStderr bool ShowStderr bool
Since string Since string
Until string
Timestamps bool Timestamps bool
Follow bool Follow bool
Tail string Tail string
@ -98,6 +98,7 @@ type ContainerStartOptions struct {
// about files to copy into a container // about files to copy into a container
type CopyToContainerOptions struct { type CopyToContainerOptions struct {
AllowOverwriteDirWithFile bool AllowOverwriteDirWithFile bool
CopyUIDGID bool
} }
// EventsOptions holds parameters to filter events with. // EventsOptions holds parameters to filter events with.
@ -160,9 +161,10 @@ type ImageBuildOptions struct {
ShmSize int64 ShmSize int64
Dockerfile string Dockerfile string
Ulimits []*units.Ulimit Ulimits []*units.Ulimit
// See the parsing of buildArgs in api/server/router/build/build_routes.go // BuildArgs needs to be a *string instead of just a string so that
// for an explaination of why BuildArgs needs to use *string instead of // we can tell the difference between "" (empty string) and no value
// just a string // at all (nil). See the parsing of buildArgs in
// api/server/router/build/build_routes.go for even more info.
BuildArgs map[string]*string BuildArgs map[string]*string
AuthConfigs map[string]AuthConfig AuthConfigs map[string]AuthConfig
Context io.Reader Context io.Reader
@ -175,8 +177,37 @@ type ImageBuildOptions struct {
// specified here do not need to have a valid parent chain to match cache. // specified here do not need to have a valid parent chain to match cache.
CacheFrom []string CacheFrom []string
SecurityOpt []string SecurityOpt []string
ExtraHosts []string // List of extra hosts
Target string
SessionID string
Platform string
// Version specifies the version of the unerlying builder to use
Version BuilderVersion
// BuildID is an optional identifier that can be passed together with the
// build request. The same identifier can be used to gracefully cancel the
// build with the cancel request.
BuildID string
// Outputs defines configurations for exporting build results. Only supported
// in BuildKit mode
Outputs []ImageBuildOutput
} }
// ImageBuildOutput defines configuration for exporting a build result
type ImageBuildOutput struct {
Type string
Attrs map[string]string
}
// BuilderVersion sets the version of underlying builder to use
type BuilderVersion string
const (
// BuilderV1 is the first generation builder in docker daemon
BuilderV1 BuilderVersion = "1"
// BuilderBuildKit is builder based on moby/buildkit project
BuilderBuildKit BuilderVersion = "2"
)
// ImageBuildResponse holds information // ImageBuildResponse holds information
// returned by a server after building // returned by a server after building
// an image. // an image.
@ -187,13 +218,14 @@ type ImageBuildResponse struct {
// ImageCreateOptions holds information to create images. // ImageCreateOptions holds information to create images.
type ImageCreateOptions struct { type ImageCreateOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
Platform string // Platform is the target platform of the image if it needs to be pulled from the registry.
} }
// ImageImportSource holds source information for ImageImport // ImageImportSource holds source information for ImageImport
type ImageImportSource struct { type ImageImportSource struct {
Source io.Reader // Source is the data to send to the server to create this image from (mutually exclusive with SourceName) Source io.Reader // Source is the data to send to the server to create this image from. You must set SourceName to "-" to leverage this.
SourceName string // SourceName is the name of the image to pull (mutually exclusive with Source) SourceName string // SourceName is the name of the image to pull. Set to "-" to leverage the Source attribute.
} }
// ImageImportOptions holds information to import images from the client host. // ImageImportOptions holds information to import images from the client host.
@ -201,6 +233,7 @@ type ImageImportOptions struct {
Tag string // Tag is the name to tag this image with. This attribute is deprecated. Tag string // Tag is the name to tag this image with. This attribute is deprecated.
Message string // Message is the message to tag the image with Message string // Message is the message to tag the image with
Changes []string // Changes are the raw changes to apply to this image Changes []string // Changes are the raw changes to apply to this image
Platform string // Platform is the target platform of the image
} }
// ImageListOptions holds parameters to filter the list of images with. // ImageListOptions holds parameters to filter the list of images with.
@ -221,6 +254,7 @@ type ImagePullOptions struct {
All bool All bool
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
PrivilegeFunc RequestPrivilegeFunc PrivilegeFunc RequestPrivilegeFunc
Platform string
} }
// RequestPrivilegeFunc is a function interface that // RequestPrivilegeFunc is a function interface that
@ -231,7 +265,7 @@ type ImagePullOptions struct {
// if the privilege request fails. // if the privilege request fails.
type RequestPrivilegeFunc func() (string, error) type RequestPrivilegeFunc func() (string, error)
//ImagePushOptions holds information to push images. // ImagePushOptions holds information to push images.
type ImagePushOptions ImagePullOptions type ImagePushOptions ImagePullOptions
// ImageRemoveOptions holds parameters to remove images. // ImageRemoveOptions holds parameters to remove images.
@ -256,18 +290,6 @@ type ResizeOptions struct {
Width uint Width uint
} }
// VersionResponse holds version information for the client and the server
type VersionResponse struct {
Client *Version
Server *Version
}
// ServerOK returns true when the client could connect to the docker server
// and parse the information received. It returns false otherwise.
func (v VersionResponse) ServerOK() bool {
return v.Server != nil
}
// NodeListOptions holds parameters to list nodes with. // NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct { type NodeListOptions struct {
Filters filters.Args Filters filters.Args
@ -285,6 +307,12 @@ type ServiceCreateOptions struct {
// //
// This field follows the format of the X-Registry-Auth header. // This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string EncodedRegistryAuth string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
} }
// ServiceCreateResponse contains the information returned to a client // ServiceCreateResponse contains the information returned to a client
@ -318,11 +346,33 @@ type ServiceUpdateOptions struct {
// credentials if they are not given in EncodedRegistryAuth. Valid // credentials if they are not given in EncodedRegistryAuth. Valid
// values are "spec" and "previous-spec". // values are "spec" and "previous-spec".
RegistryAuthFrom string RegistryAuthFrom string
// Rollback indicates whether a server-side rollback should be
// performed. When this is set, the provided spec will be ignored.
// The valid values are "previous" and "none". An empty value is the
// same as "none".
Rollback string
// QueryRegistry indicates whether the service update requires
// contacting a registry. A registry may be contacted to retrieve
// the image digest and manifest, which in turn can be used to update
// platform or other information about the service.
QueryRegistry bool
} }
// ServiceListOptions holds parameters to list services with. // ServiceListOptions holds parameters to list services with.
type ServiceListOptions struct { type ServiceListOptions struct {
Filters filters.Args Filters filters.Args
// Status indicates whether the server should include the service task
// count of running and desired tasks.
Status bool
}
// ServiceInspectOptions holds parameters related to the "service inspect"
// operation.
type ServiceInspectOptions struct {
InsertDefaults bool
} }
// TaskListOptions holds parameters to list tasks with. // TaskListOptions holds parameters to list tasks with.
@ -356,15 +406,6 @@ type PluginInstallOptions struct {
Args []string Args []string
} }
// SecretRequestOption is a type for requesting secrets
type SecretRequestOption struct {
Source string
Target string
UID string
GID string
Mode os.FileMode
}
// SwarmUnlockKeyResponse contains the response for Engine API: // SwarmUnlockKeyResponse contains the response for Engine API:
// GET /swarm/unlockkey // GET /swarm/unlockkey
type SwarmUnlockKeyResponse struct { type SwarmUnlockKeyResponse struct {

View File

@ -1,8 +1,9 @@
package types package types // import "github.com/docker/docker/api/types"
import ( import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
specs "github.com/opencontainers/image-spec/specs-go/v1"
) )
// configs holds structs used for internal communication between the // configs holds structs used for internal communication between the
@ -15,6 +16,7 @@ type ContainerCreateConfig struct {
Config *container.Config Config *container.Config
HostConfig *container.HostConfig HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig NetworkingConfig *network.NetworkingConfig
Platform *specs.Platform
AdjustCPUShares bool AdjustCPUShares bool
} }
@ -25,19 +27,6 @@ type ContainerRmConfig struct {
ForceRemove, RemoveVolume, RemoveLink bool ForceRemove, RemoveVolume, RemoveLink bool
} }
// ContainerCommitConfig contains build configs for commit operation,
// and is used when making a commit with the current state of the container.
type ContainerCommitConfig struct {
Pause bool
Repo string
Tag string
Author string
Comment string
// merge container config into commit config before commit
MergeConfigs bool
Config *container.Config
}
// ExecConfig is a small subset of the Config struct that holds the configuration // ExecConfig is a small subset of the Config struct that holds the configuration
// for the exec feature of docker. // for the exec feature of docker.
type ExecConfig struct { type ExecConfig struct {
@ -50,6 +39,7 @@ type ExecConfig struct {
Detach bool // Execute in detach mode Detach bool // Execute in detach mode
DetachKeys string // Escape keys for detach DetachKeys string // Escape keys for detach
Env []string // Environment variables Env []string // Environment variables
WorkingDir string // Working directory
Cmd []string // Execution commands and args Cmd []string // Execution commands and args
} }
@ -67,3 +57,10 @@ type PluginEnableConfig struct {
type PluginDisableConfig struct { type PluginDisableConfig struct {
ForceDisable bool ForceDisable bool
} }
// NetworkListConfig stores the options available for listing networks
type NetworkListConfig struct {
// TODO(@cpuguy83): naming is hard, this is pulled from what was being used in the router before moving here
Detailed bool
Verbose bool
}

View File

@ -1,4 +1,4 @@
package container package container // import "github.com/docker/docker/api/types/container"
import ( import (
"time" "time"
@ -7,6 +7,12 @@ import (
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
) )
// MinimumDuration puts a minimum on user configured duration.
// This is to prevent API error on time unit. For example, API may
// set 3 as healthcheck interval with intention of 3 seconds, but
// Docker interprets it as 3 nanoseconds.
const MinimumDuration = 1 * time.Millisecond
// HealthConfig holds configuration settings for the HEALTHCHECK feature. // HealthConfig holds configuration settings for the HEALTHCHECK feature.
type HealthConfig struct { type HealthConfig struct {
// Test is the test to perform to check that the container is healthy. // Test is the test to perform to check that the container is healthy.
@ -21,6 +27,7 @@ type HealthConfig struct {
// Zero means to inherit. Durations are expressed as integer nanoseconds. // Zero means to inherit. Durations are expressed as integer nanoseconds.
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks. Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung. Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.
// Retries is the number of consecutive failures needed to consider a container as unhealthy. // Retries is the number of consecutive failures needed to consider a container as unhealthy.
// Zero means inherit. // Zero means inherit.
@ -47,7 +54,7 @@ type Config struct {
Env []string // List of environment variable to set in the container Env []string // List of environment variable to set in the container
Cmd strslice.StrSlice // Command to run when starting the container Cmd strslice.StrSlice // Command to run when starting the container
Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy
ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (meaning treat as a command line) (Windows specific).
Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) Image string // Name of the image as it was passed by the operator (e.g. could be symbolic)
Volumes map[string]struct{} // List of volumes (mounts) used for the container Volumes map[string]struct{} // List of volumes (mounts) used for the container
WorkingDir string // Current directory (PWD) in the command will be launched WorkingDir string // Current directory (PWD) in the command will be launched

View File

@ -0,0 +1,20 @@
package container // import "github.com/docker/docker/api/types/container"
// ----------------------------------------------------------------------------
// Code generated by `swagger generate operation`. DO NOT EDIT.
//
// See hack/generate-swagger-api.sh
// ----------------------------------------------------------------------------
// ContainerChangeResponseItem change item in response to ContainerChanges operation
// swagger:model ContainerChangeResponseItem
type ContainerChangeResponseItem struct {
// Kind of change
// Required: true
Kind uint8 `json:"Kind"`
// Path to file that has changed
// Required: true
Path string `json:"Path"`
}

View File

@ -1,13 +1,12 @@
package container package container // import "github.com/docker/docker/api/types/container"
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// DO NOT EDIT THIS FILE // Code generated by `swagger generate operation`. DO NOT EDIT.
// This file was generated by `swagger generate operation`
// //
// See hack/swagger-gen.sh // See hack/generate-swagger-api.sh
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// ContainerCreateCreatedBody container create created body // ContainerCreateCreatedBody OK response to ContainerCreate operation
// swagger:model ContainerCreateCreatedBody // swagger:model ContainerCreateCreatedBody
type ContainerCreateCreatedBody struct { type ContainerCreateCreatedBody struct {

View File

@ -0,0 +1,22 @@
package container // import "github.com/docker/docker/api/types/container"
// ----------------------------------------------------------------------------
// Code generated by `swagger generate operation`. DO NOT EDIT.
//
// See hack/generate-swagger-api.sh
// ----------------------------------------------------------------------------
// ContainerTopOKBody OK response to ContainerTop operation
// swagger:model ContainerTopOKBody
type ContainerTopOKBody struct {
// Each process running in the container, where each is process
// is an array of values corresponding to the titles.
//
// Required: true
Processes [][]string `json:"Processes"`
// The ps column titles
// Required: true
Titles []string `json:"Titles"`
}

View File

@ -1,13 +1,12 @@
package container package container // import "github.com/docker/docker/api/types/container"
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// DO NOT EDIT THIS FILE // Code generated by `swagger generate operation`. DO NOT EDIT.
// This file was generated by `swagger generate operation`
// //
// See hack/swagger-gen.sh // See hack/generate-swagger-api.sh
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// ContainerUpdateOKBody container update o k body // ContainerUpdateOKBody OK response to ContainerUpdate operation
// swagger:model ContainerUpdateOKBody // swagger:model ContainerUpdateOKBody
type ContainerUpdateOKBody struct { type ContainerUpdateOKBody struct {

View File

@ -1,16 +1,27 @@
package container package container // import "github.com/docker/docker/api/types/container"
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// DO NOT EDIT THIS FILE // Code generated by `swagger generate operation`. DO NOT EDIT.
// This file was generated by `swagger generate operation`
// //
// See hack/swagger-gen.sh // See hack/generate-swagger-api.sh
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// ContainerWaitOKBody container wait o k body // ContainerWaitOKBodyError container waiting error, if any
// swagger:model ContainerWaitOKBodyError
type ContainerWaitOKBodyError struct {
// Details of an error
Message string `json:"Message,omitempty"`
}
// ContainerWaitOKBody OK response to ContainerWait operation
// swagger:model ContainerWaitOKBody // swagger:model ContainerWaitOKBody
type ContainerWaitOKBody struct { type ContainerWaitOKBody struct {
// error
// Required: true
Error *ContainerWaitOKBodyError `json:"Error"`
// Exit code of the container // Exit code of the container
// Required: true // Required: true
StatusCode int64 `json:"StatusCode"` StatusCode int64 `json:"StatusCode"`

View File

@ -1,4 +1,4 @@
package container package container // import "github.com/docker/docker/api/types/container"
import ( import (
"strings" "strings"
@ -7,11 +7,31 @@ import (
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/strslice" "github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/docker/go-units" units "github.com/docker/go-units"
) )
// NetworkMode represents the container network stack. // CgroupnsMode represents the cgroup namespace mode of the container
type NetworkMode string type CgroupnsMode string
// IsPrivate indicates whether the container uses its own private cgroup namespace
func (c CgroupnsMode) IsPrivate() bool {
return c == "private"
}
// IsHost indicates whether the container shares the host's cgroup namespace
func (c CgroupnsMode) IsHost() bool {
return c == "host"
}
// IsEmpty indicates whether the container cgroup namespace mode is unset
func (c CgroupnsMode) IsEmpty() bool {
return c == ""
}
// Valid indicates whether the cgroup namespace mode is valid
func (c CgroupnsMode) Valid() bool {
return c.IsEmpty() || c.IsPrivate() || c.IsHost()
}
// Isolation represents the isolation technology of a container. The supported // Isolation represents the isolation technology of a container. The supported
// values are platform specific // values are platform specific
@ -23,42 +43,101 @@ func (i Isolation) IsDefault() bool {
return strings.ToLower(string(i)) == "default" || string(i) == "" return strings.ToLower(string(i)) == "default" || string(i) == ""
} }
// IsHyperV indicates the use of a Hyper-V partition for isolation
func (i Isolation) IsHyperV() bool {
return strings.ToLower(string(i)) == "hyperv"
}
// IsProcess indicates the use of process isolation
func (i Isolation) IsProcess() bool {
return strings.ToLower(string(i)) == "process"
}
const (
// IsolationEmpty is unspecified (same behavior as default)
IsolationEmpty = Isolation("")
// IsolationDefault is the default isolation mode on current daemon
IsolationDefault = Isolation("default")
// IsolationProcess is process isolation mode
IsolationProcess = Isolation("process")
// IsolationHyperV is HyperV isolation mode
IsolationHyperV = Isolation("hyperv")
)
// IpcMode represents the container ipc stack. // IpcMode represents the container ipc stack.
type IpcMode string type IpcMode string
// IsPrivate indicates whether the container uses its private ipc stack. // IsPrivate indicates whether the container uses its own private ipc namespace which can not be shared.
func (n IpcMode) IsPrivate() bool { func (n IpcMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer()) return n == "private"
} }
// IsHost indicates whether the container uses the host's ipc stack. // IsHost indicates whether the container shares the host's ipc namespace.
func (n IpcMode) IsHost() bool { func (n IpcMode) IsHost() bool {
return n == "host" return n == "host"
} }
// IsContainer indicates whether the container uses a container's ipc stack. // IsShareable indicates whether the container's ipc namespace can be shared with another container.
func (n IpcMode) IsShareable() bool {
return n == "shareable"
}
// IsContainer indicates whether the container uses another container's ipc namespace.
func (n IpcMode) IsContainer() bool { func (n IpcMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2) parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container" return len(parts) > 1 && parts[0] == "container"
} }
// Valid indicates whether the ipc stack is valid. // IsNone indicates whether container IpcMode is set to "none".
func (n IpcMode) IsNone() bool {
return n == "none"
}
// IsEmpty indicates whether container IpcMode is empty
func (n IpcMode) IsEmpty() bool {
return n == ""
}
// Valid indicates whether the ipc mode is valid.
func (n IpcMode) Valid() bool { func (n IpcMode) Valid() bool {
parts := strings.Split(string(n), ":") return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer()
switch mode := parts[0]; mode {
case "", "host":
case "container":
if len(parts) != 2 || parts[1] == "" {
return false
}
default:
return false
}
return true
} }
// Container returns the name of the container ipc stack is going to be used. // Container returns the name of the container ipc stack is going to be used.
func (n IpcMode) Container() string { func (n IpcMode) Container() string {
parts := strings.SplitN(string(n), ":", 2)
if len(parts) > 1 && parts[0] == "container" {
return parts[1]
}
return ""
}
// NetworkMode represents the container network stack.
type NetworkMode string
// IsNone indicates whether container isn't using a network stack.
func (n NetworkMode) IsNone() bool {
return n == "none"
}
// IsDefault indicates whether container uses the default network stack.
func (n NetworkMode) IsDefault() bool {
return n == "default"
}
// IsPrivate indicates whether container uses its private network stack.
func (n NetworkMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer())
}
// IsContainer indicates whether container uses a container network stack.
func (n NetworkMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container"
}
// ConnectedContainer is the id of the container which network this container is connected to.
func (n NetworkMode) ConnectedContainer() string {
parts := strings.SplitN(string(n), ":", 2) parts := strings.SplitN(string(n), ":", 2)
if len(parts) > 1 { if len(parts) > 1 {
return parts[1] return parts[1]
@ -66,6 +145,14 @@ func (n IpcMode) Container() string {
return "" return ""
} }
// UserDefined indicates user-created network
func (n NetworkMode) UserDefined() string {
if n.IsUserDefined() {
return string(n)
}
return ""
}
// UsernsMode represents userns mode in the container. // UsernsMode represents userns mode in the container.
type UsernsMode string type UsernsMode string
@ -180,6 +267,16 @@ func (n PidMode) Container() string {
return "" return ""
} }
// DeviceRequest represents a request for devices from a device driver.
// Used by GPU device drivers.
type DeviceRequest struct {
Driver string // Name of device driver
Count int // Number of devices to request (-1 = All)
DeviceIDs []string // List of device IDs as recognizable by the device driver
Capabilities [][]string // An OR list of AND lists of device capabilities (e.g. "gpu")
Options map[string]string // Options to pass onto the device driver
}
// DeviceMapping represents the device mapping between the host and the container. // DeviceMapping represents the device mapping between the host and the container.
type DeviceMapping struct { type DeviceMapping struct {
PathOnHost string PathOnHost string
@ -223,6 +320,17 @@ func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool {
return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount
} }
// LogMode is a type to define the available modes for logging
// These modes affect how logs are handled when log messages start piling up.
type LogMode string
// Available logging modes
const (
LogModeUnset = ""
LogModeBlocking LogMode = "blocking"
LogModeNonBlock LogMode = "non-blocking"
)
// LogConfig represents the logging configuration of the container. // LogConfig represents the logging configuration of the container.
type LogConfig struct { type LogConfig struct {
Type string Type string
@ -251,13 +359,15 @@ type Resources struct {
CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetCpus string // CpusetCpus 0-2, 0,1
CpusetMems string // CpusetMems 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1
Devices []DeviceMapping // List of devices to map inside the container Devices []DeviceMapping // List of devices to map inside the container
DiskQuota int64 // Disk limit (in bytes) DeviceCgroupRules []string // List of rule to be added to the device cgroup
KernelMemory int64 // Kernel memory limit (in bytes) DeviceRequests []DeviceRequest // List of device requests for device drivers
KernelMemory int64 // Kernel memory limit (in bytes), Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
KernelMemoryTCP int64 // Hard limit for kernel TCP buffer memory (in bytes)
MemoryReservation int64 // Memory soft limit (in bytes) MemoryReservation int64 // Memory soft limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
MemorySwappiness *int64 // Tuning container memory swappiness behaviour MemorySwappiness *int64 // Tuning container memory swappiness behaviour
OomKillDisable *bool // Whether to disable OOM Killer or not OomKillDisable *bool // Whether to disable OOM Killer or not
PidsLimit int64 // Setting pids limit for a container PidsLimit *int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change.
Ulimits []*units.Ulimit // List of ulimits to be set in the container Ulimits []*units.Ulimit // List of ulimits to be set in the container
// Applicable to Windows // Applicable to Windows
@ -293,6 +403,7 @@ type HostConfig struct {
// Applicable to UNIX platforms // Applicable to UNIX platforms
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container
DNS []string `json:"Dns"` // List of DNS server to lookup DNS []string `json:"Dns"` // List of DNS server to lookup
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
@ -317,7 +428,7 @@ type HostConfig struct {
// Applicable to Windows // Applicable to Windows
ConsoleSize [2]uint // Initial console size (height,width) ConsoleSize [2]uint // Initial console size (height,width)
Isolation Isolation // Isolation technology of the container (eg default, hyperv) Isolation Isolation // Isolation technology of the container (e.g. default, hyperv)
// Contains container's resources (cgroups, ulimits) // Contains container's resources (cgroups, ulimits)
Resources Resources
@ -325,9 +436,12 @@ type HostConfig struct {
// Mounts specs used by the container // Mounts specs used by the container
Mounts []mount.Mount `json:",omitempty"` Mounts []mount.Mount `json:",omitempty"`
// MaskedPaths is the list of paths to be masked inside the container (this overrides the default set of paths)
MaskedPaths []string
// ReadonlyPaths is the list of paths to be set as read-only inside the container (this overrides the default set of paths)
ReadonlyPaths []string
// Run a custom init inside the container, if null, use the daemon's configured settings // Run a custom init inside the container, if null, use the daemon's configured settings
Init *bool `json:",omitempty"` Init *bool `json:",omitempty"`
// Custom init path
InitPath string `json:",omitempty"`
} }

Some files were not shown because too many files have changed in this diff Show More