mirror of
https://github.com/strongdm/comply
synced 2025-04-07 07:53:28 +00:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
aab484948d | ||
|
5371b7c8a4 | ||
|
29b667f624 | ||
|
b386a2d227 | ||
|
62001ca96b | ||
|
4735c3c42d | ||
|
4a143af5db | ||
|
e31fef1bb5 | ||
|
5b2a8e93ee | ||
|
298220fe8e | ||
|
5d5773ba71 | ||
|
9e9d23c60d | ||
|
08d3e750fa | ||
|
9bcac77e52 | ||
|
4dc7f94857 | ||
|
c78f9007c2 | ||
|
3d8652f497 | ||
|
f4f46bad4d | ||
|
c959617bf6 | ||
|
ecc021985e | ||
|
f64aed0f60 | ||
|
76fb5ec31a | ||
|
ad2c89ec67 | ||
|
1c80e1ce66 | ||
|
fd4aab67be | ||
|
bc25dae339 | ||
|
fb6d1c0bdd | ||
|
55fa4a7fd6 | ||
|
6d2d387aa7 | ||
|
f3717bea38 | ||
|
53a65f6c00 | ||
|
373e7737ab | ||
|
b1e88ea919 | ||
|
ac11b307ec | ||
|
26eb39c036 | ||
|
3c19f849d7 | ||
|
34c0105b47 | ||
|
9209a37ccf | ||
|
e3efe3d74f | ||
|
acbd62f4df | ||
|
fffe6dc02f | ||
|
04fd6d351b | ||
|
7b75522ee0 | ||
|
ccb78ddfbd | ||
|
efb59fd233 | ||
|
4370d2292c | ||
|
dbe49a09b3 | ||
|
73bebe0202 | ||
|
f5f7c08b73 | ||
|
b2276f9e54 | ||
|
f1b5bbeff9 | ||
|
b7acb1eecf | ||
|
da8a574e86 | ||
|
7468711b3b | ||
|
8baf787ed7 | ||
|
4c5c18964b | ||
|
022fd7b0d5 | ||
|
d6d12c3206 | ||
|
22f1657411 | ||
|
3262c130be | ||
|
bbbd125408 | ||
|
9fc23274a5 | ||
|
1ab846e396 | ||
|
c5a1bd804b | ||
|
c303b68201 | ||
|
87e8266f1b | ||
|
e8d6d536a8 | ||
|
ef7a4092a7 | ||
|
9bcc88eebc | ||
|
edd3c7dd44 |
.env.example.gitignoreDockerfileGopkg.lockGopkg.tomlMakefileREADME.mdSUPPORT.mdVERSIONgo.modgo.sum
build
comply.goexample
fixtures
narratives
policies
procedures
standards
internal
cli
config
gitlab
model
path
plugin/github
render
theme
util
themes
tools.govendor/github.com
Clever/gitsem
Microsoft/go-winio
CODEOWNERSREADME.mdea.gofile.gofileinfo.gohvsock.gopipe.go
pkg/guid
privilege.gosyscall.gozsyscall_windows.goaktau/github-release
.gitignoreCONTRIBUTING.mdGopkg.lockGopkg.tomlLICENSEMakefileREADME.mdassets.gocmd.gocommit.goerror.gogithub-release.goreleasereleases.gotags.goterm.goutil.goversion.go
andygrunwald/go-jira
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
||||
COMPLY_USE_LOCAL_PANDOC=
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,3 +2,7 @@ comply
|
||||
output
|
||||
dist
|
||||
.envrc
|
||||
bindata.go
|
||||
.idea/
|
||||
.env
|
||||
.vscode/
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM strongdm/pandoc:latest
|
||||
FROM strongdm/pandoc:edge
|
||||
|
||||
# based on implementation by James Gregory <james@jagregory.com>
|
||||
MAINTAINER Comply <comply@strongdm.com>
|
||||
@ -6,7 +6,10 @@ MAINTAINER Comply <comply@strongdm.com>
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y curl
|
||||
|
||||
ENV COMPLY_VERSION "1.3.7"
|
||||
ARG COMPLY_VERSION
|
||||
ENV COMPLY_VERSION ${COMPLY_VERSION:-1.6.0}
|
||||
|
||||
EXPOSE 4000/tcp
|
||||
|
||||
# install comply binary
|
||||
RUN curl -J -L -o /tmp/comply.tgz https://github.com/strongdm/comply/releases/download/v${COMPLY_VERSION}/comply-v${COMPLY_VERSION}-linux-amd64.tgz \
|
||||
|
289
Gopkg.lock
generated
289
Gopkg.lock
generated
@ -1,289 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Microsoft/go-winio"
|
||||
packages = ["."]
|
||||
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
|
||||
version = "v0.4.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/andygrunwald/go-jira"
|
||||
packages = ["."]
|
||||
revision = "5cfdb85cc91c6299f75b6504a1d0ec174c21be39"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/chzyer/readline"
|
||||
packages = ["."]
|
||||
revision = "f6d7a1f6fbf35bbf9beb80dc63c56a29dcfb759f"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/distribution"
|
||||
packages = [
|
||||
"digest",
|
||||
"reference"
|
||||
]
|
||||
revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89"
|
||||
version = "v2.6.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/docker"
|
||||
packages = [
|
||||
"api/types",
|
||||
"api/types/blkiodev",
|
||||
"api/types/container",
|
||||
"api/types/events",
|
||||
"api/types/filters",
|
||||
"api/types/mount",
|
||||
"api/types/network",
|
||||
"api/types/reference",
|
||||
"api/types/registry",
|
||||
"api/types/strslice",
|
||||
"api/types/swarm",
|
||||
"api/types/time",
|
||||
"api/types/versions",
|
||||
"api/types/volume",
|
||||
"client",
|
||||
"pkg/tlsconfig"
|
||||
]
|
||||
revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
|
||||
version = "v1.13.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/go-connections"
|
||||
packages = [
|
||||
"nat",
|
||||
"sockets",
|
||||
"tlsconfig"
|
||||
]
|
||||
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/go-units"
|
||||
packages = ["."]
|
||||
revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52"
|
||||
version = "v0.3.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/elazarl/go-bindata-assetfs"
|
||||
packages = ["."]
|
||||
revision = "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
revision = "507f6050b8568533fb3f5504de8e5205fa62a114"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fatih/structs"
|
||||
packages = ["."]
|
||||
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gohugoio/hugo"
|
||||
packages = ["watcher"]
|
||||
revision = "f414966b942b5aad75565bee6c644782a07f0658"
|
||||
version = "v0.37.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/google/go-github"
|
||||
packages = ["github"]
|
||||
revision = "e48060a28fac52d0f1cb758bc8b87c07bac4a87d"
|
||||
version = "v15.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-querystring"
|
||||
packages = ["query"]
|
||||
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jcelliott/lumber"
|
||||
packages = ["."]
|
||||
revision = "dd349441af25132d146d7095c6693a15431fc9b1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/juju/ansiterm"
|
||||
packages = [
|
||||
".",
|
||||
"tabwriter"
|
||||
]
|
||||
revision = "720a0952cc2ac777afc295d9861263e2a4cf96a1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/lunixbochs/vtclean"
|
||||
packages = ["."]
|
||||
revision = "d14193dfc626125c831501c1c42340b4248e1f5a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/manifoldco/promptui"
|
||||
packages = [
|
||||
".",
|
||||
"list",
|
||||
"screenbuf"
|
||||
]
|
||||
revision = "c0c0d3afc6a03bcb5c1df10b70b862a650db9f9b"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/nanobox-io/golang-scribble"
|
||||
packages = ["."]
|
||||
revision = "ced58d671850da57ce8c11315424513b608083d7"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/olekukonko/tablewriter"
|
||||
packages = ["."]
|
||||
revision = "b8a9be070da40449e501c3c4730a889e42d87a9e"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/robfig/cron"
|
||||
packages = ["."]
|
||||
revision = "b024fc5ea0e34bc3f83d9941c8d60b0622bfaca4"
|
||||
version = "v1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/skratchdot/open-golang"
|
||||
packages = ["open"]
|
||||
revision = "75fb7ed4208cf72d323d7d02fd1a5964a7a9073c"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/trivago/tgo"
|
||||
packages = [
|
||||
"tcontainer",
|
||||
"treflect"
|
||||
]
|
||||
revision = "e4d1ddd28c17dd89ed26327cf69fded22060671b"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/urfave/cli"
|
||||
packages = ["."]
|
||||
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
||||
version = "v1.20.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/xanzy/go-gitlab"
|
||||
packages = ["."]
|
||||
revision = "79dad8e74fd097eb2e0fd0883f1978213e88107a"
|
||||
version = "v0.10.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/yosssi/ace"
|
||||
packages = ["."]
|
||||
revision = "ea038f4770b6746c3f8f84f14fa60d9fe1205b56"
|
||||
version = "v0.0.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"proxy"
|
||||
]
|
||||
revision = "d0aafc73d5cdc42264b0af071c261abac580695e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"internal"
|
||||
]
|
||||
revision = "7af32f14d0a25aec7873e0683e8e48dcead159a8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "dd2ff4accc098aceecb86b36eaa7829b2a17b1c9"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
"internal",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch"
|
||||
]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5"
|
||||
version = "v2.1.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "50dc710fa4b581d4fd6907e0e3337c53c3d530e46c53aa71ef9f8e80d3a54d8b"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
30
Gopkg.toml
30
Gopkg.toml
@ -1,30 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
26
Makefile
26
Makefile
@ -3,10 +3,9 @@ GO_SOURCES := $(shell find . -name '*.go')
|
||||
THEME_SOURCES := $(shell find themes)
|
||||
|
||||
assets: $(THEME_SOURCES)
|
||||
@go get github.com/jteeuwen/go-bindata/...
|
||||
@go get github.com/elazarl/go-bindata-assetfs/...
|
||||
@go install github.com/elazarl/go-bindata-assetfs
|
||||
go-bindata-assetfs -o bindata.go -pkg theme -prefix themes themes/...
|
||||
go install -mod=vendor github.com/containous/go-bindata/go-bindata
|
||||
go install -mod=vendor github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs
|
||||
go-bindata-assetfs -pkg theme -prefix themes themes/...
|
||||
mv bindata.go internal/theme/themes_bindata.go
|
||||
|
||||
comply: assets $(GO_SOURCES)
|
||||
@ -53,8 +52,8 @@ else
|
||||
endif
|
||||
|
||||
docker:
|
||||
cd build && docker build -t strongdm/pandoc .
|
||||
docker push strongdm/pandoc
|
||||
cd build && docker build -t strongdm/pandoc:edge .
|
||||
docker push strongdm/pandoc:edge
|
||||
|
||||
cleanse:
|
||||
git checkout --orphan newbranch
|
||||
@ -72,6 +71,9 @@ endif
|
||||
ifndef COMPLY_TAPDIR
|
||||
$(error COMPLY_TAPDIR must be set to the path of the comply homebrew tap repo)
|
||||
endif
|
||||
ifndef COMPLY_RELEASE_WEBHOOK
|
||||
$(error COMPLY_RELEASE_WEBHOOK must be set to a webhook for the release Slack channel)
|
||||
endif
|
||||
|
||||
release: release-env dist release-deps
|
||||
$(eval VERSION := $(shell git describe --tags --always --dirty="-dev"))
|
||||
@ -105,11 +107,15 @@ release: release-env dist release-deps
|
||||
|
||||
patch-release: release-env patch release
|
||||
$(eval VERSION := $(shell git describe --tags --always --dirty="-dev"))
|
||||
curl -X POST --data-urlencode 'payload={"channel": "#release", "username": "release", "text": "comply $(VERSION) released", "icon_emoji": ":shipit:"}' https://hooks.slack.com/services/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
|
||||
curl -X POST --data-urlencode 'payload={"channel": "#release", "username": "release", "text": "comply $(VERSION) released", "icon_emoji": ":shipit:"}' $$COMPLY_RELEASE_WEBHOOK
|
||||
|
||||
minor-release: release-env minor release
|
||||
$(eval VERSION := $(shell git describe --tags --always --dirty="-dev"))
|
||||
curl -X POST --data-urlencode 'payload={"channel": "#release", "username": "release", "text": "comply $(VERSION) released", "icon_emoji": ":shipit:"}' https://hooks.slack.com/services/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
|
||||
curl -X POST --data-urlencode 'payload={"channel": "#release", "username": "release", "text": "comply $(VERSION) released", "icon_emoji": ":shipit:"}' $$COMPLY_RELEASE_WEBHOOK
|
||||
|
||||
docker-release:
|
||||
docker build --build-arg COMPLY_VERSION=`cat VERSION` -t strongdm/comply .
|
||||
docker push strongdm/comply
|
||||
|
||||
patch: clean gitsem
|
||||
gitsem -m "increment patch for release (via Makefile)" patch
|
||||
@ -124,7 +130,7 @@ minor: clean gitsem
|
||||
release-deps: gitsem gh-release
|
||||
|
||||
gitsem:
|
||||
go get -u github.com/Clever/gitsem
|
||||
go install github.com/Clever/gitsem
|
||||
|
||||
gh-release:
|
||||
go get -u github.com/aktau/github-release
|
||||
go install github.com/aktau/github-release
|
58
README.md
58
README.md
@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
Comply is a SOC2-focused compliance automation tool:
|
||||
|
||||
@ -34,24 +34,27 @@ Once `comply init` is complete, just `git init` and `git push` your project to a
|
||||
|
||||
# Discussion
|
||||
|
||||
Join us in [Comply Users](https://join.slack.com/t/comply-users/shared_invite/enQtMzU3MTk5MDkxNDU4LTMwYzZkMjA4YjQ2YTM5Zjc0NTAyYWY5MDBlOGMwMzRmZTk5YzBlOTRiMTVlNGRlZjY1MTY1NDE0MjY5ZjYwNWU)
|
||||
Join us in [Comply Users](https://join.slack.com/t/comply-users/shared_invite/zt-4k3f46wy-Cs1DceznNvAL~lnW9_HjIA)
|
||||
|
||||
# Screenshots
|
||||
|
||||
[Demo video](https://vimeo.com/270257486)
|
||||
|
||||
## Start a Project
|
||||
|
||||

|
||||
|
||||
## Build PDFs
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Track Policy Coverage
|
||||
|
||||

|
||||
|
||||
## Dashboard
|
||||
|
||||

|
||||
|
||||
## Dependencies
|
||||
@ -83,24 +86,59 @@ COMMANDS:
|
||||
Comply is currently only released for Linux and macOS, however from other operating systems it's possible to run using Docker:
|
||||
|
||||
```
|
||||
# first build a copy of the container using the included Dockerfile in the root of this project
|
||||
$ docker build -t comply .
|
||||
# first pull the latest published docker image
|
||||
$ docker pull strongdm/comply
|
||||
|
||||
# from an empty directory that will contain your comply project
|
||||
$ docker run -it --rm -v "$PWD":/source comply
|
||||
$ docker run --rm -v "$PWD":/source -p 4000:4000 -it strongdm/comply
|
||||
root@ec4544732298:/source# comply init
|
||||
✗ Organization Name:
|
||||
|
||||
# serve content live from an established project
|
||||
$ docker run --rm -v "$PWD":/source -p 4000:4000 -it strongdm/comply
|
||||
root@ae4d499583fc:/source# comply serve
|
||||
Serving content of output/ at http://127.0.0.1:4000 (ctrl-c to quit)
|
||||
```
|
||||
|
||||
For Windows users, replace $PWD with the full path to your project directory
|
||||
|
||||
### Running in macOS M1
|
||||
|
||||
If you're running Comply inside Docker, or using it installed by HomeBrew, in a macOS M1, you should increase the Docker allocatable memory space to ~7 GB, but it won't run smoothly. So, we recommend to run Comply locally with pandoc binary installed via HomeBrew. For that, install the `pandoc` and `basictex` packages using the following command:
|
||||
|
||||
```bash
|
||||
brew install pandoc basictex
|
||||
```
|
||||
|
||||
Then when running the Comply binary -installed by HomeBrew- it will work as expected.
|
||||
|
||||
## Ticketing Integrations:
|
||||
|
||||
- Jira
|
||||
- Github
|
||||
- Gitlab
|
||||
|
||||
## Configuring Jira
|
||||
## Configuration
|
||||
|
||||
## GitHub
|
||||
|
||||
Ticketing integration with GitHub can be configured with the following YAML in `comply.yml`:
|
||||
|
||||
```yaml
|
||||
tickets:
|
||||
github:
|
||||
repo: <repo-name>
|
||||
token: <token>
|
||||
username: org or personal username
|
||||
```
|
||||
|
||||
If you're setting up the repo in your personal account, set `username` to your username.
|
||||
If you're setting up the repo in an github organization, set `username` to your org's username instead.
|
||||
|
||||
Also, `GITHUB_REPO`, `GITHUB_TOKEN`, and `GITHUB_USERNAME` can be used to override values from the YAML file.
|
||||
|
||||
### Jira
|
||||
|
||||
When comply creates a ticket (through `proc`, for instance), it sets the following fields.
|
||||
|
||||
- assignee
|
||||
@ -111,12 +149,12 @@ When comply creates a ticket (through `proc`, for instance), it sets the followi
|
||||
- reporter
|
||||
- summary
|
||||
|
||||
Please make sure that the default *Create Screen* has all of those fields enabled. Additionally, make sure that there are no other required fields for the issue type you choose.
|
||||
|
||||
|
||||
Please make sure that the default _Create Screen_ has all of those fields enabled. Additionally, make sure that there are no other required fields for the issue type you choose.
|
||||
|
||||
About authentication, you need to create an [API Token](https://id.atlassian.com/manage-profile/security/api-tokens) to use as a password.
|
||||
|
||||
## Forking and local development
|
||||
|
||||
> Assumes installation of golang and configuration of GOPATH in .bash_profile, .zshrc, etc
|
||||
> Inspiration: http://code.openark.org/blog/development/forking-golang-repositories-on-github-and-managing-the-import-path
|
||||
|
||||
|
5
SUPPORT.md
Normal file
5
SUPPORT.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Support
|
||||
|
||||
All questions/support related to Comply would be kindly addressed by the community, you can reach out to us via:
|
||||
* [Issues](https://github.com/strongdm/comply/issues), or
|
||||
* [Slack](https://join.slack.com/t/comply-users/shared_invite/zt-4k3f46wy-Cs1DceznNvAL~lnW9_HjIA)
|
2
VERSION
2
VERSION
@ -1 +1 @@
|
||||
1.4.0
|
||||
1.6.0
|
@ -1,29 +1,10 @@
|
||||
FROM haskell:latest
|
||||
FROM pandoc/ubuntu
|
||||
|
||||
# based on implementation by James Gregory <james@jagregory.com>
|
||||
MAINTAINER Comply <comply@strongdm.com>
|
||||
|
||||
# install latex packages
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y -o Acquire::Retries=10 --no-install-recommends \
|
||||
texlive-latex-base \
|
||||
texlive-xetex \
|
||||
texlive-fonts-recommended \
|
||||
latex-xcolor \
|
||||
texlive-latex-extra \
|
||||
fontconfig \
|
||||
unzip \
|
||||
lmodern
|
||||
|
||||
# will ease up the update process
|
||||
# updating this env variable will trigger the automatic build of the Docker image
|
||||
ENV PANDOC_VERSION "2.2.1"
|
||||
|
||||
# install pandoc
|
||||
RUN cabal update && cabal install pandoc-${PANDOC_VERSION}
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y -o Acquire::Retries=10 --no-install-recommends \
|
||||
texlive-latex-base \
|
||||
texlive-latex-extra \
|
||||
texlive-plain-generic \
|
||||
lmodern
|
||||
|
||||
WORKDIR /source
|
||||
|
||||
ENTRYPOINT ["/root/.cabal/bin/pandoc"]
|
||||
|
||||
CMD ["--help"]
|
||||
|
12
comply.go
12
comply.go
@ -1,7 +1,17 @@
|
||||
package main
|
||||
|
||||
import "github.com/strongdm/comply/internal/cli"
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/strongdm/comply/internal/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
_, b, _, _ := runtime.Caller(0)
|
||||
basepath := filepath.Dir(b)
|
||||
godotenv.Load(fmt.Sprintf("%s/.env", basepath))
|
||||
cli.Main()
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ Assets are built using [`comply`](https://comply.strongdm.com), which can be ins
|
||||
|
||||
# Publishing
|
||||
|
||||
The `output/` directory contains all generated assets. Links in the HTML dashboard a relative, and all dependencies are included via direct CDN references. The entire `output/` directory therefore may be uploaded to an S3 bucket or other static asset host without further modification.
|
||||
The `output/` directory contains all generated assets. Links in the HTML dashboard are relative, and all dependencies are included via direct CDN references. The entire `output/` directory, therefore, may be uploaded to an S3 bucket or another static asset host without further modification.
|
||||
|
||||
# Dashboard Status
|
||||
|
||||
@ -57,4 +57,4 @@ comply build
|
||||
|
||||
# publish static site from output/ directory
|
||||
upload.sh output/
|
||||
```
|
||||
```
|
||||
|
@ -16,7 +16,7 @@ majorRevisions:
|
||||
|
||||
a. This policy applies to all employees and contractors. They are bound by their Employment Offer Letter or Independent Contractor Agreement to follow the Code of Conduct Policy while performing their duties. The Code of Conduct is outlined below:
|
||||
|
||||
#Policy
|
||||
# Policy
|
||||
|
||||
a. *Compliance with law*
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
94
fixtures/narratives/invalid-control.md
Normal file
94
fixtures/narratives/invalid-control.md
Normal file
@ -0,0 +1,94 @@
|
||||
name: Control Environment Narrative
|
||||
acronym: CEN
|
||||
satisfies:
|
||||
TSC:
|
||||
- CC2.1
|
||||
- CC2.2
|
||||
- CC2.3
|
||||
- CC4.1
|
||||
- CC4.2
|
||||
- CC5.1
|
||||
- CC5.2
|
||||
- CC5.3
|
||||
majorRevisions:
|
||||
- date: Jun 1 2018
|
||||
comment: Initial document
|
||||
---
|
||||
|
||||
# Control Environment Narrative
|
||||
|
||||
The following provides a description of the control structure of {{.Name}}.
|
||||
|
||||
The intent of this description is to enumerate the logical, policy, and procedural controls that serve to monitor {{.Name}}'s application and data security. Changes uncovered by these procedures in the logical, policy, procedural, or customer environment are addressed by remediations specific to the noted change.
|
||||
|
||||
# Logical Controls
|
||||
|
||||
{{.Name}} employs several logical controls to protect confidential data and ensure normal operation of its core product.
|
||||
|
||||
- Mandatory data encryption at rest and in motion
|
||||
- Multi-factor authentication for access to cloud infrastructure
|
||||
- Activity and anomaly monitoring on production systems
|
||||
- Vulnerability management program
|
||||
|
||||
# Policy Controls
|
||||
|
||||
{{.Name}} employs several policy controls to protect confidential data and ensure normal operation of its core product. These policies include, but are not limited to:
|
||||
|
||||
- Access Control Policy
|
||||
- Encryption Policy
|
||||
- Office Security Policy
|
||||
- Password Policy
|
||||
- Policy Training Policy
|
||||
- Vendor Policy
|
||||
- Workstation Policy
|
||||
|
||||
# Procedural Controls
|
||||
|
||||
{{.Name}} has numerous scheduled procedures to monitor and tune the effectiveness of ongoing security controls, and a series of event-driven procedures to respond to security-related events.
|
||||
|
||||
TODO: Finalize these lists
|
||||
|
||||
## Scheduled Security and Audit Procedures
|
||||
|
||||
- Review Access [quarterly]
|
||||
- Review Security Logs [weekly]
|
||||
- Review Cyber Risk Assessment (enumerate possible compromise scenarios) [quarterly]
|
||||
- Review Data Classification [quarterly]
|
||||
- Backup Testing [quarterly]
|
||||
- Disaster Recovery Testing [semi-annual]
|
||||
- Review Devices & Workstations [quarterly]
|
||||
- Review & Clear Low-Priority Alerts [weekly]
|
||||
- Apply OS Patches [monthly]
|
||||
- Verify Data Disposal per Retention Policy [quarterly]
|
||||
- Conduct Security Training [annual]
|
||||
- Review Security Monitoring and Alerting Configuration [quarterly]
|
||||
- Penetration Test [annual]
|
||||
- Whitebox Security Review [annual]
|
||||
- SOC2 Audit [annual]
|
||||
|
||||
## Event-Driven Security and Audit Procedures
|
||||
|
||||
- Onboard Employee
|
||||
- Offboard Employee
|
||||
- Investigate Security Alert
|
||||
- Investigate Security Incident
|
||||
|
||||
# Remediations
|
||||
|
||||
{{.Name}} uses the outcomes of the aforementioned controls and procedures to identify shortcomings in the existing control environment. Once identified, these shortcomes are remediated by improving existing controls and procedures, and creating new controls and procedures as needed.
|
||||
|
||||
# Communications
|
||||
|
||||
{{.Name}} communicates relevant information regarding the functioning of the above controls with internal and external parties on an as-needed basis and according to statutory requirements.
|
||||
|
||||
## Internal
|
||||
|
||||
{{.Name}} communicates control outcomes, anomalies, and remediations internally using the following channels:
|
||||
|
||||
- Slack
|
||||
- Email
|
||||
- Github ticketing
|
||||
|
||||
## External
|
||||
|
||||
{{.Name}} communicates relevant control-related information to external parties including shareholders, customers, contractors, regulators, and government entities as needed according to contractual and regulatory/statutory obligation.
|
55
fixtures/policies/invalid-access.md
Normal file
55
fixtures/policies/invalid-access.md
Normal file
@ -0,0 +1,55 @@
|
||||
name: Access Onboarding and Termination Policy
|
||||
acronym: AOTP
|
||||
satisfies:
|
||||
TSC:
|
||||
- CC6.1
|
||||
- CC6.2
|
||||
- CC6.3
|
||||
majorRevisions:
|
||||
- date: Jun 1 2018
|
||||
comment: Initial document
|
||||
---
|
||||
# Purpose and Scope
|
||||
|
||||
a. The purpose of this policy is to define procedures to onboard and offboard users to technical infrastructure in a manner that minimizes the risk of information loss or exposure.
|
||||
|
||||
a. This policy applies to all technical infrastructure within the organization.
|
||||
|
||||
a. This policy applies to all full-time and part-time employees and contractors.
|
||||
|
||||
# Background
|
||||
|
||||
a. In order to minimize the risk of information loss or exposure (from both inside and outside the organization), the organization is reliant on the principle of least privilege. Account creation and permission levels are restricted to only the resources absolutely needed to perform each person’s job duties. When a user’s role within the organization changes, those accounts and permission levels are changed/revoked to fit the new role and disabled when the user leaves the organization altogether.
|
||||
|
||||
# Policy
|
||||
|
||||
a. *During onboarding:*
|
||||
|
||||
i. Hiring Manager informs HR upon hire of a new employee.
|
||||
|
||||
i. HR emails IT to inform them of a new hire and their role.
|
||||
|
||||
i. IT creates a checklist of accounts and permission levels needed for that role.
|
||||
|
||||
i. The owner of each resource reviews and approves account creation and the
|
||||
associated permissions.
|
||||
|
||||
i. IT works with the owner of each resource to set up the user.
|
||||
|
||||
a. *During offboarding:*
|
||||
|
||||
i. Hiring Manager notifies HR when an employee has been terminated.
|
||||
|
||||
i. HR sends a weekly email report to IT summarizing list of users terminated and instructs IT to disable their access.
|
||||
|
||||
i. IT terminates access within five business days from receipt of notification.
|
||||
|
||||
a. *When an employee changes roles within the organization:*
|
||||
|
||||
i. Hiring Manager will inform HR of a change in role.
|
||||
|
||||
i. HR and IT will follow the same steps as outlined in the onboarding and offboarding procedures.
|
||||
|
||||
a. *Review of accounts and permissions:*
|
||||
|
||||
i. Each month, IT and HR will review accounts and permission levels for accuracy.
|
39
fixtures/procedures/invalid-workstation.md
Normal file
39
fixtures/procedures/invalid-workstation.md
Normal file
@ -0,0 +1,39 @@
|
||||
id: "workstation"
|
||||
name: "Collect Workstation Details"cron: "0 0 0 15 4 *"
|
||||
---
|
||||
|
||||
Resolve this ticket by executing the following steps:
|
||||
|
||||
- [ ] Send the communications below
|
||||
- [ ] For any email replies, attach content to this ticket
|
||||
- [ ] Validate responses are received from each
|
||||
|
||||
|
||||
```
|
||||
To: Desktop support
|
||||
Subject: Annual workstation inventory
|
||||
|
||||
Please attach the current workstation inventory to the following ticket: [REPLACE WITH URL TO THIS TICKET]
|
||||
|
||||
The workstation inventory shall include the following fields:
|
||||
* Serial number
|
||||
* Custodian
|
||||
* Full disk encryption status
|
||||
* Malware protection status
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
To: Outsourced Call Center IT
|
||||
Subject: Annual workstation inventory
|
||||
|
||||
As part of our ongoing compliance efforts and per our services agreement, we require a current inventory of workstations in use in the service of our account.
|
||||
|
||||
Please respond to this message with the current inventory.
|
||||
|
||||
The workstation inventory shall include the following fields:
|
||||
* Serial number
|
||||
* Custodian
|
||||
* Full disk encryption status
|
||||
* Malware protection status
|
||||
```
|
245
fixtures/standards/invalid-standard.yml
Normal file
245
fixtures/standards/invalid-standard.yml
Normal file
@ -0,0 +1,245 @@
|
||||
name: TSC
|
||||
***CC1.1:
|
||||
family: CC1
|
||||
name: Integrity and Ethics
|
||||
description: The entity demonstrates a commitment to integrity and ethical values
|
||||
CC1.2:
|
||||
family: CC1
|
||||
name: Board Independence
|
||||
description: The board of directors demonstrates independence from management and exercises oversight of the development and performance of internal control
|
||||
CC1.3:
|
||||
family: CC1
|
||||
name: Organizational Structure
|
||||
description: Management establishes, with board oversight, structures, reporting lines, and appropriate authorities and responsibilities in the pursuit of objectives
|
||||
CC1.4:
|
||||
family: CC1
|
||||
name: Hiring, Training and Retention
|
||||
description: The entity demonstrates a commitment to attract, develop, and retain competent individuals in alignment with objectives
|
||||
CC1.5:
|
||||
family: CC1
|
||||
name: Individual Accountability
|
||||
description: The entity holds individuals accountable for their internal control responsibilities in the pursuit of objectives.
|
||||
CC2.1:
|
||||
family: CC2
|
||||
name: Use of Information Systems
|
||||
description: The entity obtains or generates and uses relevant, quality information to support the functioning of internal control
|
||||
CC2.2:
|
||||
family: CC2
|
||||
name: Use of Communication Systems, Internal
|
||||
description: The entity internally communicates information, including objectives and responsibilities for internal control, necessary to support the functioning of internal control
|
||||
CC2.3:
|
||||
family: CC2
|
||||
name: Use of Communication Systems, External
|
||||
description: The entity communicates with external parties regarding matters affecting the functioning of internal control
|
||||
CC3.1:
|
||||
family: CC3
|
||||
name: Objectives
|
||||
description: The entity specifies objectives with sufficient clarity to enable the identification and assessment of risks relating to objectives
|
||||
CC3.2:
|
||||
family: CC3
|
||||
name: Risk to Objectives
|
||||
description: The entity identifies risks to the achievement of its objectives across the entity and analyzes risks as a basis for determining how the risks should be managed
|
||||
CC3.3:
|
||||
family: CC3
|
||||
name: Fraud Risk to Objectives
|
||||
description: The entity considers the potential for fraud in assessing risks to the achievement of objectives
|
||||
CC3.4:
|
||||
family: CC3
|
||||
name: Impact of Changes
|
||||
description: The entity identifies and assesses changes that could significantly impact the system of internal control
|
||||
CC4.1:
|
||||
family: CC4
|
||||
name: Monitoring
|
||||
description: The entity selects, develops, and performs ongoing and/or separate evaluations to ascertain whether the components of internal control are present and functioning
|
||||
CC4.2:
|
||||
family: CC4
|
||||
name: Remediation
|
||||
description: The entity evaluates and communicates internal control deficiencies in a timely manner to those parties responsible for taking corrective action, including senior management and the board of directors, as appropriate
|
||||
CC5.1:
|
||||
family: CC5
|
||||
name: Objective Risk Mitigation
|
||||
description: The entity selects and develops control activities that contribute to the mitigation of risks to the achievement of objectives to acceptable levels
|
||||
CC5.2:
|
||||
family: CC5
|
||||
name: Technology Controls
|
||||
description: The entity also selects and develops general control activities over technology to support the achievement of objectives
|
||||
CC5.3:
|
||||
family: CC5
|
||||
name: Established Policies
|
||||
description: The entity deploys control activities through policies that establish what is expected and in procedures that put policies into action
|
||||
CC6.1:
|
||||
family: CC6
|
||||
name: Logical Access
|
||||
description: The entity implements logical access security software, infrastructure, and architectures over protected information assets to protect them from security events to meet the entity’s objectives
|
||||
CC6.2:
|
||||
family: CC6
|
||||
name: User Access
|
||||
description: Prior to issuing system credentials and granting system access, the entity registers and authorizes new internal and external users whose access is administered by the entity. For those users whose access is administered by the entity, user system credentials are removed when user access is no longer authorized
|
||||
CC6.3:
|
||||
family: CC6
|
||||
name: Role-Based Access
|
||||
description: The entity authorizes, modifies, or removes access to data, software, functions, and other protected information assets based on roles, responsibilities, or the system design and changes, giving consideration to the concepts of least privilege and segregation of duties, to meet the entity’s objectives
|
||||
CC6.4:
|
||||
family: CC6
|
||||
name: Physical Access
|
||||
description: The entity restricts physical access to facilities and protected information assets (for example, data center facilities, back-up media storage, and other sensitive locations) to authorized personnel to meet the entity’s objectives
|
||||
CC6.5:
|
||||
family: CC6
|
||||
name: Data Disposal
|
||||
description: The entity discontinues logical and physical protections over physical assets only after the ability to read or recover data and software from those assets has been diminished and is no longer required to meet the entity’s objectives
|
||||
CC6.6:
|
||||
family: CC6
|
||||
name: External Threats
|
||||
description: The entity implements logical access security measures to protect against threats from sources outside its system boundaries
|
||||
CC6.7:
|
||||
family: CC6
|
||||
name: Data Custody and Transmission
|
||||
description: The entity restricts the transmission, movement, and removal of information to authorized internal and external users and processes, and protects it during transmission, movement, or removal to meet the entity’s objectives
|
||||
CC6.8:
|
||||
family: CC6
|
||||
name: Malware Detection
|
||||
description: The entity implements controls to prevent or detect and act upon the introduction of unauthorized or malicious software to meet the entity’s objectives
|
||||
CC7.1:
|
||||
family: CC7
|
||||
name: Vulnerability Detection
|
||||
description: To meet its objectives, the entity uses detection and monitoring procedures to identify (1) changes to configurations that result in the introduction of new vulnerabilities, and (2) susceptibilities to newly discovered vulnerabilities
|
||||
CC7.2:
|
||||
family: CC7
|
||||
name: Anomaly Detection
|
||||
description: The entity monitors system components and the operation of those components for anomalies that are indicative of malicious acts, natural disasters, and errors affecting the entity’s ability to meet its objectives; anomalies are analyzed to determine whether they represent security events
|
||||
CC7.3:
|
||||
family: CC7
|
||||
name: Security Incident Evaluation
|
||||
description: The entity evaluates security events to determine whether they could or have resulted in a failure of the entity to meet its objectives (security incidents) and, if so, takes actions to prevent or address such failures
|
||||
CC7.4:
|
||||
family: CC7
|
||||
name: Security Incident Response Plan
|
||||
description: The entity responds to identified security incidents by executing a defined incident response program to understand, contain, remediate, and communicate security incidents, as appropriate
|
||||
CC7.5:
|
||||
family: CC7
|
||||
name: Security Incident Response Execution
|
||||
description: The entity identifies, develops, and implements activities to recover from identified security incidents
|
||||
CC8.1:
|
||||
family: CC8
|
||||
name: Change Control
|
||||
description: The entity authorizes, designs, develops or acquires, configures, documents, tests, approves, and implements changes to infrastructure, data, software, and procedures to meet its objectives
|
||||
CC9.1:
|
||||
family: CC9
|
||||
name: Disruption Risk Mitigation
|
||||
description: The entity identifies, selects, and develops risk mitigation activities for risks arising from potential business disruptions
|
||||
CC9.2:
|
||||
family: CC9
|
||||
name: Vendor Risk Management
|
||||
description: The entity assesses and manages risks associated with vendors and business partners
|
||||
A1.1:
|
||||
family: A1
|
||||
name: Capacity Planning
|
||||
description: The entity maintains, monitors, and evaluates current processing capacity and use of system components (infrastructure, data, and software) to manage capacity demand and to enable the implementation of additional capacity to help meet its objectives
|
||||
A1.2:
|
||||
family: A1
|
||||
name: Backup and Recovery
|
||||
description: The entity authorizes, designs, develops or acquires, implements, operates, approves, maintains, and monitors environmental protections, software, data back-up processes, and recovery infrastructure to meet its objectives
|
||||
A1.3:
|
||||
family: A1
|
||||
name: Recovery Testing
|
||||
description: The entity tests recovery plan procedures supporting system recovery to meet its objectives
|
||||
C1.1:
|
||||
family: C1
|
||||
name: Confidential Information Identification
|
||||
description: The entity identifies and maintains confidential information to meet the entity’s objectives related to confidentiality
|
||||
C1.2:
|
||||
family: C1
|
||||
name: Confidential Information Disposal
|
||||
description: The entity disposes of confidential information to meet the entity’s objectives related to confidentiality.
|
||||
PI1.1:
|
||||
family: PI1
|
||||
name: Processing Integrity Monitoring
|
||||
description: The entity obtains or generates, uses, and communicates relevant, quality information regarding the objectives related to processing, including definitions of data processed and product and service speci cations, to support the use of products and services
|
||||
PI1.2:
|
||||
family: PI1
|
||||
name: Processing Integrity Accuracy
|
||||
description: The entity implements policies and procedures over system inputs, including controls over completeness and accuracy, to result in products, services, and reporting to meet the entity’s objectives
|
||||
PI1.3:
|
||||
family: PI1
|
||||
name: Processing Integrity Operations
|
||||
description: The entity implements policies and procedures over system processing to result in products, services, and reporting to meet the entity’s objectives
|
||||
PI1.4:
|
||||
family: PI1
|
||||
name: Processing Integrity Outputs
|
||||
description: The entity implements policies and procedures to make available or deliver output completely, accurately, and timely in accordance with specifications to meet the entity’s objectives
|
||||
PI1.5:
|
||||
family: PI1
|
||||
name: Processing Integrity Backups
|
||||
description: The entity implements policies and procedures to store inputs, items in processing, and outputs completely, accurately, and timely in accordance with system specifications to meet the entity’s objectives
|
||||
P1.1:
|
||||
family: P1
|
||||
name: Privacy Notification
|
||||
description: The entity provides notice to data subjects about its privacy practices to meet the entity’s objectives related to privacy. The notice is updated and communicated to data subjects in a timely manner for changes to the entity’s privacy practices, including changes in the use of personal information, to meet the entity’s objectives related to privacy
|
||||
P2.1:
|
||||
family: P2
|
||||
name: Privacy Consent and Choice
|
||||
description: The entity communicates choices available regarding the collection, use, retention, disclosure, and disposal of personal information to the data subjects and the consequences, if any, of each choice. Explicit consent for the collection, use, retention, disclosure, and disposal of personal information is obtained from data subjects or other authorized persons, if required. Such consent is obtained only for the intended purpose of the information to meet the entity’s objectives related to privacy. The entity’s basis for determining implicit consent for the collection, use, retention, disclosure, and disposal of personal information is documented
|
||||
P3.1:
|
||||
family: P3
|
||||
name: Personal Information Collection
|
||||
description: Personal information is collected consistent with the entity’s objectives related to privacy
|
||||
P3.2:
|
||||
family: P3
|
||||
name: Explicit Consent
|
||||
description: For information requiring explicit consent, the entity communicates the need for such consent, as well as the consequences of a failure to provide consent for the request for personal information, and obtains the consent prior to the collection of the information to meet the entity’s objectives related to privacy
|
||||
P4.1:
|
||||
family: P4
|
||||
name: Proper Use of Personal Information
|
||||
description: The entity limits the use of personal information to the purposes identified in the entity’s objectives related to privacy
|
||||
P4.2:
|
||||
family: P4
|
||||
name: Personal Information Retention
|
||||
description: The entity retains personal information consistent with the entity’s objectives related to privacy
|
||||
P4.3:
|
||||
family: P4
|
||||
name: Personal Information Disposal
|
||||
description: The entity securely disposes of personal information to meet the entity’s objectives related to privacy
|
||||
P5.1:
|
||||
family: P5
|
||||
name: Data Subject Access
|
||||
description: The entity grants identified and authenticated data subjects the ability to access their stored personal information for review and, upon request, provides physical or electronic copies of that information to data subjects to meet the entity’s objectives related to privacy. If access is denied, data subjects are informed of the denial and reason for such denial, as required, to meet the entity’s objectives related to privacy
|
||||
P5.2:
|
||||
family: P5
|
||||
name: Data Subject Amendment
|
||||
description: The entity corrects, amends, or appends personal information based on information provided by data subjects and communicates such information to third parties, as committed or required, to meet the entity’s objectives related to privacy. If a request for correction is denied, data subjects are informed of the denial and reason for such denial to meet the entity’s objectives related to privacy
|
||||
P6.1:
|
||||
family: P6
|
||||
name: Consent for Third Party Disclosure
|
||||
description: The entity discloses personal information to third parties with the explicit consent of data subjects, and such consent is obtained prior to disclosure to meet the entity’s objectives related to privacy
|
||||
P6.2:
|
||||
family: P6
|
||||
name: Authorized Disclosures
|
||||
description: The entity creates and retains a complete, accurate, and timely record of authorized disclosures of personal information to meet the entity’s objectives related to privacy
|
||||
P6.3:
|
||||
family: P6
|
||||
name: Unauthorized Disclosures
|
||||
description: The entity creates and retains a complete, accurate, and timely record of detected or reported unauthorized disclosures (including breaches) of personal information to meet the entity’s objectives related to privacy
|
||||
P6.4:
|
||||
family: P6
|
||||
name: Appropriate Third Party Disclosure
|
||||
description: The entity obtains privacy commitments from vendors and other third parties who have access to personal information to meet the entity’s objectives related to privacy. The entity assesses those parties’ compliance on a periodic and as-needed basis and takes corrective action, if necessary
|
||||
P6.5:
|
||||
family: P6
|
||||
name: Unauthorized Third Party Disclosure
|
||||
description: The entity obtains commitments from vendors and other third parties with access to personal information to notify the entity in the event of actual or suspected unauthorized disclosures of personal information. Such notifications are reported to appropriate personnel and acted on in accordance with established incident response procedures to meet the entity’s objectives related to privacy
|
||||
P6.6:
|
||||
family: P6
|
||||
name: Notification of Unauthorized Third Party Disclosure
|
||||
description: The entity provides notification of breaches and incidents to affected data subjects, regulators, and others to meet the entity’s objectives related to privacy
|
||||
P6.7:
|
||||
family: P6
|
||||
name: Accounting of Personal Information
|
||||
description: The entity provides data subjects with an accounting of the personal information held and disclosure of the data subjects’ personal information, upon the data subjects’ request, to meet the entity’s objectives related to privacy
|
||||
P7.1:
|
||||
family: P7
|
||||
name: Accuracy of Personal Information
|
||||
description: The entity collects and maintains accurate, up-to-date, complete, and relevant personal information to meet the entity’s objectives related to privacy
|
||||
P8.1:
|
||||
family: P8
|
||||
name: Personal Information Dispute Resolution
|
||||
description: The entity implements a process for receiving, addressing, resolving, and communicating the resolution of inquiries, complaints, and disputes from data subjects and others and periodically monitors compliance to meet the entity’s objectives related to privacy. Corrections and other necessary actions related to identified deficiencies are made or taken in a timely manner
|
73
go.mod
Normal file
73
go.mod
Normal file
@ -0,0 +1,73 @@
|
||||
module github.com/strongdm/comply
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/Clever/gitsem v1.1.0
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/aktau/github-release v0.10.0
|
||||
github.com/andygrunwald/go-jira v1.14.0
|
||||
github.com/containerd/containerd v1.5.10 // indirect
|
||||
github.com/containous/go-bindata v1.0.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/docker/docker v20.10.9+incompatible
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/github-release/github-release v0.10.0 // indirect
|
||||
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-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect
|
||||
github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c // indirect
|
||||
github.com/manifoldco/promptui v0.8.0
|
||||
github.com/mattn/go-colorable v0.1.11 // 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/olekukonko/tablewriter v0.0.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/robfig/cron v1.2.0
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
|
||||
github.com/urfave/cli v1.22.5
|
||||
github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 // indirect
|
||||
github.com/xanzy/go-gitlab v0.51.1
|
||||
github.com/yosssi/ace v0.0.5
|
||||
golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
|
||||
golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
google.golang.org/genproto v0.0.0-20211005153810-c76a74d43a8e // indirect
|
||||
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.8.0+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.2 // 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
|
||||
)
|
@ -135,12 +135,12 @@ func notifyVersion(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func pandocMustExist(c *cli.Context) error {
|
||||
eitherMustExistErr := fmt.Errorf("\n\nPlease install either Docker or the pandoc package and re-run `%s`. Find OS-specific pandoc installation instructions at: [TODO]", c.Command.Name)
|
||||
eitherMustExistErr := fmt.Errorf("\n\nPlease install either Docker or the pandoc package and re-run `%s`. Find OS-specific pandoc installation instructions at: https://pandoc.org/installing.html", c.Command.Name)
|
||||
|
||||
pandocExistErr, found, goodVersion, pdfLatex := pandocBinaryMustExist(c)
|
||||
pandocBinaryExistErr, found, goodVersion, pdfLatex := pandocBinaryMustExist(c)
|
||||
dockerExistErr, inPath, isRunning := dockerMustExist(c)
|
||||
|
||||
config.SetPandoc(pandocExistErr == nil, dockerExistErr == nil)
|
||||
config.SetPandoc(pandocBinaryExistErr == nil, dockerExistErr == nil)
|
||||
check := func(b bool) string {
|
||||
if b {
|
||||
return "✔"
|
||||
@ -150,7 +150,7 @@ func pandocMustExist(c *cli.Context) error {
|
||||
|
||||
}
|
||||
|
||||
if pandocExistErr != nil && dockerExistErr != nil {
|
||||
if pandocBinaryExistErr != nil && dockerExistErr != nil {
|
||||
|
||||
fmt.Printf(`
|
||||
[%s] pandoc binary installed and in PATH
|
||||
@ -165,14 +165,20 @@ func pandocMustExist(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// if we don't have pandoc, but we do have docker, execute a pull
|
||||
if (pandocExistErr != nil && dockerExistErr == nil) || config.WhichPandoc() == config.UseDocker {
|
||||
dockerPull(c)
|
||||
if !pandocImageExists(context.Background()) && ((pandocBinaryExistErr != nil && dockerExistErr == nil) || config.WhichPandoc() == config.UseDocker) {
|
||||
canPullPandoc := strings.TrimSpace(strings.ToLower(os.Getenv("COMPLY_USE_LOCAL_PANDOC"))) != "true"
|
||||
if canPullPandoc {
|
||||
fmt.Println("Pulling docker image")
|
||||
dockerPull(c)
|
||||
} else {
|
||||
return fmt.Errorf("Local Pandoc not found. Please set COMPLY_USE_LOCAL_PANDOC to false")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pandocBinaryMustExist(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
var pandocBinaryMustExist = func(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
cmd := exec.Command("pandoc", "-v")
|
||||
outputRaw, err := cmd.Output()
|
||||
|
||||
@ -230,7 +236,7 @@ func pandocBinaryMustExist(c *cli.Context) (e error, found, goodVersion, pdfLate
|
||||
return e, found, goodVersion, pdfLatex
|
||||
}
|
||||
|
||||
func dockerMustExist(c *cli.Context) (e error, inPath, isRunning bool) {
|
||||
var dockerMustExist = func(c *cli.Context) (e error, inPath, isRunning bool) {
|
||||
dockerErr := fmt.Errorf("Docker must be available in order to run `%s`", c.Command.Name)
|
||||
|
||||
inPath = true
|
||||
@ -257,7 +263,26 @@ func dockerMustExist(c *cli.Context) (e error, inPath, isRunning bool) {
|
||||
return nil, inPath, isRunning
|
||||
}
|
||||
|
||||
func dockerPull(c *cli.Context) error {
|
||||
var pandocImageExists = func(ctx context.Context) bool {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
options := types.ImageListOptions{All: true}
|
||||
imageList, err := cli.ImageList(ctx, options)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, image := range imageList {
|
||||
if len(image.RepoTags) > 0 && strings.Contains(image.RepoTags[0], "strongdm/pandoc:edge") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var dockerPull = func(c *cli.Context) error {
|
||||
dockerErr := fmt.Errorf("Docker must be available in order to run `%s`", c.Command.Name)
|
||||
|
||||
ctx := context.Background()
|
||||
@ -275,7 +300,7 @@ func dockerPull(c *cli.Context) error {
|
||||
|
||||
select {
|
||||
case <-longishPull:
|
||||
fmt.Print("Pulling strongdm/pandoc:latest Docker image (this will take some time) ")
|
||||
fmt.Print("Pulling strongdm/pandoc:edge Docker image (this will take some time) ")
|
||||
|
||||
go func() {
|
||||
for {
|
||||
@ -294,7 +319,7 @@ func dockerPull(c *cli.Context) error {
|
||||
}
|
||||
}()
|
||||
|
||||
r, err := cli.ImagePull(ctx, "strongdm/pandoc:latest", types.ImagePullOptions{})
|
||||
r, err := cli.ImagePull(ctx, "strongdm/pandoc:edge", types.ImagePullOptions{})
|
||||
if err != nil {
|
||||
return dockerErr
|
||||
}
|
||||
@ -327,7 +352,7 @@ func cleanContainers(c *cli.Context) error {
|
||||
|
||||
for _, c := range containers {
|
||||
// assume this container was leftover from previous aborted run
|
||||
if strings.HasPrefix(c.Image, "strongdm/pandoc") {
|
||||
if strings.HasPrefix(c.Image, "strongdm/pandoc:edge") {
|
||||
d := time.Second * 2
|
||||
err = cli.ContainerStop(ctx, c.ID, &d)
|
||||
if err != nil {
|
||||
|
189
internal/cli/app_test.go
Normal file
189
internal/cli/app_test.go
Normal file
@ -0,0 +1,189 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/strongdm/comply/internal/util"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type PandocMustExist struct{}
|
||||
|
||||
func TestPandocMustExist(t *testing.T) {
|
||||
util.ExecuteTests(t, reflect.TypeOf(PandocMustExist{}), beforeEach, nil)
|
||||
}
|
||||
|
||||
func beforeEach() {
|
||||
util.MockConfig()
|
||||
}
|
||||
|
||||
func (tg PandocMustExist) WhenBinaryExists(t *testing.T) {
|
||||
dockerPullCalled := false
|
||||
|
||||
pandocBinaryMustExist = func(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
return nil, true, true, true
|
||||
}
|
||||
|
||||
dockerMustExist = func(c *cli.Context) (e error, inPath bool, isRunning bool) {
|
||||
return errors.New("docker doesn't exist"), false, false
|
||||
}
|
||||
|
||||
pandocImageExists = func(ctx context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
dockerPull = func(c *cli.Context) error {
|
||||
dockerPullCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
pandocMustExist(&cli.Context{})
|
||||
|
||||
if dockerPullCalled != false {
|
||||
t.Fatal("Docker was pulled")
|
||||
}
|
||||
}
|
||||
|
||||
func (tg PandocMustExist) WhenImageExists(t *testing.T) {
|
||||
dockerPullCalled := false
|
||||
|
||||
pandocBinaryMustExist = func(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
return errors.New("binary doesn't exist"), false, true, true
|
||||
}
|
||||
|
||||
dockerMustExist = func(c *cli.Context) (e error, inPath bool, isRunning bool) {
|
||||
return errors.New("docker doesn't exist"), false, false
|
||||
}
|
||||
|
||||
pandocImageExists = func(ctx context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
dockerPull = func(c *cli.Context) error {
|
||||
dockerPullCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
pandocMustExist(&cli.Context{})
|
||||
|
||||
if dockerPullCalled != false {
|
||||
t.Fatal("Docker was pulled")
|
||||
}
|
||||
}
|
||||
|
||||
func (tg PandocMustExist) WhenBinaryAndImageDontExists(t *testing.T) {
|
||||
dockerPullCalled := false
|
||||
|
||||
pandocBinaryMustExist = func(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
return errors.New("binary doesn't exist"), false, false, false
|
||||
}
|
||||
|
||||
dockerMustExist = func(c *cli.Context) (e error, inPath bool, isRunning bool) {
|
||||
return nil, true, false
|
||||
}
|
||||
|
||||
pandocImageExists = func(ctx context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
dockerPull = func(c *cli.Context) error {
|
||||
dockerPullCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
pandocMustExist(&cli.Context{})
|
||||
|
||||
if dockerPullCalled != true {
|
||||
t.Fatal("Docker wasn't pulled")
|
||||
}
|
||||
}
|
||||
|
||||
func (tg PandocMustExist) WhenCannotPullPandoc(t *testing.T) {
|
||||
dockerPullCalled := false
|
||||
|
||||
pandocBinaryMustExist = func(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
return errors.New("binary doesn't exist"), false, false, false
|
||||
}
|
||||
|
||||
dockerMustExist = func(c *cli.Context) (e error, inPath bool, isRunning bool) {
|
||||
return errors.New("docker doesn't exist"), false, false
|
||||
}
|
||||
|
||||
pandocImageExists = func(ctx context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
dockerPull = func(c *cli.Context) error {
|
||||
dockerPullCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
pandocMustExist(&cli.Context{})
|
||||
|
||||
if dockerPullCalled != false {
|
||||
t.Fatal("Docker was pulled")
|
||||
}
|
||||
}
|
||||
|
||||
func (tg PandocMustExist) WhenMustUseLocalPandoc(t *testing.T) {
|
||||
os.Setenv("COMPLY_USE_LOCAL_PANDOC", "true")
|
||||
dockerPullCalled := false
|
||||
|
||||
pandocBinaryMustExist = func(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
return errors.New("binary doesn't exist"), false, false, false
|
||||
}
|
||||
|
||||
dockerMustExist = func(c *cli.Context) (e error, inPath bool, isRunning bool) {
|
||||
return errors.New("docker doesn't exist"), false, false
|
||||
}
|
||||
|
||||
pandocImageExists = func(ctx context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
dockerPull = func(c *cli.Context) error {
|
||||
dockerPullCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
pandocMustExist(&cli.Context{})
|
||||
|
||||
if dockerPullCalled != false {
|
||||
t.Fatal("Docker was pulled")
|
||||
}
|
||||
|
||||
os.Clearenv()
|
||||
}
|
||||
|
||||
func (tg PandocMustExist) WhenPandocDontExistsAndCannotPull(t *testing.T) {
|
||||
os.Setenv("COMPLY_USE_LOCAL_PANDOC", "true")
|
||||
dockerPullCalled := false
|
||||
|
||||
pandocBinaryMustExist = func(c *cli.Context) (e error, found, goodVersion, pdfLatex bool) {
|
||||
return errors.New("binary doesn't exist"), false, false, false
|
||||
}
|
||||
|
||||
dockerMustExist = func(c *cli.Context) (e error, inPath bool, isRunning bool) {
|
||||
return nil, true, false
|
||||
}
|
||||
|
||||
pandocImageExists = func(ctx context.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
dockerPull = func(c *cli.Context) error {
|
||||
dockerPullCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
pandocMustExist(&cli.Context{})
|
||||
|
||||
if dockerPullCalled != false {
|
||||
t.Fatal("Docker was pulled")
|
||||
}
|
||||
os.Clearenv()
|
||||
}
|
@ -82,7 +82,7 @@ func Exists() bool {
|
||||
}
|
||||
|
||||
// Config is the parsed contents of ProjectRoot()/config.yml.
|
||||
func Config() *Project {
|
||||
var Config = func() *Project {
|
||||
p := Project{}
|
||||
cfgBytes, err := ioutil.ReadFile(filepath.Join(ProjectRoot(), "comply.yml"))
|
||||
if err != nil {
|
||||
|
@ -46,8 +46,8 @@ func (g *gitlabPlugin) api() *gitlab.Client {
|
||||
defer g.clientMu.Unlock()
|
||||
if g.client == nil {
|
||||
// get go-gitlab client
|
||||
gl := gitlab.NewClient(nil, g.token)
|
||||
gl.SetBaseURL(g.domain)
|
||||
// TODO: see if it's necessary to verify the error
|
||||
gl, _ := gitlab.NewClient(g.token, gitlab.WithBaseURL(g.domain))
|
||||
g.client = gl
|
||||
}
|
||||
return g.client
|
||||
@ -99,13 +99,33 @@ func getCfg(cfg map[string]interface{}, k string) (string, error) {
|
||||
return vS, nil
|
||||
}
|
||||
|
||||
func getProjectIssues(g *gitlabPlugin, options *gitlab.ListProjectIssuesOptions) ([]*gitlab.Issue, error) {
|
||||
issues := []*gitlab.Issue{}
|
||||
options.Page = 1
|
||||
|
||||
for {
|
||||
pageIssues, resp, err := g.api().Issues.ListProjectIssues(g.reponame, options)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error retreiving issues from gitlab")
|
||||
}
|
||||
|
||||
issues = append(issues, pageIssues...)
|
||||
|
||||
if resp.CurrentPage >= resp.TotalPages {
|
||||
break
|
||||
}
|
||||
options.Page = resp.NextPage
|
||||
}
|
||||
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func (g *gitlabPlugin) FindOpen() ([]*model.Ticket, error) {
|
||||
options := &gitlab.ListProjectIssuesOptions{
|
||||
State: gitlab.String("opened"),
|
||||
}
|
||||
|
||||
issues, _, err := g.api().Issues.ListProjectIssues(g.reponame, options)
|
||||
|
||||
issues, err := getProjectIssues(g, options)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during FindOpen")
|
||||
}
|
||||
@ -123,10 +143,9 @@ func (g *gitlabPlugin) FindByTagName(name string) ([]*model.Ticket, error) {
|
||||
Labels: []string{name},
|
||||
}
|
||||
|
||||
issues, _, err := g.api().Issues.ListProjectIssues(g.reponame, options)
|
||||
|
||||
issues, err := getProjectIssues(g, options)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during FindOpen")
|
||||
return nil, errors.Wrap(err, "error during FindByTagName")
|
||||
}
|
||||
|
||||
return toTickets(issues), nil
|
||||
@ -137,10 +156,11 @@ func (g *gitlabPlugin) LinkFor(t *model.Ticket) string {
|
||||
}
|
||||
|
||||
func (g *gitlabPlugin) Create(ticket *model.Ticket, labels []string) error {
|
||||
l := gitlab.Labels(labels)
|
||||
options := &gitlab.CreateIssueOptions{
|
||||
Title: gitlab.String(ticket.Name),
|
||||
Description: gitlab.String(ticket.Body),
|
||||
Labels: labels,
|
||||
Labels: l,
|
||||
}
|
||||
_, _, err := g.api().Issues.CreateIssue(g.reponame, options)
|
||||
return err
|
||||
@ -169,6 +189,15 @@ func toTicket(i *gitlab.Issue) *model.Ticket {
|
||||
if l == "procedure" {
|
||||
t.SetBool("comply-procedure")
|
||||
}
|
||||
|
||||
// seems redundant, but fixes a bug the other two labels introduce
|
||||
// whereby open comply tickets aren't properly accounted for in the UI
|
||||
if l == "comply-audit" {
|
||||
t.SetBool("comply-audit")
|
||||
}
|
||||
if l == "comply-procedure" {
|
||||
t.SetBool("comply-procedure")
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
@ -83,7 +83,10 @@ func ReadStandards() ([]*Standard, error) {
|
||||
return nil, errors.Wrap(err, "unable to read "+f.FullPath)
|
||||
}
|
||||
|
||||
yaml.Unmarshal(sBytes, &s)
|
||||
err = yaml.Unmarshal(sBytes, &s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse "+f.FullPath)
|
||||
}
|
||||
standards = append(standards, s)
|
||||
}
|
||||
|
||||
@ -101,7 +104,10 @@ func ReadNarratives() ([]*Document, error) {
|
||||
|
||||
for _, f := range files {
|
||||
n := &Document{}
|
||||
mdmd := loadMDMD(f.FullPath)
|
||||
mdmd, err := loadMDMD(f.FullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = yaml.Unmarshal([]byte(mdmd.yaml), &n)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse "+f.FullPath)
|
||||
@ -120,13 +126,17 @@ func ReadNarratives() ([]*Document, error) {
|
||||
func ReadProcedures() ([]*Procedure, error) {
|
||||
var procedures []*Procedure
|
||||
files, err := path.Procedures()
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to enumerate paths")
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
p := &Procedure{}
|
||||
mdmd := loadMDMD(f.FullPath)
|
||||
mdmd, err := loadMDMD(f.FullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = yaml.Unmarshal([]byte(mdmd.yaml), &p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse "+f.FullPath)
|
||||
@ -151,7 +161,10 @@ func ReadPolicies() ([]*Document, error) {
|
||||
|
||||
for _, f := range files {
|
||||
p := &Document{}
|
||||
mdmd := loadMDMD(f.FullPath)
|
||||
mdmd, err := loadMDMD(f.FullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = yaml.Unmarshal([]byte(mdmd.yaml), &p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse "+f.FullPath)
|
||||
@ -171,18 +184,20 @@ type metadataMarkdown struct {
|
||||
body string
|
||||
}
|
||||
|
||||
func loadMDMD(path string) metadataMarkdown {
|
||||
func loadMDMD(path string) (*metadataMarkdown, error) {
|
||||
bytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
content := string(bytes)
|
||||
components := strings.Split(content, "---")
|
||||
if len(components) == 1 {
|
||||
panic(fmt.Sprintf("Malformed metadata markdown in %s, must be of the form: YAML\\n---\\nmarkdown content", path))
|
||||
if components[0] == "" && (len(components) > 1) {
|
||||
components = components[1:]
|
||||
}
|
||||
yaml := components[0]
|
||||
if len(components) == 1 {
|
||||
return nil, errors.New(fmt.Sprintf("Malformed metadata markdown in %s, must be of the form: YAML\\n---\\nmarkdown content", path))
|
||||
}
|
||||
item := components[0]
|
||||
body := strings.Join(components[1:], "---")
|
||||
return metadataMarkdown{yaml, body}
|
||||
return &metadataMarkdown{item, body}, nil
|
||||
}
|
||||
|
238
internal/model/fs_test.go
Normal file
238
internal/model/fs_test.go
Normal file
@ -0,0 +1,238 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/strongdm/comply/internal/path"
|
||||
"github.com/strongdm/comply/internal/util"
|
||||
)
|
||||
|
||||
type ReadFiles struct{}
|
||||
|
||||
func beforeEach() {
|
||||
util.MockConfig()
|
||||
}
|
||||
|
||||
func TestReadFiles(t *testing.T) {
|
||||
util.ExecuteTests(t, reflect.TypeOf(ReadFiles{}), beforeEach, nil)
|
||||
}
|
||||
|
||||
// TestReadNarratives calls model.ReadNarratives checking for a valid return value.
|
||||
func (tg ReadFiles) TestReadNarratives(t *testing.T) {
|
||||
filePath := fmt.Sprintf("%s/narratives/control.md", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
path.Narratives = func() ([]path.File, error) {
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadNarratives()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadNarratives() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 1 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
if documents[0].FullPath != filePath {
|
||||
t.Fatalf(`Invalid document path %s`, documents[0].FullPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadNarrativesWhenThereAreNoNarratives calls model.ReadNarratives checking for a valid return when
|
||||
// there are no narratives to process
|
||||
func (tg ReadFiles) TestReadNarrativesWhenThereAreNoNarratives(t *testing.T) {
|
||||
path.Narratives = func() ([]path.File, error) {
|
||||
return []path.File{}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadNarratives()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadNarratives() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 0 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadNarrativesFailsWhenInvalidNarrative calls model.ReadNarratives checking for an error return when
|
||||
// there is an invalid narrative
|
||||
func (tg ReadFiles) TestReadNarrativesFailsWhenInvalidNarrative(t *testing.T) {
|
||||
path.Narratives = func() ([]path.File, error) {
|
||||
filePath := fmt.Sprintf("%s/../fixtures/narratives/invalid-control.md", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err := ReadNarratives()
|
||||
if err == nil {
|
||||
t.Fatal(`ReadNarratives() was expected to fail`)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadProcedures calls model.ReadProcedures checking for a valid return value.
|
||||
func (tg ReadFiles) TestReadProcedures(t *testing.T) {
|
||||
filePath := fmt.Sprintf("%s/procedures/workstation.md", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
path.Procedures = func() ([]path.File, error) {
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadProcedures()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadProcedures() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 1 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
if documents[0].FullPath != filePath {
|
||||
t.Fatalf(`Invalid document path %s`, documents[0].FullPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadProceduresWhenThereAreNoProcedures calls model.ReadProcedures checking for a valid return when
|
||||
// there are no procedures to process
|
||||
func (tg ReadFiles) TestReadProceduresWhenThereAreNoProcedures(t *testing.T) {
|
||||
path.Procedures = func() ([]path.File, error) {
|
||||
return []path.File{}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadProcedures()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadProcedures() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 0 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadProceduresFailsWhenInvalidProcedure calls model.ReadProcedures checking for an error return when
|
||||
// there is an invalid procedure
|
||||
func (tg ReadFiles) TestReadProceduresFailsWhenInvalidProcedure(t *testing.T) {
|
||||
path.Procedures = func() ([]path.File, error) {
|
||||
filePath := fmt.Sprintf("%s/../fixtures/procedures/invalid-workstation.md", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err := ReadProcedures()
|
||||
if err == nil {
|
||||
t.Fatal(`ReadProcedures() was expected to fail`, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadPolicies calls model.ReadPolicies checking for a valid return value.
|
||||
func (tg ReadFiles) TestReadPolicies(t *testing.T) {
|
||||
filePath := fmt.Sprintf("%s/policies/access.md", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
path.Policies = func() ([]path.File, error) {
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadPolicies()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadPolicies() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 1 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
if documents[0].FullPath != filePath {
|
||||
t.Fatalf(`Invalid document path %s`, documents[0].FullPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadPoliciesWhenThereAreNoPolicies calls model.ReadPolicies checking for a valid return when
|
||||
// there are no policies to process
|
||||
func (tg ReadFiles) TestReadPoliciesWhenThereAreNoPolicies(t *testing.T) {
|
||||
path.Policies = func() ([]path.File, error) {
|
||||
return []path.File{}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadPolicies()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadPolicies() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 0 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadPoliciesFailsWhenInvalidPolicy calls model.ReadPolicies checking for an error return when
|
||||
// there is an invalid policy
|
||||
func (tg ReadFiles) TestReadPoliciesFailsWhenInvalidPolicy(t *testing.T) {
|
||||
path.Policies = func() ([]path.File, error) {
|
||||
filePath := fmt.Sprintf("%s/../fixtures/policies/invalid-access.md", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err := ReadPolicies()
|
||||
if err == nil {
|
||||
t.Fatal(`ReadPolicies() was expected to fail`, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadStandards calls model.ReadStandards checking for a valid return value.
|
||||
func (tg ReadFiles) TestReadStandards(t *testing.T) {
|
||||
filePath := fmt.Sprintf("%s/standards/TSC-2017.yml", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
path.Standards = func() ([]path.File, error) {
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadStandards()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadStandards() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 1 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadStandardsWhenThereAreNoStandards calls model.ReadStandards checking for a valid return when
|
||||
// there are no standards to process
|
||||
func (tg ReadFiles) TestReadStandardsWhenThereAreNoStandards(t *testing.T) {
|
||||
path.Standards = func() ([]path.File, error) {
|
||||
return []path.File{}, nil
|
||||
}
|
||||
|
||||
documents, err := ReadStandards()
|
||||
if err != nil {
|
||||
t.Fatalf(`ReadStandards() returned an error %v`, err)
|
||||
}
|
||||
if len(documents) != 0 {
|
||||
t.Fatal(`Invalid number of documents`)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadStandardsFailsWhenInvalidStandard calls model.ReadStandards checking for an error return when
|
||||
// there is an invalid standard
|
||||
func (tg ReadFiles) TestReadStandardsFailsWhenInvalidStandard(t *testing.T) {
|
||||
path.Standards = func() ([]path.File, error) {
|
||||
filePath := fmt.Sprintf("%s/../fixtures/standards/invalid-standard.yml", util.GetRootPath())
|
||||
fileInfo, _ := os.Lstat(filePath)
|
||||
return []path.File{
|
||||
{FullPath: filePath, Info: fileInfo},
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err := ReadStandards()
|
||||
if err == nil {
|
||||
t.Fatal(`ReadStandards() was expected to fail`, err)
|
||||
}
|
||||
}
|
@ -9,36 +9,32 @@ import (
|
||||
func TestMarshal(t *testing.T) {
|
||||
d := Data{
|
||||
Tickets: []*Ticket{
|
||||
&Ticket{
|
||||
{
|
||||
ID: "t1",
|
||||
},
|
||||
},
|
||||
Audits: []*Audit{
|
||||
&Audit{
|
||||
{
|
||||
ID: "a1",
|
||||
},
|
||||
},
|
||||
Procedures: []*Procedure{
|
||||
&Procedure{
|
||||
Code: "pro1",
|
||||
{
|
||||
ID: "pro1",
|
||||
},
|
||||
},
|
||||
Policies: []*Policy{
|
||||
&Policy{
|
||||
Policies: []*Document{
|
||||
{
|
||||
Name: "pol1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m, _ := json.Marshal(d)
|
||||
|
||||
encoded := string(m)
|
||||
|
||||
if !strings.Contains(encoded, "t1") ||
|
||||
!strings.Contains(encoded, "a1") ||
|
||||
!strings.Contains(encoded, "pro1") ||
|
||||
!strings.Contains(encoded, "pol1") {
|
||||
t.Error("identifier not found in marshalled string")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,22 +16,22 @@ type File struct {
|
||||
}
|
||||
|
||||
// Standards lists all standard files.
|
||||
func Standards() ([]File, error) {
|
||||
var Standards = func() ([]File, error) {
|
||||
return filesFor("standards", "yml")
|
||||
}
|
||||
|
||||
// Narratives lists all narrative files.
|
||||
func Narratives() ([]File, error) {
|
||||
var Narratives = func() ([]File, error) {
|
||||
return filesFor("narratives", "md")
|
||||
}
|
||||
|
||||
// Policies lists all policy files.
|
||||
func Policies() ([]File, error) {
|
||||
var Policies = func() ([]File, error) {
|
||||
return filesFor("policies", "md")
|
||||
}
|
||||
|
||||
// Procedures lists all procedure files.
|
||||
func Procedures() ([]File, error) {
|
||||
var Procedures = func() ([]File, error) {
|
||||
return filesFor("procedures", "md")
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,9 @@ package github
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
@ -95,7 +97,10 @@ func (g *githubPlugin) Configure(cfg map[string]interface{}) error {
|
||||
func getCfg(cfg map[string]interface{}, k string) (string, error) {
|
||||
v, ok := cfg[k]
|
||||
if !ok {
|
||||
return "", errors.New("Missing key: " + k)
|
||||
v = os.Getenv(fmt.Sprintf("GITHUB_%s", strings.ToUpper(k)))
|
||||
if v == "" {
|
||||
return "", errors.New("Missing key: " + k)
|
||||
}
|
||||
}
|
||||
|
||||
vS, ok := v.(string)
|
||||
|
@ -135,9 +135,12 @@ func preprocessDoc(data *renderData, pol *model.Document, fullPath string) error
|
||||
%% %s
|
||||
|
||||
---
|
||||
header-includes: yes
|
||||
head-content: "%s"
|
||||
foot-content: "%s confidential %d"
|
||||
header-includes: |
|
||||
\usepackage{fancyhdr}
|
||||
\pagestyle{fancy}
|
||||
\fancyhead{}
|
||||
\fancyhead[RO,RE]{%s}
|
||||
\fancyfoot[LO,LE]{%s confidential %d}
|
||||
---
|
||||
|
||||
%s
|
||||
|
@ -18,10 +18,7 @@ var pandocArgs = []string{"-f", "markdown+smart", "--toc", "-N", "--template", "
|
||||
|
||||
func pandoc(outputFilename string, errOutputCh chan error) {
|
||||
if config.WhichPandoc() == config.UsePandoc {
|
||||
err := pandocPandoc(outputFilename)
|
||||
if err != nil {
|
||||
errOutputCh <- err
|
||||
}
|
||||
pandocPandoc(outputFilename, errOutputCh)
|
||||
} else {
|
||||
dockerPandoc(outputFilename, errOutputCh)
|
||||
}
|
||||
@ -47,9 +44,9 @@ func dockerPandoc(outputFilename string, errOutputCh chan error) {
|
||||
}
|
||||
|
||||
resp, err := cli.ContainerCreate(ctx, &container.Config{
|
||||
Image: "strongdm/pandoc",
|
||||
Image: "strongdm/pandoc:edge",
|
||||
Cmd: pandocCmd},
|
||||
hc, nil, "")
|
||||
hc, nil, nil, "")
|
||||
|
||||
if err != nil {
|
||||
errOutputCh <- errors.Wrap(err, "unable to create Docker container")
|
||||
@ -64,15 +61,20 @@ func dockerPandoc(outputFilename string, errOutputCh chan error) {
|
||||
errOutputCh <- errors.Wrap(err, "unable to remove container")
|
||||
return
|
||||
}
|
||||
errOutputCh <- nil
|
||||
}()
|
||||
|
||||
if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
|
||||
err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
||||
if err != nil {
|
||||
errOutputCh <- errors.Wrap(err, "unable to start Docker container")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = cli.ContainerWait(ctx, resp.ID)
|
||||
if err != nil {
|
||||
chanResult, chanErr := cli.ContainerWait(ctx, resp.ID, "not-running")
|
||||
resultValue := <-chanResult
|
||||
|
||||
if resultValue.StatusCode != 0 {
|
||||
err = <-chanErr
|
||||
errOutputCh <- errors.Wrap(err, "error awaiting Docker container")
|
||||
return
|
||||
}
|
||||
@ -90,12 +92,14 @@ func dockerPandoc(outputFilename string, errOutputCh chan error) {
|
||||
}
|
||||
|
||||
// 🐼
|
||||
func pandocPandoc(outputFilename string) error {
|
||||
func pandocPandoc(outputFilename string, errOutputCh chan error) error {
|
||||
cmd := exec.Command("pandoc", append(pandocArgs, fmt.Sprintf("output/%s", outputFilename), fmt.Sprintf("output/%s.md", outputFilename))...)
|
||||
outputRaw, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println(string(outputRaw))
|
||||
return errors.Wrap(err, "error calling pandoc")
|
||||
errOutputCh <- errors.Wrap(err, "error calling pandoc")
|
||||
} else {
|
||||
errOutputCh <- nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -26,6 +26,12 @@ func pdf(output string, live bool, errCh chan error, wg *sync.WaitGroup) {
|
||||
}
|
||||
for _, policy := range policies {
|
||||
renderToFilesystem(&pdfWG, errOutputCh, data, policy, live)
|
||||
err = <-errOutputCh
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
narratives, err := model.ReadNarratives()
|
||||
@ -36,6 +42,12 @@ func pdf(output string, live bool, errCh chan error, wg *sync.WaitGroup) {
|
||||
|
||||
for _, narrative := range narratives {
|
||||
renderToFilesystem(&pdfWG, errOutputCh, data, narrative, live)
|
||||
err = <-errOutputCh
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pdfWG.Wait()
|
||||
|
@ -13,6 +13,8 @@ import (
|
||||
"github.com/yosssi/ace"
|
||||
)
|
||||
|
||||
const BindAddress = "0.0.0.0"
|
||||
|
||||
var ServePort int
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
@ -94,13 +96,13 @@ func Build(output string, live bool) error {
|
||||
|
||||
go func() {
|
||||
http.Handle("/", http.FileServer(http.Dir(filepath.Join(".", "output"))))
|
||||
err := http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", ServePort), nil)
|
||||
err := http.ListenAndServe(fmt.Sprintf("%s:%d", BindAddress, ServePort), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf("Serving content of output/ at http://127.0.0.1:%d (ctrl-c to quit)\n", ServePort)
|
||||
fmt.Printf("Serving content of output/ at http://%s:%d (ctrl-c to quit)\n", BindAddress, ServePort)
|
||||
}
|
||||
// PDF
|
||||
wg.Add(1)
|
||||
|
@ -8,7 +8,8 @@ import (
|
||||
)
|
||||
|
||||
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 {
|
||||
errCh <- err
|
||||
return
|
||||
@ -25,7 +26,7 @@ func watch(errCh chan error) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-b.Errors:
|
||||
case e := <-b.Errors():
|
||||
errCh <- e
|
||||
case <-b.Events:
|
||||
broadcast()
|
||||
|
@ -29,7 +29,8 @@ func SaveTo(themeName string, replace map[string]string, saveDir string) error {
|
||||
rootMdFile := string(MustAsset(name))
|
||||
|
||||
var w bytes.Buffer
|
||||
rootMdFileTemplate, err := template.New("rootMdFile").Parse(rootMdFile)
|
||||
var rootMdFileTemplate *template.Template
|
||||
rootMdFileTemplate, err = template.New("rootMdFile").Parse(rootMdFile)
|
||||
if err != nil {
|
||||
w.WriteString(fmt.Sprintf("# Error processing template:\n\n%s\n", err.Error()))
|
||||
} else {
|
||||
|
File diff suppressed because it is too large
Load Diff
52
internal/util/test_utils.go
Normal file
52
internal/util/test_utils.go
Normal file
@ -0,0 +1,52 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/strongdm/comply/internal/config"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type TestFixture func()
|
||||
|
||||
func ExecuteTests(t *testing.T, testGroupType reflect.Type, beforeEach TestFixture, afterEach TestFixture) {
|
||||
testGroup := reflect.New(testGroupType).Elem().Interface()
|
||||
for i := 0; i < testGroupType.NumMethod(); i++ {
|
||||
m := testGroupType.Method(i)
|
||||
t.Run(m.Name, func(t *testing.T) {
|
||||
if beforeEach != nil {
|
||||
beforeEach()
|
||||
}
|
||||
|
||||
in := []reflect.Value{reflect.ValueOf(testGroup), reflect.ValueOf(t)}
|
||||
m.Func.Call(in)
|
||||
|
||||
if afterEach != nil {
|
||||
afterEach()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func MockConfig() {
|
||||
config.Config = func() *config.Project {
|
||||
p := config.Project{}
|
||||
cfgBytes, _ := ioutil.ReadFile(filepath.Join(GetRootPath(), "comply.yml.example"))
|
||||
err := yaml.Unmarshal(cfgBytes, &p)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &p
|
||||
}
|
||||
}
|
||||
|
||||
func GetRootPath() string {
|
||||
_, fileName, _, _ := runtime.Caller(0)
|
||||
fileDir := filepath.Dir(fileName)
|
||||
return fmt.Sprintf("%s/../../example", fileDir)
|
||||
}
|
@ -20,7 +20,7 @@ Assets are built using [`comply`](https://comply.strongdm.com), which can be ins
|
||||
|
||||
# Publishing
|
||||
|
||||
The `output/` directory contains all generated assets. Links in the HTML dashboard a relative, and all dependencies are included via direct CDN references. The entire `output/` directory therefore may be uploaded to an S3 bucket or other static asset host without further modification.
|
||||
The `output/` directory contains all generated assets. Links in the HTML dashboard are relative, and all dependencies are included via direct CDN references. The entire `output/` directory, therefore, may be uploaded to an S3 bucket or another static asset host without further modification.
|
||||
|
||||
# Dashboard Status
|
||||
|
||||
@ -57,4 +57,4 @@ comply build
|
||||
|
||||
# publish static site from output/ directory
|
||||
upload.sh output/
|
||||
```
|
||||
```
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,7 @@ Assets are built using [`comply`](https://comply.strongdm.com), which can be ins
|
||||
|
||||
# Publishing
|
||||
|
||||
The `output/` directory contains all generated assets. Links in the HTML dashboard a relative, and all dependencies are included via direct CDN references. The entire `output/` directory therefore may be uploaded to an S3 bucket or other static asset host without further modification.
|
||||
The `output/` directory contains all generated assets. Links in the HTML dashboard are relative, and all dependencies are included via direct CDN references. The entire `output/` directory, therefore, may be uploaded to an S3 bucket or another static asset host without further modification.
|
||||
|
||||
# Dashboard Status
|
||||
|
||||
@ -57,4 +57,4 @@ comply build
|
||||
|
||||
# publish static site from output/ directory
|
||||
upload.sh output/
|
||||
```
|
||||
```
|
||||
|
@ -75,7 +75,7 @@ TODO: Finalize these lists
|
||||
|
||||
# Remediations
|
||||
|
||||
{{.Name}} uses the outcomes of the aforementioned controls and procedures to identify shortcomings in the existing control environment. Once identified, these shortcomes are remediated by improving existing controls and procedures, and creating new controls and procedures as needed.
|
||||
{{.Name}} uses the outcomes of the aforementioned controls and procedures to identify shortcomings in the existing control environment. Once identified, these shortcomings are remediated by improving existing controls and procedures, and creating new controls and procedures as needed.
|
||||
|
||||
# Communications
|
||||
|
||||
@ -87,7 +87,7 @@ TODO: Finalize these lists
|
||||
|
||||
- Slack
|
||||
- Email
|
||||
- Github ticketing
|
||||
- GitHub ticketing
|
||||
|
||||
## External
|
||||
|
||||
|
@ -66,7 +66,7 @@ a. When classifying information, the level of confidentiality is determined by:
|
||||
+-------------------+------------------+---------------------------+---------------------------+
|
||||
| Restricted | Restricted | Unauthorized access to | Information is available |
|
||||
| | | information may cause | to a specific group of |
|
||||
| | | considerable damage to | employees and authhorized |
|
||||
| | | considerable damage to | employees and authorized |
|
||||
| | | the business and/or | third parties. |
|
||||
| | | the organization's | |
|
||||
| | | reputation. | |
|
||||
|
@ -22,7 +22,7 @@ a. This policy defines the policies and rules governing data centers and secure
|
||||
|
||||
# Policy
|
||||
|
||||
a. The following locations are classified by the organization as secure areas and are goverened by this policy:
|
||||
a. The following locations are classified by the organization as secure areas and are governed by this policy:
|
||||
|
||||
i. [list all data center locations and secure areas under the organization’s control]
|
||||
|
||||
|
@ -8,7 +8,8 @@ majorRevisions:
|
||||
- date: Jun 1 2018
|
||||
comment: Initial document
|
||||
---
|
||||
#Purpose and Scope
|
||||
|
||||
# Purpose and Scope
|
||||
|
||||
a. The purpose of this policy is to define the organization’s procedures to recover Information Technology (IT) infrastructure and IT services within set deadlines in the case of a disaster or other disruptive incident. The objective of this plan is to complete the recovery of IT infrastructure and IT services within a set Recovery Time Objective (RTO).
|
||||
|
||||
|
@ -10,14 +10,14 @@ majorRevisions:
|
||||
comment: Initial document
|
||||
---
|
||||
|
||||
#Appendices
|
||||
# Appendices
|
||||
Appendix A: Retention Periods
|
||||
|
||||
# Purpose and Scope
|
||||
|
||||
a. This data retention policy defines the objectives and requirements for data retention within the organization.
|
||||
|
||||
a. This policy covers all data within the organization’s custody or control, irregardless of the medium the data is stored in (electronic form, paper form, etc.) Within this policy, the medium which holds data is referred to as information, no matter what form it is in.
|
||||
a. This policy covers all data within the organization’s custody or control, regardless of the medium the data is stored in (electronic form, paper form, etc.) Within this policy, the medium which holds data is referred to as information, no matter what form it is in.
|
||||
|
||||
a. This policy applies to all users of information systems within the organization. This typically includes employees and contractors, as well as any external parties that come into contact with systems and information the organization owns or controls (hereinafter referred to as “users”). This policy must be made readily available to all users.
|
||||
|
||||
|
@ -87,13 +87,13 @@ Table 3: Description of Consequence Levels and Criteria
|
||||
| Moderate | 1 | Either existing security controls have most provided an |
|
||||
| | | adequate level of protection or the probability of the risk |
|
||||
| | | being realized is moderate. Some minor incidents may have |
|
||||
| | | occured. New incidents are possible, but not highly likely. |
|
||||
| | | occurred. New incidents are possible, but not highly likely. |
|
||||
+-----------------+-----------------+--------------------------------------------------------------+
|
||||
| | | |
|
||||
+-----------------+-----------------+--------------------------------------------------------------+
|
||||
| High | 2 | Either existing security controls are not in place or |
|
||||
| | | ineffective; there is a high probability of the risk being |
|
||||
| | | realized. Incidents have a high likelihood of occuring in the|
|
||||
| | | realized. Incidents have a high likelihood of occurring in the|
|
||||
| | | future. |
|
||||
+-----------------+-----------------+--------------------------------------------------------------+
|
||||
| | | |
|
||||
|
@ -154,7 +154,7 @@ C1.2:
|
||||
PI1.1:
|
||||
family: PI1
|
||||
name: Processing Integrity Monitoring
|
||||
description: The entity obtains or generates, uses, and communicates relevant, quality information regarding the objectives related to processing, including de nitions of data processed and product and service speci cations, to support the use of products and services
|
||||
description: The entity obtains or generates, uses, and communicates relevant, quality information regarding the objectives related to processing, including definitions of data processed and product and service speci cations, to support the use of products and services
|
||||
PI1.2:
|
||||
family: PI1
|
||||
name: Processing Integrity Accuracy
|
||||
@ -166,11 +166,11 @@ PI1.3:
|
||||
PI1.4:
|
||||
family: PI1
|
||||
name: Processing Integrity Outputs
|
||||
description: The entity implements policies and procedures to make available or deliver output completely, accurately, and timely in accordance with speci cations to meet the entity’s objectives
|
||||
description: The entity implements policies and procedures to make available or deliver output completely, accurately, and timely in accordance with specifications to meet the entity’s objectives
|
||||
PI1.5:
|
||||
family: PI1
|
||||
name: Processing Integrity Backups
|
||||
description: The entity implements policies and procedures to store inputs, items in processing, and outputs completely, accurately, and timely in accordance with system speci cations to meet the entity’s objectives
|
||||
description: The entity implements policies and procedures to store inputs, items in processing, and outputs completely, accurately, and timely in accordance with system specifications to meet the entity’s objectives
|
||||
P1.1:
|
||||
family: P1
|
||||
name: Privacy Notification
|
||||
|
File diff suppressed because it is too large
Load Diff
10
tools.go
Normal file
10
tools.go
Normal file
@ -0,0 +1,10 @@
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/Clever/gitsem"
|
||||
_ "github.com/aktau/github-release"
|
||||
_ "github.com/containous/go-bindata/go-bindata" // v1.0.0
|
||||
_ "github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs" // v0.0.0-20170227122030-30f82fa23fd8
|
||||
)
|
25
vendor/github.com/Clever/gitsem/.gitignore
generated
vendored
Normal file
25
vendor/github.com/Clever/gitsem/.gitignore
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
||||
gitsem
|
3
vendor/github.com/docker/docker/pkg/symlink/LICENSE.APACHE → vendor/github.com/Clever/gitsem/LICENSE
generated
vendored
3
vendor/github.com/docker/docker/pkg/symlink/LICENSE.APACHE → vendor/github.com/Clever/gitsem/LICENSE
generated
vendored
@ -1,4 +1,3 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
@ -176,7 +175,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014-2016 Docker, Inc.
|
||||
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.
|
51
vendor/github.com/Clever/gitsem/Makefile
generated
vendored
Normal file
51
vendor/github.com/Clever/gitsem/Makefile
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
include golang.mk
|
||||
.DEFAULT_GOAL := test # override default goal set in library makefile
|
||||
|
||||
SHELL := /bin/bash
|
||||
PKG := github.com/Clever/gitsem
|
||||
PKGS := $(shell go list ./... | grep -v /vendor)
|
||||
EXECUTABLE := gitsem
|
||||
VERSION := $(shell cat VERSION)
|
||||
BUILDS := \
|
||||
build/$(EXECUTABLE)-v$(VERSION)-darwin-amd64 \
|
||||
build/$(EXECUTABLE)-v$(VERSION)-linux-amd64 \
|
||||
build/$(EXECUTABLE)-v$(VERSION)-windows-amd64
|
||||
COMPRESSED_BUILDS := $(BUILDS:%=%.tar.gz)
|
||||
RELEASE_ARTIFACTS := $(COMPRESSED_BUILDS:build/%=release/%)
|
||||
|
||||
.PHONY: test golint build vendor
|
||||
|
||||
$(eval $(call golang-version-check,1.13))
|
||||
|
||||
test: $(PKGS)
|
||||
|
||||
$(PKGS): golang-test-all-strict-deps
|
||||
$(call golang-test-all-strict,$@)
|
||||
|
||||
|
||||
run:
|
||||
@go run main.go
|
||||
|
||||
build:
|
||||
go build -o bin/$(EXECUTABLE) $(PKG)
|
||||
|
||||
build/$(EXECUTABLE)-v$(VERSION)-darwin-amd64:
|
||||
GOARCH=amd64 GOOS=darwin go build -o "$@/$(EXECUTABLE)"
|
||||
build/$(EXECUTABLE)-v$(VERSION)-linux-amd64:
|
||||
GOARCH=amd64 GOOS=linux go build -o "$@/$(EXECUTABLE)"
|
||||
build/$(EXECUTABLE)-v$(VERSION)-windows-amd64:
|
||||
GOARCH=amd64 GOOS=windows go build -o "$@/$(EXECUTABLE).exe"
|
||||
build: $(BUILDS)
|
||||
%.tar.gz: %
|
||||
tar -C `dirname $<` -zcvf "$<.tar.gz" `basename $<`
|
||||
$(RELEASE_ARTIFACTS): release/% : build/%
|
||||
mkdir -p release
|
||||
cp $< $@
|
||||
release: $(RELEASE_ARTIFACTS)
|
||||
|
||||
clean:
|
||||
rm -rf build release
|
||||
|
||||
|
||||
install_deps:
|
||||
go mod vendor
|
39
vendor/github.com/Clever/gitsem/README.md
generated
vendored
Normal file
39
vendor/github.com/Clever/gitsem/README.md
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
# gitsem
|
||||
|
||||
A command line utility for managing semantically versioned (semver) git tags.
|
||||
|
||||
Run this in a git repository to bump the version and write the new data back to the VERSION file.
|
||||
It will also create a version commit and (optional) tag, and fail if the repo is not clean.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
$ go get github.com/Clever/gitsem
|
||||
```
|
||||
|
||||
## Example
|
||||
```shell
|
||||
$ gitsem patch
|
||||
$ gitsem -m "Upgrade to %s for reasons" patch
|
||||
$ gitsem minor
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```shell
|
||||
gitsem [options] version
|
||||
```
|
||||
|
||||
`version` can be one of: `newversion | patch | minor | major`
|
||||
|
||||
The version argument should be a valid semver string, or a field of a semver string (one of "patch", "minor", or "major").
|
||||
In the second case, the existing version will be incremented by 1 in the specified field.
|
||||
|
||||
### 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.
|
||||
- `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).
|
1
vendor/github.com/Clever/gitsem/VERSION
generated
vendored
Normal file
1
vendor/github.com/Clever/gitsem/VERSION
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
1.1.0
|
39
vendor/github.com/Clever/gitsem/git.go
generated
vendored
Normal file
39
vendor/github.com/Clever/gitsem/git.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isRepoClean() (bool, error) {
|
||||
cmd := exec.Command("git", "status", "--porcelain")
|
||||
result := &bytes.Buffer{}
|
||||
cmd.Stdout = result
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result.String() == "", nil
|
||||
}
|
||||
|
||||
func repoRoot() (string, error) {
|
||||
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
|
||||
result := &bytes.Buffer{}
|
||||
cmd.Stdout = result
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(result.String()), nil
|
||||
}
|
||||
|
||||
func addFile(path string) error {
|
||||
return exec.Command("git", "add", path).Run()
|
||||
}
|
||||
|
||||
func commit(message string) error {
|
||||
return exec.Command("git", "commit", "-m", message).Run()
|
||||
}
|
||||
|
||||
func tag(version string) error {
|
||||
return exec.Command("git", "tag", version).Run()
|
||||
}
|
156
vendor/github.com/Clever/gitsem/golang.mk
generated
vendored
Normal file
156
vendor/github.com/Clever/gitsem/golang.mk
generated
vendored
Normal 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
|
126
vendor/github.com/Clever/gitsem/main.go
generated
vendored
Normal file
126
vendor/github.com/Clever/gitsem/main.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"gopkg.in/blang/semver.v1"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func commitMessage(message, version string) string {
|
||||
if strings.Contains(message, "%s") {
|
||||
return fmt.Sprintf(message, version)
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func getCurrentVersion(path string) (*semver.Version, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return &semver.Version{}, nil
|
||||
}
|
||||
contents, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return semver.New(strings.TrimSpace(string(contents)))
|
||||
}
|
||||
|
||||
const versionFileName = "VERSION"
|
||||
|
||||
func exitWithError(message string) {
|
||||
fmt.Fprintf(os.Stderr, message+"\n\n")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func bump(old *semver.Version, part string) *semver.Version {
|
||||
// We don't want to mutate the input, but there's no Clone or Copy method on a semver.Version,
|
||||
// so we make a new one by parsing the string version of the old one.
|
||||
// We ignore any errors because we know it's valid semver.
|
||||
new, _ := semver.New(old.String())
|
||||
switch part {
|
||||
case "major":
|
||||
new.Major++
|
||||
new.Minor = 0
|
||||
new.Patch = 0
|
||||
case "minor":
|
||||
new.Minor++
|
||||
new.Patch = 0
|
||||
case "patch":
|
||||
new.Patch++
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s: [options] version\n\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "version can be one of: newversion | patch | minor | major\n\n")
|
||||
fmt.Fprintf(os.Stderr, "options:\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
message := flag.String("m", "%s", "commit message for version commit")
|
||||
help := flag.Bool("h", false, "print usage and exit")
|
||||
shouldTag := flag.Bool("tag", true, "whether or not to make a tag at the version commit")
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *message == "" {
|
||||
exitWithError("missing message")
|
||||
}
|
||||
|
||||
if clean, err := isRepoClean(); err != nil {
|
||||
log.Fatal(err)
|
||||
} else if !clean {
|
||||
log.Fatal("repo isn't clean")
|
||||
}
|
||||
|
||||
root, err := repoRoot()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
versionFile := filepath.Join(root, versionFileName)
|
||||
version, err := getCurrentVersion(versionFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if len(flag.Args()) != 1 {
|
||||
exitWithError("gitsem takes exactly one non-flag argument: version")
|
||||
}
|
||||
|
||||
newVersion := flag.Args()[0]
|
||||
switch newVersion {
|
||||
case "patch", "minor", "major":
|
||||
version = bump(version, newVersion)
|
||||
default:
|
||||
if version, err = semver.New(newVersion); err != nil {
|
||||
log.Fatalf("failed to parse %s as semver: %s", newVersion, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(versionFile, []byte(version.String()), 0666); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := addFile(versionFile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
versionString := "v" + version.String()
|
||||
*message = commitMessage(*message, versionString)
|
||||
if err := commit(*message); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if *shouldTag {
|
||||
if err := tag(versionString); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
fmt.Println(versionString)
|
||||
}
|
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
* @microsoft/containerplat
|
2
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
2
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
@ -1,4 +1,4 @@
|
||||
# go-winio
|
||||
# go-winio [](https://github.com/microsoft/go-winio/actions/workflows/ci.yml)
|
||||
|
||||
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
|
||||
|
274
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
274
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
@ -1,137 +1,137 @@
|
||||
package winio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type fileFullEaInformation struct {
|
||||
NextEntryOffset uint32
|
||||
Flags uint8
|
||||
NameLength uint8
|
||||
ValueLength uint16
|
||||
}
|
||||
|
||||
var (
|
||||
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
||||
|
||||
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
||||
errEaNameTooLarge = errors.New("extended attribute name too large")
|
||||
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||
)
|
||||
|
||||
// ExtendedAttribute represents a single Windows EA.
|
||||
type ExtendedAttribute struct {
|
||||
Name string
|
||||
Value []byte
|
||||
Flags uint8
|
||||
}
|
||||
|
||||
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
||||
var info fileFullEaInformation
|
||||
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||
if err != nil {
|
||||
err = errInvalidEaBuffer
|
||||
return
|
||||
}
|
||||
|
||||
nameOffset := fileFullEaInformationSize
|
||||
nameLen := int(info.NameLength)
|
||||
valueOffset := nameOffset + int(info.NameLength) + 1
|
||||
valueLen := int(info.ValueLength)
|
||||
nextOffset := int(info.NextEntryOffset)
|
||||
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
||||
err = errInvalidEaBuffer
|
||||
return
|
||||
}
|
||||
|
||||
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
||||
ea.Value = b[valueOffset : valueOffset+valueLen]
|
||||
ea.Flags = info.Flags
|
||||
if info.NextEntryOffset != 0 {
|
||||
nb = b[info.NextEntryOffset:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
||||
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
||||
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
||||
for len(b) != 0 {
|
||||
ea, nb, err := parseEa(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eas = append(eas, ea)
|
||||
b = nb
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||
return errEaNameTooLarge
|
||||
}
|
||||
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
||||
return errEaValueTooLarge
|
||||
}
|
||||
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
||||
withPadding := (entrySize + 3) &^ 3
|
||||
nextOffset := uint32(0)
|
||||
if !last {
|
||||
nextOffset = withPadding
|
||||
}
|
||||
info := fileFullEaInformation{
|
||||
NextEntryOffset: nextOffset,
|
||||
Flags: ea.Flags,
|
||||
NameLength: uint8(len(ea.Name)),
|
||||
ValueLength: uint16(len(ea.Value)),
|
||||
}
|
||||
|
||||
err := binary.Write(buf, binary.LittleEndian, &info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write([]byte(ea.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write(ea.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
||||
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for i := range eas {
|
||||
last := false
|
||||
if i == len(eas)-1 {
|
||||
last = true
|
||||
}
|
||||
|
||||
err := writeEa(&buf, &eas[i], last)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
package winio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type fileFullEaInformation struct {
|
||||
NextEntryOffset uint32
|
||||
Flags uint8
|
||||
NameLength uint8
|
||||
ValueLength uint16
|
||||
}
|
||||
|
||||
var (
|
||||
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
||||
|
||||
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
||||
errEaNameTooLarge = errors.New("extended attribute name too large")
|
||||
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||
)
|
||||
|
||||
// ExtendedAttribute represents a single Windows EA.
|
||||
type ExtendedAttribute struct {
|
||||
Name string
|
||||
Value []byte
|
||||
Flags uint8
|
||||
}
|
||||
|
||||
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
||||
var info fileFullEaInformation
|
||||
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||
if err != nil {
|
||||
err = errInvalidEaBuffer
|
||||
return
|
||||
}
|
||||
|
||||
nameOffset := fileFullEaInformationSize
|
||||
nameLen := int(info.NameLength)
|
||||
valueOffset := nameOffset + int(info.NameLength) + 1
|
||||
valueLen := int(info.ValueLength)
|
||||
nextOffset := int(info.NextEntryOffset)
|
||||
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
||||
err = errInvalidEaBuffer
|
||||
return
|
||||
}
|
||||
|
||||
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
||||
ea.Value = b[valueOffset : valueOffset+valueLen]
|
||||
ea.Flags = info.Flags
|
||||
if info.NextEntryOffset != 0 {
|
||||
nb = b[info.NextEntryOffset:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
||||
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
||||
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
||||
for len(b) != 0 {
|
||||
ea, nb, err := parseEa(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eas = append(eas, ea)
|
||||
b = nb
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||
return errEaNameTooLarge
|
||||
}
|
||||
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
||||
return errEaValueTooLarge
|
||||
}
|
||||
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
||||
withPadding := (entrySize + 3) &^ 3
|
||||
nextOffset := uint32(0)
|
||||
if !last {
|
||||
nextOffset = withPadding
|
||||
}
|
||||
info := fileFullEaInformation{
|
||||
NextEntryOffset: nextOffset,
|
||||
Flags: ea.Flags,
|
||||
NameLength: uint8(len(ea.Name)),
|
||||
ValueLength: uint16(len(ea.Value)),
|
||||
}
|
||||
|
||||
err := binary.Write(buf, binary.LittleEndian, &info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write([]byte(ea.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write(ea.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
||||
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for i := range eas {
|
||||
last := false
|
||||
if i == len(eas)-1 {
|
||||
last = true
|
||||
}
|
||||
|
||||
err := writeEa(&buf, &eas[i], last)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
18
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
18
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
@ -16,6 +16,7 @@ import (
|
||||
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
type atomicBool int32
|
||||
|
||||
@ -79,6 +80,7 @@ type win32File struct {
|
||||
wg sync.WaitGroup
|
||||
wgLock sync.RWMutex
|
||||
closing atomicBool
|
||||
socket bool
|
||||
readDeadline deadlineHandler
|
||||
writeDeadline deadlineHandler
|
||||
}
|
||||
@ -109,7 +111,13 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||
}
|
||||
|
||||
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||
return makeWin32File(h)
|
||||
// If we return the result of makeWin32File directly, it can result in an
|
||||
// interface-wrapped nil, rather than a nil interface value.
|
||||
f, err := makeWin32File(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// closeHandle closes the resources associated with a Win32 handle
|
||||
@ -190,6 +198,10 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
|
||||
if f.closing.isSet() {
|
||||
err = ErrFileClosed
|
||||
}
|
||||
} else if err != nil && f.socket {
|
||||
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||
var bytes, flags uint32
|
||||
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
|
||||
}
|
||||
case <-timeout:
|
||||
cancelIoEx(f.handle, &c.o)
|
||||
@ -265,6 +277,10 @@ func (f *win32File) Flush() error {
|
||||
return syscall.FlushFileBuffers(f.handle)
|
||||
}
|
||||
|
||||
func (f *win32File) Fd() uintptr {
|
||||
return uintptr(f.handle)
|
||||
}
|
||||
|
||||
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||
d.setLock.Lock()
|
||||
defer d.setLock.Unlock()
|
||||
|
39
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
39
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
@ -5,28 +5,22 @@ package winio
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
|
||||
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
|
||||
|
||||
const (
|
||||
fileBasicInfo = 0
|
||||
fileIDInfo = 0x12
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// FileBasicInfo contains file access time and file attributes information.
|
||||
type FileBasicInfo struct {
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
|
||||
FileAttributes uintptr // includes padding
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
|
||||
FileAttributes uint32
|
||||
pad uint32 // padding
|
||||
}
|
||||
|
||||
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
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}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
@ -35,13 +29,32 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
|
||||
// SetFileBasicInfo sets times and attributes for a file.
|
||||
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}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
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
|
||||
// unique on a system.
|
||||
type FileIDInfo struct {
|
||||
@ -52,7 +65,7 @@ type FileIDInfo struct {
|
||||
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
||||
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
||||
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}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
|
307
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
307
vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
@ -0,0 +1,307 @@
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Microsoft/go-winio/pkg/guid"
|
||||
)
|
||||
|
||||
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||
|
||||
const (
|
||||
afHvSock = 34 // AF_HYPERV
|
||||
|
||||
socketError = ^uintptr(0)
|
||||
)
|
||||
|
||||
// An HvsockAddr is an address for a AF_HYPERV socket.
|
||||
type HvsockAddr struct {
|
||||
VMID guid.GUID
|
||||
ServiceID guid.GUID
|
||||
}
|
||||
|
||||
type rawHvsockAddr struct {
|
||||
Family uint16
|
||||
_ uint16
|
||||
VMID guid.GUID
|
||||
ServiceID guid.GUID
|
||||
}
|
||||
|
||||
// Network returns the address's network name, "hvsock".
|
||||
func (addr *HvsockAddr) Network() string {
|
||||
return "hvsock"
|
||||
}
|
||||
|
||||
func (addr *HvsockAddr) String() string {
|
||||
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
|
||||
}
|
||||
|
||||
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
||||
func VsockServiceID(port uint32) guid.GUID {
|
||||
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
|
||||
g.Data1 = port
|
||||
return g
|
||||
}
|
||||
|
||||
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
||||
return rawHvsockAddr{
|
||||
Family: afHvSock,
|
||||
VMID: addr.VMID,
|
||||
ServiceID: addr.ServiceID,
|
||||
}
|
||||
}
|
||||
|
||||
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
|
||||
addr.VMID = raw.VMID
|
||||
addr.ServiceID = raw.ServiceID
|
||||
}
|
||||
|
||||
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||
type HvsockListener struct {
|
||||
sock *win32File
|
||||
addr HvsockAddr
|
||||
}
|
||||
|
||||
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||
type HvsockConn struct {
|
||||
sock *win32File
|
||||
local, remote HvsockAddr
|
||||
}
|
||||
|
||||
func newHvSocket() (*win32File, error) {
|
||||
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("socket", err)
|
||||
}
|
||||
f, err := makeWin32File(fd)
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
f.socket = true
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ListenHvsock listens for connections on the specified hvsock address.
|
||||
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
|
||||
l := &HvsockListener{addr: *addr}
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", err)
|
||||
}
|
||||
sa := addr.raw()
|
||||
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
||||
}
|
||||
err = syscall.Listen(sock.handle, 16)
|
||||
if err != nil {
|
||||
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
|
||||
}
|
||||
return &HvsockListener{sock: sock, addr: *addr}, nil
|
||||
}
|
||||
|
||||
func (l *HvsockListener) opErr(op string, err error) error {
|
||||
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address.
|
||||
func (l *HvsockListener) Addr() net.Addr {
|
||||
return &l.addr
|
||||
}
|
||||
|
||||
// Accept waits for the next connection and returns it.
|
||||
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, l.opErr("accept", err)
|
||||
}
|
||||
defer func() {
|
||||
if sock != nil {
|
||||
sock.Close()
|
||||
}
|
||||
}()
|
||||
c, err := l.sock.prepareIo()
|
||||
if err != nil {
|
||||
return nil, l.opErr("accept", err)
|
||||
}
|
||||
defer l.sock.wg.Done()
|
||||
|
||||
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
||||
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
||||
var addrbuf [addrlen * 2]byte
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
|
||||
_, err = l.sock.asyncIo(c, nil, bytes, err)
|
||||
if err != nil {
|
||||
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||
}
|
||||
conn := &HvsockConn{
|
||||
sock: sock,
|
||||
}
|
||||
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
||||
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
||||
sock = nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Close closes the listener, causing any pending Accept calls to fail.
|
||||
func (l *HvsockListener) Close() error {
|
||||
return l.sock.Close()
|
||||
}
|
||||
|
||||
/* Need to finish ConnectEx handling
|
||||
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
|
||||
sock, err := newHvSocket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if sock != nil {
|
||||
sock.Close()
|
||||
}
|
||||
}()
|
||||
c, err := sock.prepareIo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sock.wg.Done()
|
||||
var bytes uint32
|
||||
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
|
||||
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn := &HvsockConn{
|
||||
sock: sock,
|
||||
remote: *addr,
|
||||
}
|
||||
sock = nil
|
||||
return conn, nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) Read(b []byte) (int, error) {
|
||||
c, err := conn.sock.prepareIo()
|
||||
if err != nil {
|
||||
return 0, conn.opErr("read", err)
|
||||
}
|
||||
defer conn.sock.wg.Done()
|
||||
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||
var flags, bytes uint32
|
||||
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
|
||||
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err)
|
||||
if err != nil {
|
||||
if _, ok := err.(syscall.Errno); ok {
|
||||
err = os.NewSyscallError("wsarecv", err)
|
||||
}
|
||||
return 0, conn.opErr("read", err)
|
||||
} else if n == 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) Write(b []byte) (int, error) {
|
||||
t := 0
|
||||
for len(b) != 0 {
|
||||
n, err := conn.write(b)
|
||||
if err != nil {
|
||||
return t + n, err
|
||||
}
|
||||
t += n
|
||||
b = b[n:]
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) write(b []byte) (int, error) {
|
||||
c, err := conn.sock.prepareIo()
|
||||
if err != nil {
|
||||
return 0, conn.opErr("write", err)
|
||||
}
|
||||
defer conn.sock.wg.Done()
|
||||
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||
var bytes uint32
|
||||
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
|
||||
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err)
|
||||
if err != nil {
|
||||
if _, ok := err.(syscall.Errno); ok {
|
||||
err = os.NewSyscallError("wsasend", err)
|
||||
}
|
||||
return 0, conn.opErr("write", err)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close closes the socket connection, failing any pending read or write calls.
|
||||
func (conn *HvsockConn) Close() error {
|
||||
return conn.sock.Close()
|
||||
}
|
||||
|
||||
func (conn *HvsockConn) shutdown(how int) error {
|
||||
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("shutdown", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseRead shuts down the read end of the socket.
|
||||
func (conn *HvsockConn) CloseRead() error {
|
||||
err := conn.shutdown(syscall.SHUT_RD)
|
||||
if err != nil {
|
||||
return conn.opErr("close", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
|
||||
// no more data will be written.
|
||||
func (conn *HvsockConn) CloseWrite() error {
|
||||
err := conn.shutdown(syscall.SHUT_WR)
|
||||
if err != nil {
|
||||
return conn.opErr("close", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the connection.
|
||||
func (conn *HvsockConn) LocalAddr() net.Addr {
|
||||
return &conn.local
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address of the connection.
|
||||
func (conn *HvsockConn) RemoteAddr() net.Addr {
|
||||
return &conn.remote
|
||||
}
|
||||
|
||||
// SetDeadline implements the net.Conn SetDeadline method.
|
||||
func (conn *HvsockConn) SetDeadline(t time.Time) error {
|
||||
conn.SetReadDeadline(t)
|
||||
conn.SetWriteDeadline(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
||||
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
|
||||
return conn.sock.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
||||
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
|
||||
return conn.sock.SetWriteDeadline(t)
|
||||
}
|
277
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
277
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
@ -3,10 +3,13 @@
|
||||
package winio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
@ -15,10 +18,51 @@ import (
|
||||
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
|
||||
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
|
||||
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
|
||||
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
|
||||
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
|
||||
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile
|
||||
//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
|
||||
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U
|
||||
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl
|
||||
|
||||
type ioStatusBlock struct {
|
||||
Status, Information uintptr
|
||||
}
|
||||
|
||||
type objectAttributes struct {
|
||||
Length uintptr
|
||||
RootDirectory uintptr
|
||||
ObjectName *unicodeString
|
||||
Attributes uintptr
|
||||
SecurityDescriptor *securityDescriptor
|
||||
SecurityQoS uintptr
|
||||
}
|
||||
|
||||
type unicodeString struct {
|
||||
Length uint16
|
||||
MaximumLength uint16
|
||||
Buffer uintptr
|
||||
}
|
||||
|
||||
type securityDescriptor struct {
|
||||
Revision byte
|
||||
Sbz1 byte
|
||||
Control uint16
|
||||
Owner uintptr
|
||||
Group uintptr
|
||||
Sacl uintptr
|
||||
Dacl uintptr
|
||||
}
|
||||
|
||||
type ntstatus int32
|
||||
|
||||
func (status ntstatus) Err() error {
|
||||
if status >= 0 {
|
||||
return nil
|
||||
}
|
||||
return rtlNtStatusToDosError(status)
|
||||
}
|
||||
|
||||
const (
|
||||
cERROR_PIPE_BUSY = syscall.Errno(231)
|
||||
@ -26,21 +70,20 @@ const (
|
||||
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
||||
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
||||
|
||||
cPIPE_ACCESS_DUPLEX = 0x3
|
||||
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
|
||||
cSECURITY_SQOS_PRESENT = 0x100000
|
||||
cSECURITY_ANONYMOUS = 0
|
||||
|
||||
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
|
||||
|
||||
cPIPE_UNLIMITED_INSTANCES = 255
|
||||
|
||||
cNMPWAIT_USE_DEFAULT_WAIT = 0
|
||||
cNMPWAIT_NOWAIT = 1
|
||||
cSECURITY_SQOS_PRESENT = 0x100000
|
||||
cSECURITY_ANONYMOUS = 0
|
||||
|
||||
cPIPE_TYPE_MESSAGE = 4
|
||||
|
||||
cPIPE_READMODE_MESSAGE = 2
|
||||
|
||||
cFILE_OPEN = 1
|
||||
cFILE_CREATE = 2
|
||||
|
||||
cFILE_PIPE_MESSAGE_TYPE = 1
|
||||
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
|
||||
|
||||
cSE_DACL_PRESENT = 4
|
||||
)
|
||||
|
||||
var (
|
||||
@ -121,6 +164,11 @@ func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||
// zero-byte message, ensure that all future Read() calls
|
||||
// also return EOF.
|
||||
f.readEOF = true
|
||||
} else if err == syscall.ERROR_MORE_DATA {
|
||||
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
||||
// and the message still has more bytes. Treat this as a success, since
|
||||
// this package presents all named pipes as byte streams.
|
||||
err = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@ -133,40 +181,60 @@ func (s pipeAddress) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
||||
func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) {
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return syscall.Handle(0), ctx.Err()
|
||||
default:
|
||||
h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||
if err == nil {
|
||||
return h, nil
|
||||
}
|
||||
if err != cERROR_PIPE_BUSY {
|
||||
return h, &os.PathError{Err: err, Op: "open", Path: *path}
|
||||
}
|
||||
// Wait 10 msec and try again. This is a rather simplistic
|
||||
// view, as we always try each 10 milliseconds.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DialPipe connects to a named pipe by path, timing out if the connection
|
||||
// takes longer than the specified duration. If timeout is nil, then the timeout
|
||||
// is the default timeout established by the pipe server.
|
||||
// takes longer than the specified duration. If timeout is nil, then we use
|
||||
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
|
||||
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||
var absTimeout time.Time
|
||||
if timeout != nil {
|
||||
absTimeout = time.Now().Add(*timeout)
|
||||
} else {
|
||||
absTimeout = time.Now().Add(2 * time.Second)
|
||||
}
|
||||
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
|
||||
conn, err := DialPipeContext(ctx, path)
|
||||
if err == context.DeadlineExceeded {
|
||||
return nil, ErrTimeout
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
|
||||
// cancellation or timeout.
|
||||
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 h syscall.Handle
|
||||
for {
|
||||
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)
|
||||
if err != cERROR_PIPE_BUSY {
|
||||
break
|
||||
}
|
||||
now := time.Now()
|
||||
var ms uint32
|
||||
if absTimeout.IsZero() {
|
||||
ms = cNMPWAIT_USE_DEFAULT_WAIT
|
||||
} else if now.After(absTimeout) {
|
||||
ms = cNMPWAIT_NOWAIT
|
||||
} else {
|
||||
ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000)
|
||||
}
|
||||
err = waitNamedPipe(path, ms)
|
||||
if err != nil {
|
||||
if err == cERROR_SEM_TIMEOUT {
|
||||
return nil, ErrTimeout
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
h, err = tryDialPipe(ctx, &path, access)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "open", Path: path, Err: err}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var flags uint32
|
||||
@ -175,16 +243,6 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var state uint32
|
||||
err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if state&cPIPE_READMODE_MESSAGE != 0 {
|
||||
return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")}
|
||||
}
|
||||
|
||||
f, err := makeWin32File(h)
|
||||
if err != nil {
|
||||
syscall.Close(h)
|
||||
@ -207,43 +265,87 @@ type acceptResponse struct {
|
||||
}
|
||||
|
||||
type win32PipeListener struct {
|
||||
firstHandle syscall.Handle
|
||||
path string
|
||||
securityDescriptor []byte
|
||||
config PipeConfig
|
||||
acceptCh chan (chan acceptResponse)
|
||||
closeCh chan int
|
||||
doneCh chan int
|
||||
firstHandle syscall.Handle
|
||||
path string
|
||||
config PipeConfig
|
||||
acceptCh chan (chan acceptResponse)
|
||||
closeCh chan int
|
||||
doneCh chan int
|
||||
}
|
||||
|
||||
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
||||
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
|
||||
if first {
|
||||
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
|
||||
}
|
||||
|
||||
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
|
||||
if c.MessageMode {
|
||||
mode |= cPIPE_TYPE_MESSAGE
|
||||
}
|
||||
|
||||
sa := &syscall.SecurityAttributes{}
|
||||
sa.Length = uint32(unsafe.Sizeof(*sa))
|
||||
if securityDescriptor != nil {
|
||||
len := uint32(len(securityDescriptor))
|
||||
sa.SecurityDescriptor = localAlloc(0, len)
|
||||
defer localFree(sa.SecurityDescriptor)
|
||||
copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor)
|
||||
}
|
||||
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa)
|
||||
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
||||
path16, err := syscall.UTF16FromString(path)
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||
}
|
||||
|
||||
var oa objectAttributes
|
||||
oa.Length = unsafe.Sizeof(oa)
|
||||
|
||||
var ntPath unicodeString
|
||||
if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil {
|
||||
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||
}
|
||||
defer localFree(ntPath.Buffer)
|
||||
oa.ObjectName = &ntPath
|
||||
|
||||
// The security descriptor is only needed for the first pipe.
|
||||
if first {
|
||||
if sd != nil {
|
||||
len := uint32(len(sd))
|
||||
sdb := localAlloc(0, len)
|
||||
defer localFree(sdb)
|
||||
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
|
||||
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
|
||||
} else {
|
||||
// Construct the default named pipe security descriptor.
|
||||
var dacl uintptr
|
||||
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
|
||||
return 0, fmt.Errorf("getting default named pipe ACL: %s", err)
|
||||
}
|
||||
defer localFree(dacl)
|
||||
|
||||
sdb := &securityDescriptor{
|
||||
Revision: 1,
|
||||
Control: cSE_DACL_PRESENT,
|
||||
Dacl: dacl,
|
||||
}
|
||||
oa.SecurityDescriptor = sdb
|
||||
}
|
||||
}
|
||||
|
||||
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS)
|
||||
if c.MessageMode {
|
||||
typ |= cFILE_PIPE_MESSAGE_TYPE
|
||||
}
|
||||
|
||||
disposition := uint32(cFILE_OPEN)
|
||||
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
|
||||
if first {
|
||||
disposition = cFILE_CREATE
|
||||
// By not asking for read or write access, the named pipe file system
|
||||
// will put this pipe into an initially disconnected state, blocking
|
||||
// client connections until the next call with first == false.
|
||||
access = syscall.SYNCHRONIZE
|
||||
}
|
||||
|
||||
timeout := int64(-50 * 10000) // 50ms
|
||||
|
||||
var (
|
||||
h syscall.Handle
|
||||
iosb ioStatusBlock
|
||||
)
|
||||
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err()
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||
}
|
||||
|
||||
runtime.KeepAlive(ntPath)
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
|
||||
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -327,10 +429,10 @@ type PipeConfig struct {
|
||||
// when the pipe is in message mode.
|
||||
MessageMode bool
|
||||
|
||||
// InputBufferSize specifies the size the input buffer, in bytes.
|
||||
// InputBufferSize specifies the size of the input buffer, in bytes.
|
||||
InputBufferSize int32
|
||||
|
||||
// OutputBufferSize specifies the size the input buffer, in bytes.
|
||||
// OutputBufferSize specifies the size of the output buffer, in bytes.
|
||||
OutputBufferSize int32
|
||||
}
|
||||
|
||||
@ -354,22 +456,13 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Immediately open and then close a client handle so that the named pipe is
|
||||
// created but not currently accepting connections.
|
||||
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||
if err != nil {
|
||||
syscall.Close(h)
|
||||
return nil, err
|
||||
}
|
||||
syscall.Close(h2)
|
||||
l := &win32PipeListener{
|
||||
firstHandle: h,
|
||||
path: path,
|
||||
securityDescriptor: sd,
|
||||
config: *c,
|
||||
acceptCh: make(chan (chan acceptResponse)),
|
||||
closeCh: make(chan int),
|
||||
doneCh: make(chan int),
|
||||
firstHandle: h,
|
||||
path: path,
|
||||
config: *c,
|
||||
acceptCh: make(chan (chan acceptResponse)),
|
||||
closeCh: make(chan int),
|
||||
doneCh: make(chan int),
|
||||
}
|
||||
go l.listenerRoutine()
|
||||
return l, nil
|
||||
|
237
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
237
vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
// +build windows
|
||||
|
||||
// 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.
|
||||
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
||||
// and the Windows (mixed-endian) encoding. See here for details:
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
||||
package guid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
||||
// how the entirety of the rest of the GUID is interpreted.
|
||||
type Variant uint8
|
||||
|
||||
// The variants specified by RFC 4122.
|
||||
const (
|
||||
// VariantUnknown specifies a GUID variant which does not conform to one of
|
||||
// the variant encodings specified in RFC 4122.
|
||||
VariantUnknown Variant = iota
|
||||
VariantNCS
|
||||
VariantRFC4122
|
||||
VariantMicrosoft
|
||||
VariantFuture
|
||||
)
|
||||
|
||||
// Version specifies how the bits in the GUID were generated. For instance, a
|
||||
// version 4 GUID is randomly generated, and a version 5 is generated from the
|
||||
// hash of an input string.
|
||||
type Version uint8
|
||||
|
||||
var _ = (encoding.TextMarshaler)(GUID{})
|
||||
var _ = (encoding.TextUnmarshaler)(&GUID{})
|
||||
|
||||
// GUID represents a GUID/UUID. It has the same structure as
|
||||
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||
// that type. It is defined as its own type so that stringification and
|
||||
// marshaling can be supported. The representation matches that used by native
|
||||
// Windows code.
|
||||
type GUID windows.GUID
|
||||
|
||||
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
|
||||
func NewV4() (GUID, error) {
|
||||
var b [16]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return GUID{}, err
|
||||
}
|
||||
|
||||
g := FromArray(b)
|
||||
g.setVersion(4) // Version 4 means randomly generated.
|
||||
g.setVariant(VariantRFC4122)
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
|
||||
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
|
||||
// and the sample code treats it as a series of bytes, so we do the same here.
|
||||
//
|
||||
// Some implementations, such as those found on Windows, treat the name as a
|
||||
// big-endian UTF16 stream of bytes. If that is desired, the string can be
|
||||
// encoded as such before being passed to this function.
|
||||
func NewV5(namespace GUID, name []byte) (GUID, error) {
|
||||
b := sha1.New()
|
||||
namespaceBytes := namespace.ToArray()
|
||||
b.Write(namespaceBytes[:])
|
||||
b.Write(name)
|
||||
|
||||
a := [16]byte{}
|
||||
copy(a[:], b.Sum(nil))
|
||||
|
||||
g := FromArray(a)
|
||||
g.setVersion(5) // Version 5 means generated from a string.
|
||||
g.setVariant(VariantRFC4122)
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
|
||||
var g GUID
|
||||
g.Data1 = order.Uint32(b[0:4])
|
||||
g.Data2 = order.Uint16(b[4:6])
|
||||
g.Data3 = order.Uint16(b[6:8])
|
||||
copy(g.Data4[:], b[8:16])
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
|
||||
b := [16]byte{}
|
||||
order.PutUint32(b[0:4], g.Data1)
|
||||
order.PutUint16(b[4:6], g.Data2)
|
||||
order.PutUint16(b[6:8], g.Data3)
|
||||
copy(b[8:16], g.Data4[:])
|
||||
return b
|
||||
}
|
||||
|
||||
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
|
||||
func FromArray(b [16]byte) GUID {
|
||||
return fromArray(b, binary.BigEndian)
|
||||
}
|
||||
|
||||
// ToArray returns an array of 16 bytes representing the GUID in big-endian
|
||||
// encoding.
|
||||
func (g GUID) ToArray() [16]byte {
|
||||
return g.toArray(binary.BigEndian)
|
||||
}
|
||||
|
||||
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
|
||||
func FromWindowsArray(b [16]byte) GUID {
|
||||
return fromArray(b, binary.LittleEndian)
|
||||
}
|
||||
|
||||
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
|
||||
// encoding.
|
||||
func (g GUID) ToWindowsArray() [16]byte {
|
||||
return g.toArray(binary.LittleEndian)
|
||||
}
|
||||
|
||||
func (g GUID) String() string {
|
||||
return fmt.Sprintf(
|
||||
"%08x-%04x-%04x-%04x-%012x",
|
||||
g.Data1,
|
||||
g.Data2,
|
||||
g.Data3,
|
||||
g.Data4[:2],
|
||||
g.Data4[2:])
|
||||
}
|
||||
|
||||
// FromString parses a string containing a GUID and returns the GUID. The only
|
||||
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
// format.
|
||||
func FromString(s string) (GUID, error) {
|
||||
if len(s) != 36 {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
var g GUID
|
||||
|
||||
data1, err := strconv.ParseUint(s[0:8], 16, 32)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data1 = uint32(data1)
|
||||
|
||||
data2, err := strconv.ParseUint(s[9:13], 16, 16)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data2 = uint16(data2)
|
||||
|
||||
data3, err := strconv.ParseUint(s[14:18], 16, 16)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data3 = uint16(data3)
|
||||
|
||||
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
||||
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
g.Data4[i] = uint8(v)
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *GUID) setVariant(v Variant) {
|
||||
d := g.Data4[0]
|
||||
switch v {
|
||||
case VariantNCS:
|
||||
d = (d & 0x7f)
|
||||
case VariantRFC4122:
|
||||
d = (d & 0x3f) | 0x80
|
||||
case VariantMicrosoft:
|
||||
d = (d & 0x1f) | 0xc0
|
||||
case VariantFuture:
|
||||
d = (d & 0x0f) | 0xe0
|
||||
case VariantUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid variant: %d", v))
|
||||
}
|
||||
g.Data4[0] = d
|
||||
}
|
||||
|
||||
// Variant returns the GUID variant, as defined in RFC 4122.
|
||||
func (g GUID) Variant() Variant {
|
||||
b := g.Data4[0]
|
||||
if b&0x80 == 0 {
|
||||
return VariantNCS
|
||||
} else if b&0xc0 == 0x80 {
|
||||
return VariantRFC4122
|
||||
} else if b&0xe0 == 0xc0 {
|
||||
return VariantMicrosoft
|
||||
} else if b&0xe0 == 0xe0 {
|
||||
return VariantFuture
|
||||
}
|
||||
return VariantUnknown
|
||||
}
|
||||
|
||||
func (g *GUID) setVersion(v Version) {
|
||||
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
|
||||
}
|
||||
|
||||
// Version returns the GUID version, as defined in RFC 4122.
|
||||
func (g GUID) Version() Version {
|
||||
return Version((g.Data3 & 0xF000) >> 12)
|
||||
}
|
||||
|
||||
// MarshalText returns the textual representation of the GUID.
|
||||
func (g GUID) MarshalText() ([]byte, error) {
|
||||
return []byte(g.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
|
||||
// into this GUID.
|
||||
func (g *GUID) UnmarshalText(text []byte) error {
|
||||
g2, err := FromString(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*g = g2
|
||||
return nil
|
||||
}
|
5
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
5
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
@ -28,8 +28,9 @@ const (
|
||||
|
||||
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
|
||||
|
||||
SeBackupPrivilege = "SeBackupPrivilege"
|
||||
SeRestorePrivilege = "SeRestorePrivilege"
|
||||
SeBackupPrivilege = "SeBackupPrivilege"
|
||||
SeRestorePrivilege = "SeRestorePrivilege"
|
||||
SeSecurityPrivilege = "SeSecurityPrivilege"
|
||||
)
|
||||
|
||||
const (
|
||||
|
2
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
2
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
@ -1,3 +1,3 @@
|
||||
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
|
||||
//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
|
||||
|
603
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
603
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
File diff suppressed because it is too large
Load Diff
7
vendor/github.com/aktau/github-release/.gitignore
generated
vendored
Normal file
7
vendor/github.com/aktau/github-release/.gitignore
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/github-release
|
||||
/go-app
|
||||
|
||||
*.exe
|
||||
|
||||
/bin
|
||||
/var/cache
|
10
vendor/github.com/aktau/github-release/CONTRIBUTING.md
generated
vendored
Normal file
10
vendor/github.com/aktau/github-release/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
## Releasing new versions
|
||||
|
||||
1) Bump the version in github/version.go
|
||||
|
||||
2) Add a commit with message "github-release v1.2.3"
|
||||
|
||||
3) Run `git tag v1.2.3` where "1.2.3" stands in for the version you actually
|
||||
want.
|
||||
|
||||
4) Run `make release`. Be sure to have `GITHUB_TOKEN` set in your environment.
|
49
vendor/github.com/aktau/github-release/Gopkg.lock
generated
vendored
Normal file
49
vendor/github.com/aktau/github-release/Gopkg.lock
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f9339c912bbdda81302633ad7e99a28dfa5a639c864061f1929510a9a64aa74"
|
||||
name = "github.com/dustin/go-humanize"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9f541cc9db5d55bce703bd99987c9d5cb8eea45e"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7462b19f7e4adb24e966b77213c693dad09c04d58ac8d29ddf449ff1b1c91d11"
|
||||
name = "github.com/kevinburke/rest"
|
||||
packages = [
|
||||
"restclient",
|
||||
"resterror",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "22cd0577e450f2fa21313f7eaf42b41a178291c1"
|
||||
version = "2.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f2dda646a25cf3b9f6f6931d86ef0b85b64979697e5833cdcbc0c23f2586d996"
|
||||
name = "github.com/tomnomnom/linkheader"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "02ca5825eb8097f10d9cc53da78481a85ad84e04"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d1d1683ae67edaebf7e51151f934943bf2f53b71260818d843ab7233aa145533"
|
||||
name = "github.com/voxelbrain/goptions"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "26cb8b04692384f4dc269de3b5fcf3e2ef78573e"
|
||||
version = "2.5.11"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/dustin/go-humanize",
|
||||
"github.com/kevinburke/rest/restclient",
|
||||
"github.com/tomnomnom/linkheader",
|
||||
"github.com/voxelbrain/goptions",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
24
vendor/github.com/aktau/github-release/Gopkg.toml
generated
vendored
Normal file
24
vendor/github.com/aktau/github-release/Gopkg.toml
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/dustin/go-humanize"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/kevinburke/rest"
|
||||
version = "2.5.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tomnomnom/linkheader"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/voxelbrain/goptions"
|
||||
version = "2.5.11"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
21
vendor/github.com/aktau/github-release/LICENSE
generated
vendored
Normal file
21
vendor/github.com/aktau/github-release/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2017 Nicolas Hillegeer
|
||||
Copyright (c) 2020 Meter, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
100
vendor/github.com/aktau/github-release/Makefile
generated
vendored
Normal file
100
vendor/github.com/aktau/github-release/Makefile
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
SHELL=/bin/bash -o pipefail
|
||||
|
||||
LAST_TAG := $(shell git describe --abbrev=0 --tags)
|
||||
|
||||
USER := github-release
|
||||
EXECUTABLE := github-release
|
||||
|
||||
# only include the amd64 binaries, otherwise the github release will become
|
||||
# too big
|
||||
UNIX_EXECUTABLES := \
|
||||
darwin/amd64/$(EXECUTABLE) \
|
||||
freebsd/amd64/$(EXECUTABLE) \
|
||||
linux/amd64/$(EXECUTABLE)
|
||||
WIN_EXECUTABLES := \
|
||||
windows/amd64/$(EXECUTABLE).exe
|
||||
|
||||
COMPRESSED_EXECUTABLES=$(UNIX_EXECUTABLES:%=%.bz2) $(WIN_EXECUTABLES:%.exe=%.zip)
|
||||
COMPRESSED_EXECUTABLE_TARGETS=$(COMPRESSED_EXECUTABLES:%=bin/%)
|
||||
|
||||
UPLOAD_CMD = bin/tmp/$(EXECUTABLE) upload -u $(USER) -r $(EXECUTABLE) -t $(LAST_TAG) -n $(subst /,-,$(FILE)) -f bin/$(FILE)
|
||||
|
||||
all: $(EXECUTABLE)
|
||||
|
||||
# the executable used to perform the upload, dogfooding and all...
|
||||
bin/tmp/$(EXECUTABLE):
|
||||
go build -v -o "$@"
|
||||
|
||||
# arm
|
||||
bin/linux/arm/5/$(EXECUTABLE):
|
||||
GOARM=5 GOARCH=arm GOOS=linux go build -o "$@"
|
||||
bin/linux/arm/7/$(EXECUTABLE):
|
||||
GOARM=7 GOARCH=arm GOOS=linux go build -o "$@"
|
||||
|
||||
# 386
|
||||
bin/darwin/386/$(EXECUTABLE):
|
||||
GOARCH=386 GOOS=darwin go build -o "$@"
|
||||
bin/linux/386/$(EXECUTABLE):
|
||||
GOARCH=386 GOOS=linux go build -o "$@"
|
||||
bin/windows/386/$(EXECUTABLE):
|
||||
GOARCH=386 GOOS=windows go build -o "$@"
|
||||
|
||||
# amd64
|
||||
bin/freebsd/amd64/$(EXECUTABLE):
|
||||
GOARCH=amd64 GOOS=freebsd go build -o "$@"
|
||||
bin/darwin/amd64/$(EXECUTABLE):
|
||||
GOARCH=amd64 GOOS=darwin go build -o "$@"
|
||||
bin/linux/amd64/$(EXECUTABLE):
|
||||
GOARCH=amd64 GOOS=linux go build -o "$@"
|
||||
bin/windows/amd64/$(EXECUTABLE).exe:
|
||||
GOARCH=amd64 GOOS=windows go build -o "$@"
|
||||
|
||||
# compressed artifacts, makes a huge difference (Go executable is ~9MB,
|
||||
# after compressing ~2MB)
|
||||
%.bz2: %
|
||||
bzip2 --keep "$<"
|
||||
%.zip: %.exe
|
||||
zip "$@" "$<"
|
||||
|
||||
# git tag -a v$(RELEASE) -m 'release $(RELEASE)'
|
||||
release: clean
|
||||
ifndef GITHUB_TOKEN
|
||||
@echo "Please set GITHUB_TOKEN in the environment to perform a release"
|
||||
exit 1
|
||||
endif
|
||||
docker run --rm --volume $(PWD)/var/cache:/root/.cache/go-build \
|
||||
--env GITHUB_TOKEN=$(GITHUB_TOKEN) \
|
||||
--volume "$(PWD)":/go/src/github.com/github-release/github-release \
|
||||
--workdir /go/src/github.com/github-release/github-release \
|
||||
meterup/ubuntu-golang:latest \
|
||||
./release \
|
||||
"$(MAKE) bin/tmp/$(EXECUTABLE) $(COMPRESSED_EXECUTABLE_TARGETS) && \
|
||||
git log --format=%B $(LAST_TAG) -1 | \
|
||||
bin/tmp/$(EXECUTABLE) release -u $(USER) -r $(EXECUTABLE) \
|
||||
-t $(LAST_TAG) -n $(LAST_TAG) -d - || true && \
|
||||
$(foreach FILE,$(COMPRESSED_EXECUTABLES),$(UPLOAD_CMD);)"
|
||||
|
||||
# install and/or update all dependencies, run this from the project directory
|
||||
# go get -u ./...
|
||||
# go test -i ./
|
||||
dep:
|
||||
go list -f '{{join .Deps "\n"}}' | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs go get -u
|
||||
|
||||
$(EXECUTABLE): dep
|
||||
go build -o "$@"
|
||||
|
||||
install:
|
||||
go install
|
||||
|
||||
clean:
|
||||
rm go-app || true
|
||||
rm $(EXECUTABLE) || true
|
||||
rm -rf bin/
|
||||
|
||||
lint:
|
||||
go vet ./...
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
.PHONY: clean release dep install
|
138
vendor/github.com/aktau/github-release/README.md
generated
vendored
Normal file
138
vendor/github.com/aktau/github-release/README.md
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
github-release
|
||||
==============
|
||||
|
||||
A small commandline app written in Go that allows you to easily create
|
||||
and delete releases of your projects on Github. In addition it allows
|
||||
you to attach files to those releases.
|
||||
|
||||
It interacts with the [github releases API][releases-api]. Though it's entirely
|
||||
possible to [do all these things with cURL][curl], it's not really that
|
||||
user-friendly. For example, you need to first query the API to find the id of
|
||||
the release you want, before you can upload an artifact. `github-release` takes
|
||||
care of those little details.
|
||||
|
||||
[curl]: https://github.com/blog/1645-releases-api-preview
|
||||
[releases-api]: https://developer.github.com/v3/repos/releases
|
||||
|
||||
It might still be a bit rough around the edges, pull requests are
|
||||
welcome!
|
||||
|
||||
How to install
|
||||
==============
|
||||
|
||||
If you don't have the Go toolset installed, and you don't want to, but
|
||||
still want to use the app, you can download binaries for your platform
|
||||
on the [releases
|
||||
page](https://github.com/github-release/github-release/releases/latest). Yes, that's
|
||||
dogfooding, check the makefile!
|
||||
|
||||
If you have Go installed, you can just do:
|
||||
|
||||
```sh
|
||||
go get github.com/github-release/github-release
|
||||
```
|
||||
|
||||
This will automatically download, compile and install the app.
|
||||
|
||||
After that you should have a `github-release` executable in your
|
||||
`$GOPATH/bin`.
|
||||
|
||||
How to use
|
||||
==========
|
||||
|
||||
**NOTE**: for these examples I've [created a github token][token] and set it as
|
||||
the env variable `GITHUB_TOKEN`. `github-release` will automatically pick it up
|
||||
from the environment so that you don't have to pass it as an argument.
|
||||
|
||||
[token]: https://help.github.com/articles/creating-an-access-token-for-command-line-use
|
||||
|
||||
```sh
|
||||
# set your token
|
||||
export GITHUB_TOKEN=...
|
||||
|
||||
# check the help
|
||||
$ github-release --help
|
||||
|
||||
# make your tag and upload
|
||||
$ git tag ... && git push --tags
|
||||
|
||||
# check the current tags and existing releases of the repo
|
||||
$ github-release info -u aktau -r gofinance
|
||||
git tags:
|
||||
- v0.1.0 (commit: https://api.github.com/repos/aktau/gofinance/commits/f562727ce83ce8971a8569a1879219e41d56a756)
|
||||
releases:
|
||||
- v0.1.0, name: 'hoary ungar', description: 'something something dark side 2', id: 166740, tagged: 29/01/2014 at 14:27, published: 30/01/2014 at 16:20, draft: ✔, prerelease: ✗
|
||||
- artifact: github.go, downloads: 0, state: uploaded, type: application/octet-stream, size: 1.9KB, id: 68616
|
||||
|
||||
# create a formal release
|
||||
$ github-release release \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--tag v0.1.0 \
|
||||
--name "the wolf of source street" \
|
||||
--description "Not a movie, contrary to popular opinion. Still, my first release!" \
|
||||
--pre-release
|
||||
|
||||
# you've made a mistake, but you can edit the release without
|
||||
# having to delete it first (this also means you can edit without having
|
||||
# to upload your files again)
|
||||
$ github-release edit \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--tag v0.1.0 \
|
||||
--name "Highlander II: The Quickening" \
|
||||
--description "This is the actual description!"
|
||||
|
||||
# upload a file, for example the OSX/AMD64 binary of my gofinance app
|
||||
$ github-release upload \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--tag v0.1.0 \
|
||||
--name "gofinance-osx-amd64" \
|
||||
--file bin/darwin/amd64/gofinance
|
||||
|
||||
# upload other files...
|
||||
$ github-release upload ...
|
||||
|
||||
# you're not happy with it, so delete it
|
||||
$ github-release delete \
|
||||
--user aktau \
|
||||
--repo gofinance \
|
||||
--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
|
||||
=========================
|
||||
You can point to a different GitHub API endpoint via the environment variable ```GITHUB_API```:
|
||||
|
||||
```
|
||||
export GITHUB_API=http://github.company.com/api/v3
|
||||
```
|
||||
|
||||
Used libraries
|
||||
==============
|
||||
|
||||
| Package | Description | License |
|
||||
| ------------------------------------------------------------------------ | ------------------- | ------- |
|
||||
| [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) | humanize file sizes | MIT |
|
||||
| [github.com/tomnomnom/linkheader](https://github.com/tomnomnom/linkheader) | GH API pagination | MIT |
|
||||
| [github.com/voxelbrain/goptions](https://github.com/voxelbrain/goptions) | option parsing | BSD |
|
||||
| [github.com/kevinburke/rest](https://github.com/kevinburke/rest) | HTTP client | MIT |
|
||||
|
||||
Todo
|
||||
====
|
||||
|
||||
- Check if an artifact is already uploaded before starting a new upload
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
Copyright (c) 2014-2017, Nicolas Hillegeer. All rights reserved.
|
||||
Copyright (c) 2020, Meter, Inc. All rights reserved.
|
58
vendor/github.com/aktau/github-release/assets.go
generated
vendored
Normal file
58
vendor/github.com/aktau/github-release/assets.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/github-release/github-release/github"
|
||||
)
|
||||
|
||||
const (
|
||||
// GET /repos/:owner/:repo/releases/assets/:id
|
||||
// DELETE /repos/:owner/:repo/releases/assets/:id
|
||||
ASSET_URI = "/repos/%s/%s/releases/assets/%d"
|
||||
|
||||
// API: https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
|
||||
// GET /repos/:owner/:repo/releases/:id/assets
|
||||
ASSET_RELEASE_LIST_URI = "/repos/%s/%s/releases/%d/assets"
|
||||
)
|
||||
|
||||
type Asset struct {
|
||||
Url string `json:"url"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ContentType string `json:"content_type"`
|
||||
State string `json:"state"`
|
||||
Size uint64 `json:"size"`
|
||||
Downloads uint64 `json:"download_count"`
|
||||
Created time.Time `json:"created_at"`
|
||||
Published time.Time `json:"published_at"`
|
||||
}
|
||||
|
||||
// findAsset returns the asset if an asset with name can be found in assets,
|
||||
// otherwise returns nil.
|
||||
func findAsset(assets []Asset, name string) *Asset {
|
||||
for _, asset := range assets {
|
||||
if asset.Name == name {
|
||||
return &asset
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete sends a HTTP DELETE request for the given asset to Github. Returns
|
||||
// nil if the asset was deleted OR there was nothing to delete.
|
||||
func (a *Asset) Delete(user, repo, token string) error {
|
||||
URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) +
|
||||
fmt.Sprintf(ASSET_URI, user, repo, a.Id)
|
||||
resp, err := github.DoAuthRequest("DELETE", URL, "application/json", token, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete asset %s (ID: %d), HTTP error: %b", a.Name, a.Id, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("failed to delete asset %s (ID: %d), status: %s", a.Name, a.Id, resp.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
496
vendor/github.com/aktau/github-release/cmd.go
generated
vendored
Normal file
496
vendor/github.com/aktau/github-release/cmd.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/github-release/github-release/github"
|
||||
)
|
||||
|
||||
func infocmd(opt Options) error {
|
||||
user := nvls(opt.Info.User, EnvUser)
|
||||
authUser := nvls(opt.Info.AuthUser, EnvAuthUser)
|
||||
repo := nvls(opt.Info.Repo, EnvRepo)
|
||||
token := nvls(opt.Info.Token, EnvToken)
|
||||
tag := opt.Info.Tag
|
||||
|
||||
if user == "" || repo == "" {
|
||||
return fmt.Errorf("user and repo need to be passed as arguments")
|
||||
}
|
||||
|
||||
// Find regular git tags.
|
||||
foundTags, err := Tags(user, repo, authUser, token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch tags, %v", err)
|
||||
}
|
||||
if len(foundTags) == 0 {
|
||||
return fmt.Errorf("no tags available for %v/%v", user, repo)
|
||||
}
|
||||
|
||||
tags := foundTags[:0]
|
||||
for _, t := range foundTags {
|
||||
// If the user only requested one tag, filter out the rest.
|
||||
if tag == "" || t.Name == tag {
|
||||
tags = append(tags, t)
|
||||
}
|
||||
}
|
||||
|
||||
renderer := renderInfoText
|
||||
|
||||
if opt.Info.JSON {
|
||||
renderer = renderInfoJSON
|
||||
}
|
||||
|
||||
// List releases + assets.
|
||||
var releases []Release
|
||||
if tag == "" {
|
||||
// Get all releases.
|
||||
vprintf("%v/%v: getting information for all releases\n", user, repo)
|
||||
releases, err = Releases(user, repo, authUser, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Get only one release.
|
||||
vprintf("%v/%v/%v: getting information for the release\n", user, repo, tag)
|
||||
release, err := ReleaseOfTag(user, repo, tag, authUser, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
releases = []Release{*release}
|
||||
}
|
||||
|
||||
return renderer(tags, releases)
|
||||
}
|
||||
|
||||
func renderInfoText(tags []Tag, releases []Release) error {
|
||||
fmt.Println("tags:")
|
||||
for _, tag := range tags {
|
||||
fmt.Println("-", &tag)
|
||||
}
|
||||
|
||||
fmt.Println("releases:")
|
||||
for _, release := range releases {
|
||||
fmt.Println("-", &release)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderInfoJSON(tags []Tag, releases []Release) error {
|
||||
out := struct {
|
||||
Tags []Tag
|
||||
Releases []Release
|
||||
}{
|
||||
Tags: tags,
|
||||
Releases: releases,
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(&out)
|
||||
}
|
||||
|
||||
func uploadcmd(opt Options) error {
|
||||
user := nvls(opt.Upload.User, EnvUser)
|
||||
authUser := nvls(opt.Upload.AuthUser, EnvAuthUser)
|
||||
repo := nvls(opt.Upload.Repo, EnvRepo)
|
||||
token := nvls(opt.Upload.Token, EnvToken)
|
||||
tag := opt.Upload.Tag
|
||||
name := opt.Upload.Name
|
||||
label := opt.Upload.Label
|
||||
file := opt.Upload.File
|
||||
|
||||
vprintln("uploading...")
|
||||
|
||||
if file == nil {
|
||||
return fmt.Errorf("provided file was not valid")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := ValidateCredentials(user, repo, token, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the release corresponding to the entered tag, if any.
|
||||
rel, err := ReleaseOfTag(user, repo, tag, authUser, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the user has attempted to upload this asset before, someone could
|
||||
// expect it to be present in the release struct (rel.Assets). However,
|
||||
// we have to separately ask for the specific assets of this release.
|
||||
// Reason: the assets in the Release struct do not contain incomplete
|
||||
// uploads (which regrettably happen often using the Github API). See
|
||||
// issue #26.
|
||||
var assets []Asset
|
||||
client := github.NewClient(authUser, token, nil)
|
||||
client.SetBaseURL(EnvApiEndpoint)
|
||||
err = client.Get(fmt.Sprintf(ASSET_RELEASE_LIST_URI, user, repo, rel.Id), &assets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Incomplete (failed) uploads will have their state set to new. These
|
||||
// assets are (AFAIK) useless in all cases. The only thing they will do
|
||||
// is prevent the upload of another asset of the same name. To work
|
||||
// around this GH API weirdness, let's just delete assets if:
|
||||
//
|
||||
// 1. Their state is new.
|
||||
// 2. The user explicitly asked to delete/replace the asset with -R.
|
||||
if asset := findAsset(assets, name); asset != nil &&
|
||||
(asset.State == "new" || opt.Upload.Replace) {
|
||||
vprintf("asset (id: %d) already existed in state %s: removing...\n", asset.Id, asset.Name)
|
||||
if err := asset.Delete(user, repo, token); err != nil {
|
||||
return fmt.Errorf("could not replace asset: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("name", name)
|
||||
if label != "" {
|
||||
v.Set("label", label)
|
||||
}
|
||||
|
||||
url := rel.CleanUploadUrl() + "?" + v.Encode()
|
||||
|
||||
resp, err := github.DoAuthRequest("POST", url, "application/octet-stream",
|
||||
token, nil, file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create upload request to %v, %v", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
vprintln("RESPONSE:", resp)
|
||||
|
||||
var r io.Reader = resp.Body
|
||||
if VERBOSITY != 0 {
|
||||
r = io.TeeReader(r, os.Stderr)
|
||||
}
|
||||
var asset *Asset
|
||||
// For HTTP status 201 and 502, Github will return a JSON encoding of
|
||||
// the (partially) created asset.
|
||||
if resp.StatusCode == http.StatusBadGateway || resp.StatusCode == http.StatusCreated {
|
||||
vprintf("ASSET: ")
|
||||
asset = new(Asset)
|
||||
if err := json.NewDecoder(r).Decode(&asset); err != nil {
|
||||
return fmt.Errorf("upload failed (%s), could not unmarshal asset (err: %v)", resp.Status, err)
|
||||
}
|
||||
} else {
|
||||
vprintf("BODY: ")
|
||||
if msg, err := ToMessage(r); err == nil {
|
||||
return fmt.Errorf("could not upload, status code (%s), %v",
|
||||
resp.Status, msg)
|
||||
}
|
||||
return fmt.Errorf("could not upload, status code (%s)", resp.Status)
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusBadGateway {
|
||||
// 502 means the upload failed, but GitHub still retains metadata
|
||||
// (an asset in state "new"). Attempt to delete that now since it
|
||||
// would clutter the list of release assets.
|
||||
vprintf("asset (id: %d) failed to upload, it's now in state %s: removing...\n", asset.Id, asset.Name)
|
||||
if err := asset.Delete(user, repo, token); err != nil {
|
||||
return fmt.Errorf("upload failed (%s), could not delete partially uploaded asset (ID: %d, err: %v) in order to cleanly reset GH API state, please try again", resp.Status, asset.Id, err)
|
||||
}
|
||||
return fmt.Errorf("could not upload, status code (%s)", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadcmd(opt Options) error {
|
||||
user := nvls(opt.Download.User, EnvUser)
|
||||
authUser := nvls(opt.Download.AuthUser, EnvAuthUser)
|
||||
repo := nvls(opt.Download.Repo, EnvRepo)
|
||||
token := nvls(opt.Download.Token, EnvToken)
|
||||
tag := opt.Download.Tag
|
||||
name := opt.Download.Name
|
||||
latest := opt.Download.Latest
|
||||
|
||||
vprintln("downloading...")
|
||||
|
||||
if err := ValidateTarget(user, repo, tag, latest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the release corresponding to the entered tag, if any.
|
||||
var rel *Release
|
||||
var err error
|
||||
if latest {
|
||||
rel, err = LatestRelease(user, repo, authUser, token)
|
||||
} else {
|
||||
rel, err = ReleaseOfTag(user, repo, tag, authUser, token)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
asset := findAsset(rel.Assets, name)
|
||||
if asset == nil {
|
||||
return fmt.Errorf("coud not find asset named %s", name)
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
if token == "" {
|
||||
// Use the regular github.com site if we don't have a token.
|
||||
resp, err = http.Get("https://github.com" + fmt.Sprintf("/%s/%s/releases/download/%s/%s", user, repo, tag, name))
|
||||
} else {
|
||||
url := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf(ASSET_URI, user, repo, asset.Id)
|
||||
resp, err = github.DoAuthRequest("GET", url, "", token, map[string]string{
|
||||
"Accept": "application/octet-stream",
|
||||
}, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch releases, %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
vprintln("GET", resp.Request.URL, "->", resp)
|
||||
|
||||
contentLength, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("github did not respond with 200 OK but with %v", resp.Status)
|
||||
}
|
||||
|
||||
out := os.Stdout // Pipe the asset to stdout by default.
|
||||
if isCharDevice(out) {
|
||||
// If stdout is a char device, assume it's a TTY (terminal). In this
|
||||
// case, don't pipe th easset to stdout, but create it as a file in
|
||||
// the current working folder.
|
||||
if out, err = os.Create(name); err != nil {
|
||||
return fmt.Errorf("could not create file %s", name)
|
||||
}
|
||||
defer out.Close()
|
||||
}
|
||||
|
||||
return mustCopyN(out, resp.Body, contentLength)
|
||||
}
|
||||
|
||||
// mustCopyN attempts to copy exactly N bytes, if this fails, an error is
|
||||
// returned.
|
||||
func mustCopyN(w io.Writer, r io.Reader, n int64) error {
|
||||
an, err := io.Copy(w, r)
|
||||
if an != n {
|
||||
return fmt.Errorf("data did not match content length %d != %d", an, n)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ValidateTarget(user, repo, tag string, latest bool) error {
|
||||
if user == "" {
|
||||
return fmt.Errorf("empty user")
|
||||
}
|
||||
if repo == "" {
|
||||
return fmt.Errorf("empty repo")
|
||||
}
|
||||
if tag == "" && !latest {
|
||||
return fmt.Errorf("empty tag")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateCredentials(user, repo, token, tag string) error {
|
||||
if err := ValidateTarget(user, repo, tag, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if token == "" {
|
||||
return fmt.Errorf("empty token")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func releasecmd(opt Options) error {
|
||||
cmdopt := opt.Release
|
||||
user := nvls(cmdopt.User, EnvUser)
|
||||
repo := nvls(cmdopt.Repo, EnvRepo)
|
||||
token := nvls(cmdopt.Token, EnvToken)
|
||||
tag := cmdopt.Tag
|
||||
name := nvls(cmdopt.Name, tag)
|
||||
desc := nvls(cmdopt.Desc, tag)
|
||||
target := nvls(cmdopt.Target)
|
||||
draft := cmdopt.Draft
|
||||
prerelease := cmdopt.Prerelease
|
||||
|
||||
vprintln("releasing...")
|
||||
|
||||
if err := ValidateCredentials(user, repo, token, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if we need to read the description from stdin.
|
||||
if desc == "-" {
|
||||
b, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read description from stdin: %v", err)
|
||||
}
|
||||
desc = string(b)
|
||||
}
|
||||
|
||||
params := ReleaseCreate{
|
||||
TagName: tag,
|
||||
TargetCommitish: target,
|
||||
Name: name,
|
||||
Body: desc,
|
||||
Draft: draft,
|
||||
Prerelease: prerelease,
|
||||
}
|
||||
|
||||
/* encode params as json */
|
||||
payload, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't encode release creation params, %v", err)
|
||||
}
|
||||
reader := bytes.NewReader(payload)
|
||||
|
||||
// NB: Github appears to ignore the user here - the only thing that seems to
|
||||
// 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 {
|
||||
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()
|
||||
|
||||
vprintln("RESPONSE:", resp)
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
if resp.StatusCode == 422 {
|
||||
return fmt.Errorf("github returned %v (this is probably because the release already exists)",
|
||||
resp.Status)
|
||||
}
|
||||
return fmt.Errorf("github returned %v", resp.Status)
|
||||
}
|
||||
|
||||
if VERBOSITY != 0 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading response, %v", err)
|
||||
}
|
||||
vprintln("BODY:", string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func editcmd(opt Options) error {
|
||||
cmdopt := opt.Edit
|
||||
user := nvls(cmdopt.User, EnvUser)
|
||||
authUser := nvls(cmdopt.AuthUser, EnvAuthUser)
|
||||
repo := nvls(cmdopt.Repo, EnvRepo)
|
||||
token := nvls(cmdopt.Token, EnvToken)
|
||||
tag := cmdopt.Tag
|
||||
name := nvls(cmdopt.Name, tag)
|
||||
desc := nvls(cmdopt.Desc, tag)
|
||||
draft := cmdopt.Draft
|
||||
prerelease := cmdopt.Prerelease
|
||||
|
||||
vprintln("editing...")
|
||||
|
||||
if err := ValidateCredentials(user, repo, token, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := IdOfTag(user, repo, tag, authUser, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vprintf("release %v has id %v\n", tag, id)
|
||||
|
||||
// Check if we need to read the description from stdin.
|
||||
if desc == "-" {
|
||||
b, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read description from stdin: %v", err)
|
||||
}
|
||||
desc = string(b)
|
||||
}
|
||||
|
||||
/* the release create struct works for editing releases as well */
|
||||
params := ReleaseCreate{
|
||||
TagName: tag,
|
||||
Name: name,
|
||||
Body: desc,
|
||||
Draft: draft,
|
||||
Prerelease: prerelease,
|
||||
}
|
||||
|
||||
/* encode the parameters as JSON, as required by the github API */
|
||||
payload, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't encode release creation params, %v", err)
|
||||
}
|
||||
|
||||
URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id)
|
||||
resp, err := github.DoAuthRequest("PATCH", URL, "application/json", token, nil, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("while submitting %v, %v", string(payload), err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
vprintln("RESPONSE:", resp)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == 422 {
|
||||
return fmt.Errorf("github returned %v (this is probably because the release already exists)",
|
||||
resp.Status)
|
||||
}
|
||||
return fmt.Errorf("github returned unexpected status code %v", resp.Status)
|
||||
}
|
||||
|
||||
if VERBOSITY != 0 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading response, %v", err)
|
||||
}
|
||||
vprintln("BODY:", string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deletecmd(opt Options) error {
|
||||
user, repo, token, tag := nvls(opt.Delete.User, EnvUser),
|
||||
nvls(opt.Delete.Repo, EnvRepo),
|
||||
nvls(opt.Delete.Token, EnvToken),
|
||||
opt.Delete.Tag
|
||||
authUser := nvls(opt.Delete.AuthUser, EnvAuthUser)
|
||||
vprintln("deleting...")
|
||||
|
||||
id, err := IdOfTag(user, repo, tag, authUser, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vprintf("release %v has id %v\n", tag, id)
|
||||
|
||||
baseURL := nvls(EnvApiEndpoint, github.DefaultBaseURL)
|
||||
resp, err := github.DoAuthRequest("DELETE", baseURL+fmt.Sprintf("/repos/%s/%s/releases/%d",
|
||||
user, repo, id), "application/json", token, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("release deletion failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("could not delete the release corresponding to tag %s on repo %s/%s",
|
||||
tag, user, repo)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
6
vendor/github.com/aktau/github-release/commit.go
generated
vendored
Normal file
6
vendor/github.com/aktau/github-release/commit.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package main
|
||||
|
||||
type Commit struct {
|
||||
Sha string `json:"sha"`
|
||||
Url string `json:"url"`
|
||||
}
|
42
vendor/github.com/aktau/github-release/error.go
generated
vendored
Normal file
42
vendor/github.com/aktau/github-release/error.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/* usually when something goes wrong, github sends something like this back */
|
||||
type Message struct {
|
||||
Message string `json:"message"`
|
||||
Errors []GithubError `json:"errors"`
|
||||
}
|
||||
|
||||
type GithubError struct {
|
||||
Resource string `json:"resource"`
|
||||
Code string `json:"code"`
|
||||
Field string `json:"field"`
|
||||
}
|
||||
|
||||
/* transforms a stream into a Message, if it's valid json */
|
||||
func ToMessage(r io.Reader) (*Message, error) {
|
||||
var msg Message
|
||||
if err := json.NewDecoder(r).Decode(&msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func (m *Message) String() string {
|
||||
str := fmt.Sprintf("msg: %v, errors: ", m.Message)
|
||||
|
||||
errstr := make([]string, len(m.Errors))
|
||||
for idx, err := range m.Errors {
|
||||
errstr[idx] = fmt.Sprintf("[field: %v, code: %v]",
|
||||
err.Field, err.Code)
|
||||
}
|
||||
|
||||
return str + strings.Join(errstr, ", ")
|
||||
}
|
143
vendor/github.com/aktau/github-release/github-release.go
generated
vendored
Normal file
143
vendor/github.com/aktau/github-release/github-release.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/github-release/github-release/github"
|
||||
"github.com/voxelbrain/goptions"
|
||||
)
|
||||
|
||||
const GH_URL = "https://github.com"
|
||||
|
||||
type Options struct {
|
||||
Help goptions.Help `goptions:"-h, --help, description='Show this help'"`
|
||||
Verbosity []bool `goptions:"-v, --verbose, description='Be verbose'"`
|
||||
Quiet bool `goptions:"-q, --quiet, description='Do not print anything, even errors (except if --verbose is specified)'"`
|
||||
Version bool `goptions:"--version, description='Print version'"`
|
||||
|
||||
goptions.Verbs
|
||||
Download struct {
|
||||
Token string `goptions:"-s, --security-token, description='Github token ($GITHUB_TOKEN if set). required if repo is private.'"`
|
||||
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
|
||||
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
|
||||
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
|
||||
Latest bool `goptions:"-l, --latest, description='Download latest release (required if tag is not specified)',mutexgroup='input'"`
|
||||
Tag string `goptions:"-t, --tag, description='Git tag to download from (required if latest is not specified)', mutexgroup='input',obligatory"`
|
||||
Name string `goptions:"-n, --name, description='Name of the file', obligatory"`
|
||||
} `goptions:"download"`
|
||||
Upload struct {
|
||||
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
|
||||
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
|
||||
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
|
||||
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
|
||||
Tag string `goptions:"-t, --tag, description='Git tag to upload to', obligatory"`
|
||||
Name string `goptions:"-n, --name, description='Name of the file', obligatory"`
|
||||
Label string `goptions:"-l, --label, description='Label (description) of the file'"`
|
||||
File *os.File `goptions:"-f, --file, description='File to upload (use - for stdin)', rdonly, obligatory"`
|
||||
Replace bool `goptions:"-R, --replace, description='Replace asset with same name if it already exists (WARNING: not atomic, failure to upload will remove the original asset too)'"`
|
||||
} `goptions:"upload"`
|
||||
Release struct {
|
||||
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
|
||||
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
|
||||
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
|
||||
Tag string `goptions:"-t, --tag, obligatory, description='Git tag to create a release from'"`
|
||||
Name string `goptions:"-n, --name, description='Name of the release (defaults to tag)'"`
|
||||
Desc string `goptions:"-d, --description, description='Release description, use - for reading a description from stdin (defaults to tag)'"`
|
||||
Target string `goptions:"-c, --target, description='Commit SHA or branch to create release of (defaults to the repository default branch)'"`
|
||||
Draft bool `goptions:"--draft, description='The release is a draft'"`
|
||||
Prerelease bool `goptions:"-p, --pre-release, description='The release is a pre-release'"`
|
||||
} `goptions:"release"`
|
||||
Edit struct {
|
||||
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
|
||||
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
|
||||
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
|
||||
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
|
||||
Tag string `goptions:"-t, --tag, obligatory, description='Git tag to edit the release of'"`
|
||||
Name string `goptions:"-n, --name, description='New name of the release (defaults to tag)'"`
|
||||
Desc string `goptions:"-d, --description, description='New release description, use - for reading a description from stdin (defaults to tag)'"`
|
||||
Draft bool `goptions:"--draft, description='The release is a draft'"`
|
||||
Prerelease bool `goptions:"-p, --pre-release, description='The release is a pre-release'"`
|
||||
} `goptions:"edit"`
|
||||
Delete struct {
|
||||
Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"`
|
||||
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
|
||||
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
|
||||
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
|
||||
Tag string `goptions:"-t, --tag, obligatory, description='Git tag of release to delete'"`
|
||||
} `goptions:"delete"`
|
||||
Info struct {
|
||||
Token string `goptions:"-s, --security-token, description='Github token ($GITHUB_TOKEN if set). required if repo is private.'"`
|
||||
User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"`
|
||||
AuthUser string `goptions:"-a, --auth-user, description='Username for authenticating to the API (falls back to $GITHUB_AUTH_USER or $GITHUB_USER)'"`
|
||||
Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"`
|
||||
Tag string `goptions:"-t, --tag, description='Git tag to query (optional)'"`
|
||||
JSON bool `goptions:"-j, --json, description='Emit info as JSON instead of text'"`
|
||||
} `goptions:"info"`
|
||||
}
|
||||
|
||||
type Command func(Options) error
|
||||
|
||||
var commands = map[goptions.Verbs]Command{
|
||||
"download": downloadcmd,
|
||||
"upload": uploadcmd,
|
||||
"release": releasecmd,
|
||||
"edit": editcmd,
|
||||
"delete": deletecmd,
|
||||
"info": infocmd,
|
||||
}
|
||||
|
||||
var (
|
||||
VERBOSITY = 0
|
||||
)
|
||||
|
||||
var (
|
||||
// The user whose token is being used to authenticate to the API. If unset,
|
||||
// EnvUser is used.
|
||||
EnvAuthUser string
|
||||
EnvToken string
|
||||
EnvUser string
|
||||
EnvRepo string
|
||||
EnvApiEndpoint string
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnvToken = os.Getenv("GITHUB_TOKEN")
|
||||
EnvUser = os.Getenv("GITHUB_USER")
|
||||
EnvAuthUser = os.Getenv("GITHUB_AUTH_USER")
|
||||
EnvRepo = os.Getenv("GITHUB_REPO")
|
||||
EnvApiEndpoint = os.Getenv("GITHUB_API")
|
||||
|
||||
if EnvAuthUser == "" {
|
||||
EnvAuthUser = EnvUser
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
options := Options{}
|
||||
|
||||
goptions.ParseAndFail(&options)
|
||||
|
||||
if options.Version {
|
||||
fmt.Printf("github-release v%s\n", github.VERSION)
|
||||
return
|
||||
}
|
||||
|
||||
if len(options.Verbs) == 0 {
|
||||
goptions.PrintHelp()
|
||||
return
|
||||
}
|
||||
|
||||
VERBOSITY = len(options.Verbosity)
|
||||
github.VERBOSITY = VERBOSITY
|
||||
|
||||
if cmd, found := commands[options.Verbs]; found {
|
||||
err := cmd(options)
|
||||
if err != nil {
|
||||
if !options.Quiet {
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
12
vendor/github.com/aktau/github-release/release
generated
vendored
Normal file
12
vendor/github.com/aktau/github-release/release
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
main() {
|
||||
apt-get update && apt-get install zip
|
||||
git branch --set-upstream-to=origin/master release
|
||||
set -x
|
||||
exec /bin/bash -c "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
141
vendor/github.com/aktau/github-release/releases.go
generated
vendored
Normal file
141
vendor/github.com/aktau/github-release/releases.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/github-release/github-release/github"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
const (
|
||||
RELEASE_LIST_URI = "/repos/%s/%s/releases"
|
||||
RELEASE_LATEST_URI = "/repos/%s/%s/releases/latest"
|
||||
RELEASE_DATE_FORMAT = "02/01/2006 at 15:04"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
Url string `json:"url"`
|
||||
PageUrl string `json:"html_url"`
|
||||
UploadUrl string `json:"upload_url"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"body"`
|
||||
TagName string `json:"tag_name"`
|
||||
Draft bool `json:"draft"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
Created *time.Time `json:"created_at"`
|
||||
Published *time.Time `json:"published_at"`
|
||||
Assets []Asset `json:"assets"`
|
||||
}
|
||||
|
||||
func (r *Release) CleanUploadUrl() string {
|
||||
bracket := strings.Index(r.UploadUrl, "{")
|
||||
|
||||
if bracket == -1 {
|
||||
return r.UploadUrl
|
||||
}
|
||||
|
||||
return r.UploadUrl[0:bracket]
|
||||
}
|
||||
|
||||
func (r *Release) String() string {
|
||||
str := make([]string, len(r.Assets)+1)
|
||||
str[0] = fmt.Sprintf(
|
||||
"%s, name: '%s', description: '%s', id: %d, tagged: %s, published: %s, draft: %v, prerelease: %v",
|
||||
r.TagName, r.Name, r.Description, r.Id,
|
||||
timeFmtOr(r.Created, RELEASE_DATE_FORMAT, ""),
|
||||
timeFmtOr(r.Published, RELEASE_DATE_FORMAT, ""),
|
||||
Mark(r.Draft), Mark(r.Prerelease))
|
||||
|
||||
for idx, asset := range r.Assets {
|
||||
str[idx+1] = fmt.Sprintf(" - artifact: %s, downloads: %d, state: %s, type: %s, size: %s, id: %d",
|
||||
asset.Name, asset.Downloads, asset.State, asset.ContentType,
|
||||
humanize.Bytes(asset.Size), asset.Id)
|
||||
}
|
||||
|
||||
return strings.Join(str, "\n")
|
||||
}
|
||||
|
||||
type ReleaseCreate struct {
|
||||
TagName string `json:"tag_name"`
|
||||
TargetCommitish string `json:"target_commitish,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Body string `json:"body"`
|
||||
Draft bool `json:"draft"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
}
|
||||
|
||||
func Releases(user, repo, authUser, token string) ([]Release, error) {
|
||||
var releases []Release
|
||||
client := github.NewClient(authUser, token, nil)
|
||||
client.SetBaseURL(EnvApiEndpoint)
|
||||
err := client.Get(fmt.Sprintf(RELEASE_LIST_URI, user, repo), &releases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
func latestReleaseApi(user, repo, authUser, token string) (*Release, error) {
|
||||
var release Release
|
||||
client := github.NewClient(authUser, token, nil)
|
||||
client.SetBaseURL(EnvApiEndpoint)
|
||||
return &release, client.Get(fmt.Sprintf(RELEASE_LATEST_URI, user, repo), &release)
|
||||
}
|
||||
|
||||
func LatestRelease(user, repo, authUser, token string) (*Release, error) {
|
||||
// If latestReleaseApi DOESN'T give an error, return the release.
|
||||
if latestRelease, err := latestReleaseApi(user, repo, authUser, token); err == nil {
|
||||
return latestRelease, nil
|
||||
}
|
||||
|
||||
// The enterprise api doesnt support the latest release endpoint. Get
|
||||
// all releases and compare the published date to get the latest.
|
||||
releases, err := Releases(user, repo, authUser, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var latestRelIndex = -1
|
||||
maxDate := time.Time{}
|
||||
for i, release := range releases {
|
||||
if relDate := *release.Published; relDate.After(maxDate) {
|
||||
maxDate = relDate
|
||||
latestRelIndex = i
|
||||
}
|
||||
}
|
||||
if latestRelIndex == -1 {
|
||||
return nil, fmt.Errorf("could not find the latest release")
|
||||
}
|
||||
|
||||
vprintln("Scanning ", len(releases), "releases, latest release is", releases[latestRelIndex])
|
||||
return &releases[latestRelIndex], nil
|
||||
}
|
||||
|
||||
func ReleaseOfTag(user, repo, tag, authUser, token string) (*Release, error) {
|
||||
releases, err := Releases(user, repo, authUser, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
if release.TagName == tag {
|
||||
return &release, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find the release corresponding to tag %s", tag)
|
||||
}
|
||||
|
||||
/* find the release-id of the specified tag */
|
||||
func IdOfTag(user, repo, tag, authUser, token string) (int, error) {
|
||||
release, err := ReleaseOfTag(user, repo, tag, authUser, token)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return release.Id, nil
|
||||
}
|
30
vendor/github.com/aktau/github-release/tags.go
generated
vendored
Normal file
30
vendor/github.com/aktau/github-release/tags.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/github-release/github-release/github"
|
||||
)
|
||||
|
||||
const (
|
||||
TAGS_URI = "/repos/%s/%s/tags"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
Commit Commit `json:"commit"`
|
||||
ZipBallUrl string `json:"zipball_url"`
|
||||
TarBallUrl string `json:"tarball_url"`
|
||||
}
|
||||
|
||||
func (t *Tag) String() string {
|
||||
return t.Name + " (commit: " + t.Commit.Url + ")"
|
||||
}
|
||||
|
||||
// Get the tags associated with a repo.
|
||||
func Tags(user, repo, authUser, token string) ([]Tag, error) {
|
||||
var tags []Tag
|
||||
client := github.NewClient(authUser, token, nil)
|
||||
client.SetBaseURL(EnvApiEndpoint)
|
||||
return tags, client.Get(fmt.Sprintf(TAGS_URI, user, repo), &tags)
|
||||
}
|
9
vendor/github.com/aktau/github-release/term.go
generated
vendored
Normal file
9
vendor/github.com/aktau/github-release/term.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
func Mark(ok bool) string {
|
||||
if ok {
|
||||
return "✔"
|
||||
} else {
|
||||
return "✗"
|
||||
}
|
||||
}
|
52
vendor/github.com/aktau/github-release/util.go
generated
vendored
Normal file
52
vendor/github.com/aktau/github-release/util.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
/* nvls returns the first value in xs that is not empty. */
|
||||
func nvls(xs ...string) string {
|
||||
for _, s := range xs {
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func vprintln(a ...interface{}) (int, error) {
|
||||
if VERBOSITY > 0 {
|
||||
return fmt.Fprintln(os.Stderr, a...)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func vprintf(format string, a ...interface{}) (int, error) {
|
||||
if VERBOSITY > 0 {
|
||||
return fmt.Fprintf(os.Stderr, format, a...)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// formats time `t` as `fmt` if it is not nil, otherwise returns `def`
|
||||
func timeFmtOr(t *time.Time, fmt, def string) string {
|
||||
if t == nil {
|
||||
return def
|
||||
}
|
||||
return t.Format(fmt)
|
||||
}
|
||||
|
||||
// isCharDevice returns true if f is a character device (panics if f can't
|
||||
// be stat'ed).
|
||||
func isCharDevice(f *os.File) bool {
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return (stat.Mode() & os.ModeCharDevice) != 0
|
||||
}
|
1
vendor/github.com/aktau/github-release/version.go
generated
vendored
Normal file
1
vendor/github.com/aktau/github-release/version.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package main
|
17
vendor/github.com/andygrunwald/go-jira/.travis.yml
generated
vendored
17
vendor/github.com/andygrunwald/go-jira/.travis.yml
generated
vendored
@ -1,17 +0,0 @@
|
||||
language: go
|
||||
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
|
||||
before_install:
|
||||
- go get -t ./...
|
||||
|
||||
script:
|
||||
- GOMAXPROCS=4 GORACE="halt_on_error=1" go test -race -v ./...
|
104
vendor/github.com/andygrunwald/go-jira/CHANGELOG.md
generated
vendored
Normal file
104
vendor/github.com/andygrunwald/go-jira/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
# Changelog
|
||||
|
||||
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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add IssueLinkTypeService with GetList and test ([261889a](https://github.com/andygrunwald/go-jira/commit/261889adc63623fcea0fa8cab0d5da26eec37e68))
|
||||
* add worklog update method ([9ff562a](https://github.com/andygrunwald/go-jira/commit/9ff562ae3ea037961f277be10412ad0a42ff8a6f))
|
||||
* Implement get remote links method ([1946cac](https://github.com/andygrunwald/go-jira/commit/1946cac0fe6ee91f784e3dda3c12f3f30f7115b8))
|
||||
* Implement issue link type DELETE ([e37cc6c](https://github.com/andygrunwald/go-jira/commit/e37cc6c6897830492c070667ab8b68bd85683fc3))
|
||||
* Implement issue link type GET ([57538b9](https://github.com/andygrunwald/go-jira/commit/57538b926c558e97940760a30bdc16cdd37ef4f1))
|
||||
* Implement issue link type POST ([75b9df8](https://github.com/andygrunwald/go-jira/commit/75b9df8b01557f01dc318d33c0bc2841a9c084eb))
|
||||
* Implement issue link type PUT ([48a15c1](https://github.com/andygrunwald/go-jira/commit/48a15c10443a3cff78f0fb2c8034dd772320e238))
|
||||
* provide access to issue transitions loaded from JIRA API ([7530b7c](https://github.com/andygrunwald/go-jira/commit/7530b7cd8266d82cdb4afe831518986772e742ba))
|
||||
|
||||
### [1.11.1](https://github.com/andygrunwald/go-jira/compare/v1.11.0...v1.11.1) (2019-10-17)
|
||||
|
||||
## [1.11.0](https://github.com/andygrunwald/go-jira/compare/v1.10.0...v1.11.0) (2019-10-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add AccountID and AccountType to GroupMember struct ([216e005](https://github.com/andygrunwald/go-jira/commit/216e0056d6385eba9d31cb37e6ff64314860d2cc))
|
||||
* Add AccountType and Locale to User struct ([52ab347](https://github.com/andygrunwald/go-jira/commit/52ab34790307144087f0d9bf86c93a2b2209fe46))
|
||||
* Add GetAllStatuses ([afc96b1](https://github.com/andygrunwald/go-jira/commit/afc96b18d17b77e32cec9e1ac7e4f5dec7e627f5))
|
||||
* Add GetMyFilters to FilterService ([ebae19d](https://github.com/andygrunwald/go-jira/commit/ebae19dda6afd0e54578f30300bc36012381e99b))
|
||||
* Add Search to FilterService ([38a755b](https://github.com/andygrunwald/go-jira/commit/38a755b407cd70d11fe2e2897d814552ca29ab51))
|
||||
* add support for JWT auth with qsh needed by add-ons ([a8bdfed](https://github.com/andygrunwald/go-jira/commit/a8bdfed27ff42a9bb0468b8cf192871780919def))
|
||||
* AddGetBoardConfiguration ([fd698c5](https://github.com/andygrunwald/go-jira/commit/fd698c57163f248f21285d5ebc6a3bb60d46694f))
|
||||
* Replace http.Client with interface for extensibility ([b59a65c](https://github.com/andygrunwald/go-jira/commit/b59a65c365dcefd42e135579e9b7ce9c9c006489))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix fixversion description tag ([8383e2f](https://github.com/andygrunwald/go-jira/commit/8383e2f5f145d04f6bcdb47fb12a95b58bdcedfa))
|
||||
* Fix typos in filter_test.go ([e9a261c](https://github.com/andygrunwald/go-jira/commit/e9a261c52249073345e5895b22e2cf4d7286497a))
|
||||
|
||||
# [1.10.0](https://github.com/andygrunwald/go-jira/compare/v1.9.0...v1.10.0) (2019-05-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* empty SearchOptions causing malformed request ([b3bf8c2](https://github.com/andygrunwald/go-jira/commit/b3bf8c2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added DeleteAttachment ([e93c0e1](https://github.com/andygrunwald/go-jira/commit/e93c0e1))
|
||||
|
||||
|
||||
|
||||
# [1.9.0](https://github.com/andygrunwald/go-jira/compare/v1.8.0...v1.9.0) (2019-05-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **issues:** Added support for AddWorklog and GetWorklogs ([1ebd7e7](https://github.com/andygrunwald/go-jira/commit/1ebd7e7))
|
||||
|
||||
|
||||
|
||||
# [1.8.0](https://github.com/andygrunwald/go-jira/compare/v1.7.0...v1.8.0) (2019-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add PriorityService to the main ([8491cb0](https://github.com/andygrunwald/go-jira/commit/8491cb0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **filter:** Add GetFavouriteList to FilterService. ([645898e](https://github.com/andygrunwald/go-jira/commit/645898e))
|
||||
* Add get all priorities ([1c63e25](https://github.com/andygrunwald/go-jira/commit/1c63e25))
|
||||
* 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 StatusCategory GetList ([049a756](https://github.com/andygrunwald/go-jira/commit/049a756))
|
36
vendor/github.com/andygrunwald/go-jira/Gopkg.lock
generated
vendored
36
vendor/github.com/andygrunwald/go-jira/Gopkg.lock
generated
vendored
@ -1,36 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fatih/structs"
|
||||
packages = ["."]
|
||||
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-querystring"
|
||||
packages = ["query"]
|
||||
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/trivago/tgo"
|
||||
packages = [
|
||||
"tcontainer",
|
||||
"treflect"
|
||||
]
|
||||
revision = "e4d1ddd28c17dd89ed26327cf69fded22060671b"
|
||||
version = "v1.0.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "e84ca9eea6d233e0947b0d760913db2983fd4cbf6fd0d8690c737a71affb635c"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
46
vendor/github.com/andygrunwald/go-jira/Gopkg.toml
generated
vendored
46
vendor/github.com/andygrunwald/go-jira/Gopkg.toml
generated
vendored
@ -1,46 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatih/structs"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-querystring"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/trivago/tgo"
|
||||
version = "1.0.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
27
vendor/github.com/andygrunwald/go-jira/Makefile
generated
vendored
27
vendor/github.com/andygrunwald/go-jira/Makefile
generated
vendored
@ -1,2 +1,25 @@
|
||||
test:
|
||||
go test -v ./...
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
.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)
|
||||
|
161
vendor/github.com/andygrunwald/go-jira/README.md
generated
vendored
161
vendor/github.com/andygrunwald/go-jira/README.md
generated
vendored
@ -1,31 +1,37 @@
|
||||
# go-jira
|
||||
|
||||
[](https://godoc.org/github.com/andygrunwald/go-jira)
|
||||
[](https://travis-ci.org/andygrunwald/go-jira)
|
||||
[](https://github.com/andygrunwald/go-jira/actions/workflows/testing.yml)
|
||||
[](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).
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* Authentication (HTTP Basic, OAuth, Session Cookie)
|
||||
* Create and retrieve issues
|
||||
* 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/).
|
||||
|
||||
## Compatible JIRA versions
|
||||
## Requirements
|
||||
|
||||
This package was tested against JIRA v6.3.4 and v7.1.2.
|
||||
* Go >= 1.14
|
||||
* 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
|
||||
|
||||
It is go gettable
|
||||
|
||||
$ go get github.com/andygrunwald/go-jira
|
||||
```bash
|
||||
go get github.com/andygrunwald/go-jira
|
||||
```
|
||||
|
||||
For stable versions you can use one of our tags with [gopkg.in](http://labix.org/gopkg.in). E.g.
|
||||
|
||||
@ -40,14 +46,16 @@ import (
|
||||
|
||||
(optional) to run unit / example tests:
|
||||
|
||||
$ cd $GOPATH/src/github.com/andygrunwald/go-jira
|
||||
$ go test -v ./...
|
||||
```bash
|
||||
cd $GOPATH/src/github.com/andygrunwald/go-jira
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
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
|
||||
|
||||
@ -87,7 +95,9 @@ 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.
|
||||
|
||||
#### Basic auth example
|
||||
#### Token (Jira on Atlassian Cloud)
|
||||
|
||||
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.
|
||||
|
||||
@ -95,7 +105,7 @@ A more thorough, [runnable example](examples/basicauth/main.go) is provided in t
|
||||
func main() {
|
||||
tp := jira.BasicAuthTransport{
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
Password: "token",
|
||||
}
|
||||
|
||||
client, err := jira.NewClient(tp.Client(), "https://my.jira.com")
|
||||
@ -106,29 +116,15 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Authenticate with session cookie
|
||||
#### Basic (self-hosted Jira)
|
||||
|
||||
A more thorough, [runnable example](examples/cookieauth/main.go) is provided in the examples directory.
|
||||
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/).
|
||||
|
||||
Note: The `AuthURL` is almost always going to have the path `/rest/auth/1/session`
|
||||
|
||||
```go
|
||||
tp := jira.CookieAuthTransport{
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
AuthURL: "https://my.jira.com/rest/auth/1/session",
|
||||
}
|
||||
|
||||
client, err := jira.NewClient(tp.Client(), "https://my.jira.com")
|
||||
u, _, err := client.User.Get("admin")
|
||||
|
||||
fmt.Printf("\nEmail: %v\nSuccess!\n", u.EmailAddress)
|
||||
}
|
||||
```
|
||||
The above token authentication example may be used, substituting a user's password for a generated token.
|
||||
|
||||
#### 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).
|
||||
|
||||
@ -146,10 +142,9 @@ import (
|
||||
|
||||
func main() {
|
||||
base := "https://my.jira.com"
|
||||
tp := jira.CookieAuthTransport{
|
||||
tp := jira.BasicAuthTransport{
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
AuthURL: fmt.Sprintf("%s/rest/auth/1/session", base),
|
||||
Password: "token",
|
||||
}
|
||||
|
||||
jiraClient, err := jira.NewClient(tp.Client(), base)
|
||||
@ -184,11 +179,9 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Call a not implemented API endpoint
|
||||
### Change an issue status
|
||||
|
||||
Not all API endpoints of the JIRA API are implemented into *go-jira*.
|
||||
But you can call them anyway:
|
||||
Lets get all public projects of [Atlassian`s JIRA instance](https://jira.atlassian.com/).
|
||||
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
|
||||
@ -200,17 +193,69 @@ import (
|
||||
|
||||
func main() {
|
||||
base := "https://my.jira.com"
|
||||
tp := jira.CookieAuthTransport{
|
||||
tp := jira.BasicAuthTransport{
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
AuthURL: fmt.Sprintf("%s/rest/auth/1/session", base),
|
||||
Password: "token",
|
||||
}
|
||||
|
||||
jiraClient, err := jira.NewClient(tp.Client(), base)
|
||||
req, _ := jiraClient.NewRequest("GET", "/rest/api/2/project", nil)
|
||||
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
|
||||
|
||||
Not all API endpoints of the Jira API are implemented into *go-jira*.
|
||||
But you can call them anyway:
|
||||
Lets get all public projects of [Atlassian`s Jira instance](https://jira.atlassian.com/).
|
||||
|
||||
```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)
|
||||
req, _ := jiraClient.NewRequest("GET", "rest/api/2/project", nil)
|
||||
|
||||
projects := new([]jira.Project)
|
||||
_, err := jiraClient.Do(req, projects)
|
||||
_, err = jiraClient.Do(req, projects)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -221,7 +266,7 @@ func main() {
|
||||
|
||||
// ...
|
||||
// BAM: Bamboo
|
||||
// BAMJ: Bamboo JIRA Plugin
|
||||
// BAMJ: Bamboo Jira Plugin
|
||||
// CLOV: Clover
|
||||
// CONF: Confluence
|
||||
// ...
|
||||
@ -230,7 +275,7 @@ func main() {
|
||||
|
||||
## 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
|
||||
|
||||
@ -238,10 +283,12 @@ The code structure of this package was inspired by [google/go-github](https://gi
|
||||
|
||||
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`.
|
||||
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
|
||||
|
||||
We ❤️ PR's
|
||||
|
||||
Contribution, in any kind of way, is highly welcome!
|
||||
It doesn't matter if you are not able to write code.
|
||||
Creating issues or holding talks and help other people to use [go-jira](https://github.com/andygrunwald/go-jira) is contribution, too!
|
||||
@ -250,15 +297,15 @@ A few examples:
|
||||
* Correct typos in the README / documentation
|
||||
* Reporting bugs
|
||||
* Implement a new feature or endpoint
|
||||
* Sharing the love if [go-jira](https://github.com/andygrunwald/go-jira) and help people to get use to it
|
||||
* Sharing the love of [go-jira](https://github.com/andygrunwald/go-jira) and help people to get use to it
|
||||
|
||||
If you are new to pull requests, checkout [Collaborating on projects using issues and pull requests / Creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
|
||||
|
||||
### Dependency management
|
||||
|
||||
`go-jira` uses `dep` for dependency management. After cloning the repo, it's easy to make sure you have the correct dependencies by running `dep ensure`.
|
||||
`go-jira` uses `go modules` for dependency management. After cloning the repo, it's easy to make sure you have the correct dependencies by running `go mod tidy`.
|
||||
|
||||
For adding new dependencies, updating dependencies, and other operations, the [Daily Dep](https://golang.github.io/dep/docs/daily-dep.html) is a good place to start.
|
||||
For adding new dependencies, updating dependencies, and other operations, the [Daily workflow](https://github.com/golang/go/wiki/Modules#daily-workflow) is a good place to start.
|
||||
|
||||
### Sandbox environment for testing
|
||||
|
||||
@ -266,6 +313,22 @@ Jira offers sandbox test environments at http://go.atlassian.com/cloud-dev.
|
||||
|
||||
You can read more about them at https://developer.atlassian.com/blog/2016/04/cloud-ecosystem-dev-env/.
|
||||
|
||||
## Releasing
|
||||
|
||||
Install [standard-version](https://github.com/conventional-changelog/standard-version)
|
||||
```bash
|
||||
npm i -g standard-version
|
||||
```
|
||||
|
||||
```bash
|
||||
standard-version
|
||||
git push --tags
|
||||
```
|
||||
|
||||
Manually copy/paste text from changelog (for this new version) into the release on Github.com. E.g.
|
||||
|
||||
[https://github.com/andygrunwald/go-jira/releases/edit/v1.11.0](https://github.com/andygrunwald/go-jira/releases/edit/v1.11.0)
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the terms of the [MIT license](http://en.wikipedia.org/wiki/MIT_License).
|
||||
|
83
vendor/github.com/andygrunwald/go-jira/authentication.go
generated
vendored
83
vendor/github.com/andygrunwald/go-jira/authentication.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -14,9 +15,9 @@ const (
|
||||
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 {
|
||||
client *Client
|
||||
|
||||
@ -30,7 +31,7 @@ type AuthenticationService struct {
|
||||
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 {
|
||||
Self string `json:"self,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
@ -47,16 +48,16 @@ type Session struct {
|
||||
Cookies []*http.Cookie
|
||||
}
|
||||
|
||||
// AcquireSessionCookie 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.
|
||||
// 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.
|
||||
// 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.
|
||||
// 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
|
||||
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"
|
||||
body := struct {
|
||||
Username string `json:"username"`
|
||||
@ -66,7 +67,7 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
||||
password,
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, body)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -79,10 +80,10 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
@ -91,7 +92,14 @@ func (s *AuthenticationService) AcquireSessionCookie(username, password string)
|
||||
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
|
||||
func (s *AuthenticationService) SetBasicAuth(username, password string) {
|
||||
@ -100,7 +108,7 @@ func (s *AuthenticationService) SetBasicAuth(username, password string) {
|
||||
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 {
|
||||
if s != nil {
|
||||
if s.authType == authTypeSession {
|
||||
@ -113,29 +121,29 @@ func (s *AuthenticationService) Authenticated() bool {
|
||||
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
|
||||
// client anymore
|
||||
func (s *AuthenticationService) Logout() error {
|
||||
func (s *AuthenticationService) LogoutWithContext(ctx context.Context) error {
|
||||
if s.authType != authTypeSession || s.client.session == nil {
|
||||
return fmt.Errorf("no user is authenticated")
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
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 {
|
||||
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
|
||||
@ -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
|
||||
func (s *AuthenticationService) GetCurrentUser() (*Session, error) {
|
||||
// Deprecated: Use CookieAuthTransport to create base client. Logging out is as simple as not using the
|
||||
// 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 {
|
||||
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 {
|
||||
return nil, fmt.Errorf("No user is authenticated yet")
|
||||
return nil, fmt.Errorf("no user is authenticated yet")
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
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 {
|
||||
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()
|
||||
ret := new(Session)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GetCurrentUser wraps GetCurrentUserWithContext using the background context.
|
||||
func (s *AuthenticationService) GetCurrentUser() (*Session, error) {
|
||||
return s.GetCurrentUserWithContext(context.Background())
|
||||
}
|
||||
|
207
vendor/github.com/andygrunwald/go-jira/board.go
generated
vendored
207
vendor/github.com/andygrunwald/go-jira/board.go
generated
vendored
@ -1,13 +1,15 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"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 {
|
||||
client *Client
|
||||
}
|
||||
@ -21,7 +23,7 @@ type BoardsList struct {
|
||||
Values []Board `json:"values" structs:"values"`
|
||||
}
|
||||
|
||||
// Board represents a JIRA agile board
|
||||
// Board represents a Jira agile board
|
||||
type Board struct {
|
||||
ID int `json:"id,omitempty" structs:"id,omitempty"`
|
||||
Self string `json:"self,omitempty" structs:"self,omitempty"`
|
||||
@ -34,7 +36,7 @@ type Board struct {
|
||||
type BoardListOptions struct {
|
||||
// BoardType filters results to boards of the specified type.
|
||||
// Valid values: scrum, kanban.
|
||||
BoardType string `url:"boardType,omitempty"`
|
||||
BoardType string `url:"type,omitempty"`
|
||||
// Name filters results to boards that match or partially match the specified name.
|
||||
Name string `url:"name,omitempty"`
|
||||
// ProjectKeyOrID filters results to boards that are relevant to a project.
|
||||
@ -44,12 +46,24 @@ type BoardListOptions struct {
|
||||
SearchOptions
|
||||
}
|
||||
|
||||
// Wrapper struct for search result
|
||||
type sprintsResult struct {
|
||||
Sprints []Sprint `json:"values" structs:"values"`
|
||||
// GetAllSprintsOptions specifies the optional parameters to the BoardService.GetList
|
||||
type GetAllSprintsOptions struct {
|
||||
// State filters results to sprints in the specified states, comma-separate list
|
||||
State string `url:"state,omitempty"`
|
||||
|
||||
SearchOptions
|
||||
}
|
||||
|
||||
// Sprint represents a sprint on JIRA agile board
|
||||
// SprintsList reflects a list of agile sprints
|
||||
type SprintsList struct {
|
||||
MaxResults int `json:"maxResults" structs:"maxResults"`
|
||||
StartAt int `json:"startAt" structs:"startAt"`
|
||||
Total int `json:"total" structs:"total"`
|
||||
IsLast bool `json:"isLast" structs:"isLast"`
|
||||
Values []Sprint `json:"values" structs:"values"`
|
||||
}
|
||||
|
||||
// Sprint represents a sprint on Jira agile board
|
||||
type Sprint struct {
|
||||
ID int `json:"id" structs:"id"`
|
||||
Name string `json:"name" structs:"name"`
|
||||
@ -61,13 +75,66 @@ type Sprint struct {
|
||||
State string `json:"state" structs:"state"`
|
||||
}
|
||||
|
||||
// GetAllBoards will returns all boards. This only includes boards that the user has permission to view.
|
||||
// BoardConfiguration represents a boardConfiguration of a jira board
|
||||
type BoardConfiguration struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Self string `json:"self"`
|
||||
Location BoardConfigurationLocation `json:"location"`
|
||||
Filter BoardConfigurationFilter `json:"filter"`
|
||||
SubQuery BoardConfigurationSubQuery `json:"subQuery"`
|
||||
ColumnConfig BoardConfigurationColumnConfig `json:"columnConfig"`
|
||||
}
|
||||
|
||||
// BoardConfigurationFilter reference to the filter used by the given board.
|
||||
type BoardConfigurationFilter struct {
|
||||
ID string `json:"id"`
|
||||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
// BoardConfigurationSubQuery (Kanban only) - JQL subquery used by the given board.
|
||||
type BoardConfigurationSubQuery struct {
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// BoardConfigurationLocation reference to the container that the board is located in
|
||||
type BoardConfigurationLocation struct {
|
||||
Type string `json:"type"`
|
||||
Key string `json:"key"`
|
||||
ID string `json:"id"`
|
||||
Self string `json:"self"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// BoardConfigurationColumnConfig lists the columns for a given board in the order defined in the column configuration
|
||||
// with constrainttype (none, issueCount, issueCountExclSubs)
|
||||
type BoardConfigurationColumnConfig struct {
|
||||
Columns []BoardConfigurationColumn `json:"columns"`
|
||||
ConstraintType string `json:"constraintType"`
|
||||
}
|
||||
|
||||
// BoardConfigurationColumn lists the name of the board with the statuses that maps to a particular column
|
||||
type BoardConfigurationColumn struct {
|
||||
Name string `json:"name"`
|
||||
Status []BoardConfigurationColumnStatus `json:"statuses"`
|
||||
}
|
||||
|
||||
// BoardConfigurationColumnStatus represents a status in the column configuration
|
||||
type BoardConfigurationColumnStatus struct {
|
||||
ID string `json:"id"`
|
||||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards
|
||||
func (s *BoardService) GetAllBoardsWithContext(ctx context.Context, opt *BoardListOptions) (*BoardsList, *Response, error) {
|
||||
apiEndpoint := "rest/agile/1.0/board"
|
||||
url, err := addOptions(apiEndpoint, opt)
|
||||
req, err := s.client.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -82,13 +149,18 @@ func (s *BoardService) GetAllBoards(opt *BoardListOptions) (*BoardsList, *Respon
|
||||
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.
|
||||
//
|
||||
// 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) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard
|
||||
func (s *BoardService) GetBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -103,17 +175,22 @@ func (s *BoardService) GetBoard(boardID int) (*Board, *Response, error) {
|
||||
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.
|
||||
// type - Valid values: scrum, kanban
|
||||
// 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
|
||||
// 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
|
||||
func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard
|
||||
func (s *BoardService) CreateBoardWithContext(ctx context.Context, board *Board) (*Board, *Response, error) {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -128,12 +205,17 @@ func (s *BoardService) CreateBoard(board *Board) (*Board, *Response, error) {
|
||||
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
|
||||
func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard
|
||||
func (s *BoardService) DeleteBoardWithContext(ctx context.Context, boardID int) (*Board, *Response, error) {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -145,22 +227,85 @@ func (s *BoardService) DeleteBoard(boardID int) (*Board, *Response, error) {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
// GetAllSprints will returns 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.
|
||||
//
|
||||
// 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) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%s/sprint", boardID)
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
|
||||
func (s *BoardService) GetAllSprintsWithContext(ctx context.Context, boardID string) ([]Sprint, *Response, error) {
|
||||
id, err := strconv.Atoi(boardID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
result := new(sprintsResult)
|
||||
result, response, err := s.GetAllSprintsWithOptions(id, &GetAllSprintsOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return result.Values, response, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board/{boardId}/sprint
|
||||
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)
|
||||
url, err := addOptions(apiEndpoint, options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
result := new(SprintsList)
|
||||
resp, err := s.client.Do(req, result)
|
||||
if err != nil {
|
||||
err = NewJiraError(resp, err)
|
||||
}
|
||||
|
||||
return result.Sprints, resp, err
|
||||
return result, resp, err
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *BoardService) GetBoardConfigurationWithContext(ctx context.Context, boardID int) (*BoardConfiguration, *Response, error) {
|
||||
apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/configuration", boardID)
|
||||
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
result := new(BoardConfiguration)
|
||||
resp, err := s.client.Do(req, result)
|
||||
if err != nil {
|
||||
err = NewJiraError(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)
|
||||
}
|
||||
|
44
vendor/github.com/andygrunwald/go-jira/component.go
generated
vendored
Normal file
44
vendor/github.com/andygrunwald/go-jira/component.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package jira
|
||||
|
||||
import "context"
|
||||
|
||||
// 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 {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// CreateComponentOptions are passed to the ComponentService.Create function to create a new Jira component
|
||||
type CreateComponentOptions struct {
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
Description string `json:"description,omitempty" structs:"description,omitempty"`
|
||||
Lead *User `json:"lead,omitempty" structs:"lead,omitempty"`
|
||||
LeadUserName string `json:"leadUserName,omitempty" structs:"leadUserName,omitempty"`
|
||||
AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"`
|
||||
Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"`
|
||||
Project string `json:"project,omitempty" structs:"project,omitempty"`
|
||||
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"`
|
||||
}
|
||||
|
||||
// CreateWithContext creates a new Jira component based on the given options.
|
||||
func (s *ComponentService) CreateWithContext(ctx context.Context, options *CreateComponentOptions) (*ProjectComponent, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/component"
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
component := new(ProjectComponent)
|
||||
resp, err := s.client.Do(req, component)
|
||||
|
||||
if err != nil {
|
||||
return nil, resp, NewJiraError(resp, err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
20
vendor/github.com/andygrunwald/go-jira/error.go
generated
vendored
20
vendor/github.com/andygrunwald/go-jira/error.go
generated
vendored
@ -5,11 +5,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Error message from JIRA
|
||||
// Error message from Jira
|
||||
// See https://docs.atlassian.com/jira/REST/cloud/#error-responses
|
||||
type Error struct {
|
||||
HTTPError error
|
||||
@ -28,12 +29,19 @@ func NewJiraError(resp *Response, httpError error) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, httpError.Error())
|
||||
}
|
||||
|
||||
jerr := Error{HTTPError: httpError}
|
||||
err = json.Unmarshal(body, &jerr)
|
||||
if err != nil {
|
||||
httpError = errors.Wrap(errors.New("Could not parse JSON"), httpError.Error())
|
||||
return errors.Wrap(err, httpError.Error())
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if strings.HasPrefix(contentType, "application/json") {
|
||||
err = json.Unmarshal(body, &jerr)
|
||||
if err != nil {
|
||||
httpError = errors.Wrap(errors.New("could not parse JSON"), httpError.Error())
|
||||
return errors.Wrap(err, httpError.Error())
|
||||
}
|
||||
} else {
|
||||
if httpError == nil {
|
||||
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 &jerr
|
||||
|
55
vendor/github.com/andygrunwald/go-jira/field.go
generated
vendored
Normal file
55
vendor/github.com/andygrunwald/go-jira/field.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package jira
|
||||
|
||||
import "context"
|
||||
|
||||
// FieldService handles fields for the Jira instance / API.
|
||||
//
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-Field
|
||||
type FieldService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Field represents a field of a Jira issue.
|
||||
type Field struct {
|
||||
ID string `json:"id,omitempty" structs:"id,omitempty"`
|
||||
Key string `json:"key,omitempty" structs:"key,omitempty"`
|
||||
Name string `json:"name,omitempty" structs:"name,omitempty"`
|
||||
Custom bool `json:"custom,omitempty" structs:"custom,omitempty"`
|
||||
Navigable bool `json:"navigable,omitempty" structs:"navigable,omitempty"`
|
||||
Searchable bool `json:"searchable,omitempty" structs:"searchable,omitempty"`
|
||||
ClauseNames []string `json:"clauseNames,omitempty" structs:"clauseNames,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 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"`
|
||||
CustomID int64 `json:"customId,omitempty" structs:"customId,omitempty"`
|
||||
}
|
||||
|
||||
// GetListWithContext gets all fields from Jira
|
||||
//
|
||||
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-field-get
|
||||
func (s *FieldService) GetListWithContext(ctx context.Context) ([]Field, *Response, error) {
|
||||
apiEndpoint := "rest/api/2/field"
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fieldList := []Field{}
|
||||
resp, err := s.client.Do(req, &fieldList)
|
||||
if err != nil {
|
||||
return nil, resp, NewJiraError(resp, err)
|
||||
}
|
||||
return fieldList, resp, nil
|
||||
}
|
||||
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
func (s *FieldService) GetList() ([]Field, *Response, error) {
|
||||
return s.GetListWithContext(context.Background())
|
||||
}
|
251
vendor/github.com/andygrunwald/go-jira/filter.go
generated
vendored
Normal file
251
vendor/github.com/andygrunwald/go-jira/filter.go
generated
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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
|
||||
type FilterService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Filter represents a Filter in Jira
|
||||
type Filter struct {
|
||||
Self string `json:"self"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Owner User `json:"owner"`
|
||||
Jql string `json:"jql"`
|
||||
ViewURL string `json:"viewUrl"`
|
||||
SearchURL string `json:"searchUrl"`
|
||||
Favourite bool `json:"favourite"`
|
||||
FavouritedCount int `json:"favouritedCount"`
|
||||
SharePermissions []interface{} `json:"sharePermissions"`
|
||||
Subscriptions struct {
|
||||
Size int `json:"size"`
|
||||
Items []interface{} `json:"items"`
|
||||
MaxResults int `json:"max-results"`
|
||||
StartIndex int `json:"start-index"`
|
||||
EndIndex int `json:"end-index"`
|
||||
} `json:"subscriptions"`
|
||||
}
|
||||
|
||||
// GetMyFiltersQueryOptions specifies the optional parameters for the Get My Filters method
|
||||
type GetMyFiltersQueryOptions struct {
|
||||
IncludeFavourites bool `url:"includeFavourites,omitempty"`
|
||||
Expand string `url:"expand,omitempty"`
|
||||
}
|
||||
|
||||
// FiltersList reflects a list of filters
|
||||
type FiltersList struct {
|
||||
MaxResults int `json:"maxResults" structs:"maxResults"`
|
||||
StartAt int `json:"startAt" structs:"startAt"`
|
||||
Total int `json:"total" structs:"total"`
|
||||
IsLast bool `json:"isLast" structs:"isLast"`
|
||||
Values []FiltersListItem `json:"values" structs:"values"`
|
||||
}
|
||||
|
||||
// FiltersListItem represents a Filter of FiltersList in Jira
|
||||
type FiltersListItem struct {
|
||||
Self string `json:"self"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Owner User `json:"owner"`
|
||||
Jql string `json:"jql"`
|
||||
ViewURL string `json:"viewUrl"`
|
||||
SearchURL string `json:"searchUrl"`
|
||||
Favourite bool `json:"favourite"`
|
||||
FavouritedCount int `json:"favouritedCount"`
|
||||
SharePermissions []interface{} `json:"sharePermissions"`
|
||||
Subscriptions []struct {
|
||||
ID int `json:"id"`
|
||||
User User `json:"user"`
|
||||
} `json:"subscriptions"`
|
||||
}
|
||||
|
||||
// FilterSearchOptions specifies the optional parameters for the Search method
|
||||
// https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-filter-search-get
|
||||
type FilterSearchOptions struct {
|
||||
// String used to perform a case-insensitive partial match with name.
|
||||
FilterName string `url:"filterName,omitempty"`
|
||||
|
||||
// User account ID used to return filters with the matching owner.accountId. This parameter cannot be used with owner.
|
||||
AccountID string `url:"accountId,omitempty"`
|
||||
|
||||
// Group name used to returns filters that are shared with a group that matches sharePermissions.group.groupname.
|
||||
GroupName string `url:"groupname,omitempty"`
|
||||
|
||||
// Project ID used to returns filters that are shared with a project that matches sharePermissions.project.id.
|
||||
// Format: int64
|
||||
ProjectID int64 `url:"projectId,omitempty"`
|
||||
|
||||
// Orders the results using one of these filter properties.
|
||||
// - `description` Orders by filter `description`. Note that this ordering works independently of whether the expand to display the description field is in use.
|
||||
// - `favourite_count` Orders by `favouritedCount`.
|
||||
// - `is_favourite` Orders by `favourite`.
|
||||
// - `id` Orders by filter `id`.
|
||||
// - `name` Orders by filter `name`.
|
||||
// - `owner` Orders by `owner.accountId`.
|
||||
//
|
||||
// Default: `name`
|
||||
//
|
||||
// Valid values: id, name, description, owner, favorite_count, is_favorite, -id, -name, -description, -owner, -favorite_count, -is_favorite
|
||||
OrderBy string `url:"orderBy,omitempty"`
|
||||
|
||||
// The index of the first item to return in a page of results (page offset).
|
||||
// Default: 0, Format: int64
|
||||
StartAt int64 `url:"startAt,omitempty"`
|
||||
|
||||
// The maximum number of items to return per page. The maximum is 100.
|
||||
// Default: 50, Format: int32
|
||||
MaxResults int32 `url:"maxResults,omitempty"`
|
||||
|
||||
// Use expand to include additional information about filter in the response. This parameter accepts multiple values separated by a comma:
|
||||
// - description Returns the description of the filter.
|
||||
// - favourite Returns an indicator of whether the user has set the filter as a favorite.
|
||||
// - favouritedCount Returns a count of how many users have set this filter as a favorite.
|
||||
// - jql Returns the JQL query that the filter uses.
|
||||
// - owner Returns the owner of the filter.
|
||||
// - searchUrl Returns a URL to perform the filter's JQL query.
|
||||
// - sharePermissions Returns the share permissions defined for the filter.
|
||||
// - subscriptions Returns the users that are subscribed to the filter.
|
||||
// - viewUrl Returns a URL to view the filter.
|
||||
Expand string `url:"expand,omitempty"`
|
||||
}
|
||||
|
||||
// GetListWithContext retrieves all filters from Jira
|
||||
func (fs *FilterService) GetListWithContext(ctx context.Context) ([]*Filter, *Response, error) {
|
||||
|
||||
options := &GetQueryOptions{}
|
||||
apiEndpoint := "rest/api/2/filter"
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
q, err := query.Values(options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
filters := []*Filter{}
|
||||
resp, err := fs.client.Do(req, &filters)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
return filters, resp, err
|
||||
}
|
||||
|
||||
// GetList wraps GetListWithContext using the background context.
|
||||
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"
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
filters := []*Filter{}
|
||||
resp, err := fs.client.Do(req, &filters)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
return filters, resp, err
|
||||
}
|
||||
|
||||
// GetFavouriteList wraps GetFavouriteListWithContext using the background context.
|
||||
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)
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
filter := new(Filter)
|
||||
resp, err := fs.client.Do(req, filter)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
return filter, resp, err
|
||||
}
|
||||
|
||||
// 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
|
||||
func (fs *FilterService) GetMyFiltersWithContext(ctx context.Context, opts *GetMyFiltersQueryOptions) ([]*Filter, *Response, error) {
|
||||
apiEndpoint := "rest/api/3/filter/my"
|
||||
url, err := addOptions(apiEndpoint, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
filters := []*Filter{}
|
||||
resp, err := fs.client.Do(req, &filters)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
return filters, resp, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
func (fs *FilterService) SearchWithContext(ctx context.Context, opt *FilterSearchOptions) (*FiltersList, *Response, error) {
|
||||
apiEndpoint := "rest/api/3/filter/search"
|
||||
url, err := addOptions(apiEndpoint, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := fs.client.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
filters := new(FiltersList)
|
||||
resp, err := fs.client.Do(req, filters)
|
||||
if err != nil {
|
||||
jerr := NewJiraError(resp, err)
|
||||
return nil, resp, jerr
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
62
vendor/github.com/andygrunwald/go-jira/group.go
generated
vendored
62
vendor/github.com/andygrunwald/go-jira/group.go
generated
vendored
@ -1,13 +1,14 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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 {
|
||||
client *Client
|
||||
}
|
||||
@ -21,7 +22,7 @@ type groupMembersResult struct {
|
||||
Members []GroupMember `json:"values"`
|
||||
}
|
||||
|
||||
// Group represents a JIRA group
|
||||
// Group represents a Jira group
|
||||
type Group struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
@ -43,28 +44,31 @@ type GroupMember struct {
|
||||
Self string `json:"self,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
AccountID string `json:"accountId,omitempty"`
|
||||
EmailAddress string `json:"emailAddress,omitempty"`
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
Active bool `json:"active,omitempty"`
|
||||
TimeZone string `json:"timeZone,omitempty"`
|
||||
AccountType string `json:"accountType,omitempty"`
|
||||
}
|
||||
|
||||
// GroupSearchOptions specifies the optional parameters for the Get Group methods
|
||||
type GroupSearchOptions struct {
|
||||
StartAt int
|
||||
MaxResults int
|
||||
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.
|
||||
// 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
|
||||
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))
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -78,12 +82,17 @@ func (s *GroupService) Get(name string) ([]GroupMember, *Response, error) {
|
||||
return group.Members, resp, nil
|
||||
}
|
||||
|
||||
// Get 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.
|
||||
// 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
|
||||
func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/server/#api/2/group-getUsersFromGroup
|
||||
func (s *GroupService) GetWithOptionsWithContext(ctx context.Context, name string, options *GroupSearchOptions) ([]GroupMember, *Response, error) {
|
||||
var apiEndpoint string
|
||||
if options == nil {
|
||||
apiEndpoint = fmt.Sprintf("/rest/api/2/group/member?groupname=%s", url.QueryEscape(name))
|
||||
@ -96,7 +105,7 @@ func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions)
|
||||
options.IncludeInactiveUsers,
|
||||
)
|
||||
}
|
||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "GET", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -109,16 +118,21 @@ func (s *GroupService) GetWithOptions(name string, options *GroupSearchOptions)
|
||||
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
|
||||
func (s *GroupService) Add(groupname string, username string) (*Group, *Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-addUserToGroup
|
||||
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)
|
||||
var user struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
user.Name = username
|
||||
req, err := s.client.NewRequest("POST", apiEndpoint, &user)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "POST", apiEndpoint, &user)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -133,12 +147,17 @@ func (s *GroupService) Add(groupname string, username string) (*Group, *Response
|
||||
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
|
||||
func (s *GroupService) Remove(groupname string, username string) (*Response, error) {
|
||||
// Jira API docs: https://docs.atlassian.com/jira/REST/cloud/#api/2/group-removeUserFromGroup
|
||||
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)
|
||||
req, err := s.client.NewRequest("DELETE", apiEndpoint, nil)
|
||||
req, err := s.client.NewRequestWithContext(ctx, "DELETE", apiEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -151,3 +170,8 @@ func (s *GroupService) Remove(groupname string, username string) (*Response, err
|
||||
|
||||
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)
|
||||
}
|
||||
|
915
vendor/github.com/andygrunwald/go-jira/issue.go
generated
vendored
915
vendor/github.com/andygrunwald/go-jira/issue.go
generated
vendored
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user