diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c13ca3f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +* +!release/ diff --git a/.drone.jsonnet b/.drone.jsonnet new file mode 100644 index 0000000..0dd1fd2 --- /dev/null +++ b/.drone.jsonnet @@ -0,0 +1,192 @@ +local PipelineTesting = { + kind: "pipeline", + name: "testing", + platform: { + os: "linux", + arch: "amd64", + }, + services: [ + { + name: "pypiserver", + image: "pypiserver/pypiserver", + pull: "always", + entrypoint: [ + "pypi-server", + "-P", + ".", + "-a", + ".", + "-p", + "8080", + "/data/packages" + ], + }, + ], + steps: [ + { + name: "vet", + image: "golang:1.11", + pull: "always", + environment: { + GO111MODULE: "on", + }, + commands: [ + "go vet ./...", + ], + }, + { + name: "test", + image: "golang:1.11-alpine", + pull: "always", + environment: { + GO111MODULE: "on", + PLUGIN_REPOSITORY: "http://pypiserver:8080/", + PLUGIN_DISTRIBUTIONS: "sdist", + PLUGIN_USERNAME: "demo", + PLUGIN_PASSWORD: "demo", + }, + commands: [ + "apk --no-cache add -U python3 git", + "pip3 install --no-cache-dir --upgrade pip setuptools wheel six twine", + "go test -cover ./...", + ], + }, + ], + trigger: { + branch: [ "master" ], + }, +}; + +local PipelineBuild(os="linux", arch="amd64") = { + kind: "pipeline", + name: os + "-" + arch, + platform: { + os: os, + arch: arch, + }, + steps: [ + { + name: "build-push", + image: "golang:1.11", + pull: "always", + environment: { + CGO_ENABLED: "0", + GO111MODULE: "on", + }, + commands: [ + "go build -v -ldflags \"-X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/" + os + "/" + arch + "/drone-pypi", + ], + when: { + event: [ "push", "pull_request" ], + }, + }, + { + name: "build-tag", + image: "golang:1.11", + pull: "always", + environment: { + CGO_ENABLED: "0", + GO111MODULE: "on", + }, + commands: [ + "go build -v -ldflags \"-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/" + os + "/" + arch + "/drone-pypi", + ], + when: { + event: [ "tag" ], + }, + }, + { + name: "executable", + image: "golang:1.11", + pull: "always", + commands: [ + "./release/" + os + "/" + arch + "/drone-pypi --help", + ], + }, + { + name: "dryrun", + image: "plugins/docker:" + os + "-" + arch, + pull: "always", + settings: { + dry_run: true, + tags: os + "-" + arch, + dockerfile: "docker/Dockerfile." + os + "." + arch, + repo: "plugins/pypi", + username: { "from_secret": "docker_username" }, + password: { "from_secret": "docker_password" }, + }, + when: { + event: [ "pull_request" ], + }, + }, + { + name: "publish", + image: "plugins/docker:" + os + "-" + arch, + pull: "always", + settings: { + auto_tag: true, + auto_tag_suffix: os + "-" + arch, + dockerfile: "docker/Dockerfile." + os + "." + arch, + repo: "plugins/pypi", + username: { "from_secret": "docker_username" }, + password: { "from_secret": "docker_password" }, + }, + when: { + event: [ "push", "tag" ], + }, + }, + ], + depends_on: [ + "testing", + ], + trigger: { + branch: [ "master" ], + }, +}; + +local PipelineNotifications = { + kind: "pipeline", + name: "notifications", + platform: { + os: "linux", + arch: "amd64", + }, + steps: [ + { + name: "manifest", + image: "plugins/manifest:1", + pull: "always", + settings: { + username: { "from_secret": "docker_username" }, + password: { "from_secret": "docker_password" }, + spec: "docker/manifest.tmpl", + ignore_missing: true, + }, + }, + { + name: "microbadger", + image: "plugins/webhook:1", + pull: "always", + settings: { + url: { "from_secret": "microbadger_url" }, + }, + }, + ], + depends_on: [ + "linux-amd64", + "linux-arm64", + "linux-arm", + ], + trigger: { + branch: [ "master" ], + event: [ "push", "tag" ], + }, +}; + +[ + PipelineTesting, + PipelineBuild("linux", "amd64"), + PipelineBuild("linux", "arm64"), + PipelineBuild("linux", "arm"), + PipelineNotifications, +] diff --git a/.drone.sec b/.drone.sec deleted file mode 100644 index 518f089..0000000 --- a/.drone.sec +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.qT1HwNOvyKOcybO375AFxDGhmCaT9y3hIX3lOo3whybdcU-sDbVPyeN4DGNFQ9wXETOinGRm76LogzL6C1buMz5AccsQZMheYC-kfvBSnI2l63UwEu7zCkIrp1J1Frd0-vuoaOOih4Qr64u_N6lDmMYbayKvAB1wLp73FIzQexJf1bc8GR_cKyzA4vK9MrIZynD8jpnJx3bvq6l-mCxZl_OtJaZMzaNEe5w-NiWmoxfmhWMr06_hdoPf7O8cp_QFCSbY0CfDLIUrj6AwD6CeL0UbGIED9Ol-b5CCvFJ-u3rbYIp5GaIWCIMWz8v9ERVr1NZDQ8nb8hu8qNt5Uq5qEw.b8x12T3ociB4416-.PlWIfdFtneRvnYGtEyaDENhR-Zq83SQSnQjRMy2llv2y7efeBhLHsq1O1sT_VaA_MJzh7eQTPPIMZgNiRgO_OINjZLDmNlr-f4fzRzk7NAntlbH46J1p5IJPnXv1lQu0YJPfbLlx9qiy6DQHKkhTOInVwOkTwahTT_2eE5Rs2vp4zgV9oEqdCkc0knIqr-eDMl3E1MbQ4qbSDPCLL5V4WuiYCXOak8PmOKyiFuYWpAdaovyOz0gwjUAFxNA-ubU7m1Xpi_E-GNkPxATtCIMYgNr2f3pQGdsrSZnRCtBvsuXJqMyZ8dU9rxr--fY65dS26y2y4KC_6A03Uh-U4W2bYvrMuWeDifgyOoh7dCM2RWGQtuZJ3XfW2j86enVzk5m5FBEq7w.bogB-GosDieE9QNTSxqUbw \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 733af34..30e006a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,38 +1,329 @@ -build: - image: golang:1.5 - environment: - - GO15VENDOREXPERIMENT=1 - - GOOS=linux - - GOARCH=amd64 - - CGO_ENABLED=0 - - DRONE_PYPI_PATH=testdata - - DRONE_PYPI_REPOSITORY=http://localhost:8000 - - DRONE_PYPI_DISTRIBUTIONS=sdist +--- +kind: pipeline +name: testing + +platform: + os: linux + arch: amd64 + +steps: +- name: vet + pull: always + image: golang:1.11 commands: - - apt-get update && apt-get install -y python-setuptools - - go get - - go build - - go test -cover + - go vet ./... + environment: + GO111MODULE: on -compose: - simplepypi: - image: msteinert/simplepypi - pull: true +- name: test + pull: always + image: golang:1.11-alpine + commands: + - apk --no-cache add -U python3 git + - pip3 install --no-cache-dir --upgrade pip setuptools wheel six twine + - go test -cover ./... + environment: + GO111MODULE: on + PLUGIN_DISTRIBUTIONS: sdist + PLUGIN_PASSWORD: demo + PLUGIN_REPOSITORY: http://pypiserver:8080/ + PLUGIN_USERNAME: demo -publish: - docker: - username: drone - password: $$DOCKER_PASS - email: $$DOCKER_EMAIL - repo: plugins/drone-pypi - when: - branch: master +services: +- name: pypiserver + pull: always + image: pypiserver/pypiserver + entrypoint: + - pypi-server + - -P + - . + - -a + - . + - -p + - 8080 + - /data/packages -plugin: - name: Pypi - desc: Publish a package to the Python package index. - type: publish - image: plugins/drone-pypi - labels: - - publish - - python +trigger: + branch: + - master + +--- +kind: pipeline +name: linux-amd64 + +platform: + os: linux + arch: amd64 + +steps: +- name: build-push + pull: always + image: golang:1.11 + commands: + - "go build -v -ldflags \"-X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/amd64/drone-pypi" + environment: + CGO_ENABLED: 0 + GO111MODULE: on + when: + event: + - push + - pull_request + +- name: build-tag + pull: always + image: golang:1.11 + commands: + - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/amd64/drone-pypi" + environment: + CGO_ENABLED: 0 + GO111MODULE: on + when: + event: + - tag + +- name: executable + pull: always + image: golang:1.11 + commands: + - ./release/linux/amd64/drone-pypi --help + +- name: dryrun + pull: always + image: plugins/docker:linux-amd64 + settings: + dockerfile: docker/Dockerfile.linux.amd64 + dry_run: true + password: + from_secret: docker_password + repo: plugins/pypi + tags: linux-amd64 + username: + from_secret: docker_username + when: + event: + - pull_request + +- name: publish + pull: always + image: plugins/docker:linux-amd64 + settings: + auto_tag: true + auto_tag_suffix: linux-amd64 + dockerfile: docker/Dockerfile.linux.amd64 + password: + from_secret: docker_password + repo: plugins/pypi + username: + from_secret: docker_username + when: + event: + - push + - tag + +trigger: + branch: + - master + +depends_on: +- testing + +--- +kind: pipeline +name: linux-arm64 + +platform: + os: linux + arch: arm64 + +steps: +- name: build-push + pull: always + image: golang:1.11 + commands: + - "go build -v -ldflags \"-X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/arm64/drone-pypi" + environment: + CGO_ENABLED: 0 + GO111MODULE: on + when: + event: + - push + - pull_request + +- name: build-tag + pull: always + image: golang:1.11 + commands: + - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/arm64/drone-pypi" + environment: + CGO_ENABLED: 0 + GO111MODULE: on + when: + event: + - tag + +- name: executable + pull: always + image: golang:1.11 + commands: + - ./release/linux/arm64/drone-pypi --help + +- name: dryrun + pull: always + image: plugins/docker:linux-arm64 + settings: + dockerfile: docker/Dockerfile.linux.arm64 + dry_run: true + password: + from_secret: docker_password + repo: plugins/pypi + tags: linux-arm64 + username: + from_secret: docker_username + when: + event: + - pull_request + +- name: publish + pull: always + image: plugins/docker:linux-arm64 + settings: + auto_tag: true + auto_tag_suffix: linux-arm64 + dockerfile: docker/Dockerfile.linux.arm64 + password: + from_secret: docker_password + repo: plugins/pypi + username: + from_secret: docker_username + when: + event: + - push + - tag + +trigger: + branch: + - master + +depends_on: +- testing + +--- +kind: pipeline +name: linux-arm + +platform: + os: linux + arch: arm + +steps: +- name: build-push + pull: always + image: golang:1.11 + commands: + - "go build -v -ldflags \"-X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/arm/drone-pypi" + environment: + CGO_ENABLED: 0 + GO111MODULE: on + when: + event: + - push + - pull_request + +- name: build-tag + pull: always + image: golang:1.11 + commands: + - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/arm/drone-pypi" + environment: + CGO_ENABLED: 0 + GO111MODULE: on + when: + event: + - tag + +- name: executable + pull: always + image: golang:1.11 + commands: + - ./release/linux/arm/drone-pypi --help + +- name: dryrun + pull: always + image: plugins/docker:linux-arm + settings: + dockerfile: docker/Dockerfile.linux.arm + dry_run: true + password: + from_secret: docker_password + repo: plugins/pypi + tags: linux-arm + username: + from_secret: docker_username + when: + event: + - pull_request + +- name: publish + pull: always + image: plugins/docker:linux-arm + settings: + auto_tag: true + auto_tag_suffix: linux-arm + dockerfile: docker/Dockerfile.linux.arm + password: + from_secret: docker_password + repo: plugins/pypi + username: + from_secret: docker_username + when: + event: + - push + - tag + +trigger: + branch: + - master + +depends_on: +- testing + +--- +kind: pipeline +name: notifications + +platform: + os: linux + arch: amd64 + +steps: +- name: manifest + pull: always + image: plugins/manifest:1 + settings: + ignore_missing: true + password: + from_secret: docker_password + spec: docker/manifest.tmpl + username: + from_secret: docker_username + +- name: microbadger + pull: always + image: plugins/webhook:1 + settings: + url: + from_secret: microbadger_url + +trigger: + branch: + - master + event: + - push + - tag + +depends_on: +- linux-amd64 +- linux-arm64 +- linux-arm + +... diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..3f95605 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,9 @@ + diff --git a/testdata/drone_pypi/__init__.py b/.github/pull_request_template.md similarity index 100% rename from testdata/drone_pypi/__init__.py rename to .github/pull_request_template.md diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..09778c4 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,75 @@ +repository: + name: drone-pypi + description: Drone plugin to publish python packages to PyPi + homepage: http://plugins.drone.io/drone-plugins/drone-pypi + topics: drone, drone-plugin + + private: false + has_issues: true + has_wiki: false + has_downloads: false + + default_branch: master + + allow_squash_merge: true + allow_merge_commit: true + allow_rebase_merge: true + +labels: + - name: bug + color: d73a4a + description: Something isn't working + - name: duplicate + color: cfd3d7 + description: This issue or pull request already exists + - name: enhancement + color: a2eeef + description: New feature or request + - name: good first issue + color: 7057ff + description: Good for newcomers + - name: help wanted + color: 008672 + description: Extra attention is needed + - name: invalid + color: e4e669 + description: This doesn't seem right + - name: question + color: d876e3 + description: Further information is requested + - name: renovate + color: e99695 + description: Automated action from Renovate + - name: wontfix + color: ffffff + description: This will not be worked on + +teams: + - name: Admins + permission: admin + - name: Captain + permission: admin + - name: Maintainers + permission: push + +branches: + - name: master + protection: + required_pull_request_reviews: + required_approving_review_count: 1 + dismiss_stale_reviews: false + require_code_owner_reviews: false + dismissal_restrictions: + teams: + - Admins + - Captain + required_status_checks: + strict: true + contexts: + - continuous-integration/drone/pr + enforce_admins: false + restrictions: + users: [] + teams: + - Admins + - Captain diff --git a/.gitignore b/.gitignore index 6b9b092..669c9a0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,8 @@ _testmain.go *.test *.prof +release/ +vendor/ + +coverage.out drone-pypi diff --git a/DOCS.md b/DOCS.md deleted file mode 100644 index fc6547d..0000000 --- a/DOCS.md +++ /dev/null @@ -1,19 +0,0 @@ -Use the PyPI plugin to deploy a Python package to a PyPI server. - -* `repository` - The repository name (optional) -* `username` - The username to login with (optional) -* `password` - A password to login with (optional) -* `distributions` - A list of distribution types to deploy (optional) - -The following is an example configuration for your .drone.yml: - -```yaml -publish: - pypi: - repository: https://pypi.python.org/pypi - username: guido - password: secret - distributions: - - sdist - - bdist_wheel -``` diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ee94db4..0000000 --- a/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM alpine:3.2 - -RUN apk add -U \ - ca-certificates \ - py-pip \ - python \ - && rm -rf /var/cache/apk/* \ - && pip install --no-cache-dir --upgrade \ - pip \ - setuptools \ - wheel - -ADD drone-pypi /bin/ - -ENTRYPOINT ["/bin/drone-pypi"] diff --git a/LICENSE b/LICENSE index 8f71f43..8dada3e 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index d4058f4..0000000 --- a/MAINTAINERS +++ /dev/null @@ -1,35 +0,0 @@ -[people] - [people.bradrydzewski] - name = "Brad Rydzewski" - email = "brad@drone.io" - login = "bradrydzewski" - [people.Bugagazavr] - login = "Bugagazavr" - [people.donny-dont] - name = "Don Olmstead" - email = "donny-dont@gmail.com" - login = "donny-dont" - [people.gtaylor] - name = Greg Taylor - email = "" - login = "gtaylor" - [people.jackspirou] - name = "Jack Spirou" - email = "" - login = "jackspirou" - [people.msteinert] - name = "Mike Steinert" - email = "" - login = "msteinert" - [people.nlf] - name = "Nathan LaFreniere" - email = "" - login = "nlf" - [people.tboerger] - name = "Thomas Boerger" - email = "" - login = "tboerger" - -[org] - [org.core] - people = ["bradrydzewski", "Bugagazavr", "donny-dont", "gtaylor", "jackspirou", "msteinert", "nlf", "tboerger"] diff --git a/README.md b/README.md index 826202c..3d54090 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,47 @@ # drone-pypi -[![Build Status](http://beta.drone.io/api/badges/drone-plugins/drone-pypi/status.svg)](http://beta.drone.io/drone-plugins/drone-pypi) -[![](https://badge.imagelayers.io/plugins/drone-pypi:latest.svg)](https://imagelayers.io/?images=plugins/drone-pypi:latest 'Get your own badge on imagelayers.io') +[![Build Status](http://cloud.drone.io/api/badges/drone-plugins/drone-pypi/status.svg)](http://cloud.drone.io/drone-plugins/drone-pypi) +[![Gitter chat](https://badges.gitter.im/drone/drone.png)](https://gitter.im/drone/drone) +[![Join the discussion at https://discourse.drone.io](https://img.shields.io/badge/discourse-forum-orange.svg)](https://discourse.drone.io) +[![Drone questions at https://stackoverflow.com](https://img.shields.io/badge/drone-stackoverflow-orange.svg)](https://stackoverflow.com/questions/tagged/drone.io) +[![](https://images.microbadger.com/badges/image/plugins/pypi.svg)](https://microbadger.com/images/plugins/pypi "Get your own image badge on microbadger.com") +[![Go Doc](https://godoc.org/github.com/drone-plugins/drone-pypi?status.svg)](http://godoc.org/github.com/drone-plugins/drone-pypi) +[![Go Report](https://goreportcard.com/badge/github.com/drone-plugins/drone-pypi)](https://goreportcard.com/report/github.com/drone-plugins/drone-pypi) -Drone plugin for publishing to the Python package index +Drone Plugin for PyPi publishing with [twine](https://pypi.org/project/twine/). For the usage information and a listing of the available options please take a look at [the docs](http://plugins.drone.io/drone-plugins/drone-pypi/). -## Usage +## Build -Upload a source distribution to PyPI +Build the binary with the following commands: -```sh -./drone-pypi < - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/main.go b/main.go index 22cef07..6108dec 100644 --- a/main.go +++ b/main.go @@ -1,122 +1,74 @@ package main import ( - "bufio" "fmt" - "io" - "log" "os" - "os/exec" - "path" - "strings" - "github.com/drone/drone-go/drone" - "github.com/drone/drone-go/plugin" + "github.com/urfave/cli" + _ "github.com/joho/godotenv/autoload" ) -// Params desribes how to upload a Python module to PyPI. -type Params struct { - Distributions []string `json:"distributions"` - Password *string `json:"password,omitempty"` - Repository *string `json:"repository,omitempty"` - Username *string `json:"username,omitempty"` -} +var build = "0" // build number set at compile-time func main() { - w := drone.Workspace{} - v := Params{} - plugin.Param("workspace", &w) - plugin.Param("vargs", &v) - plugin.MustParse() + app := cli.NewApp() + app.Name = "pypi plugin" + app.Usage = "pypi publish plugin" + app.Action = run + app.Version = fmt.Sprintf("0.0.%s", build) + app.Flags = []cli.Flag{ - err := v.Deploy(&w) - if err != nil { - log.Fatal(err) + cli.StringFlag{ + Name: "repository", + Usage: "pypi repository URL", + Value: "https://upload.pypi.org/legacy/", + EnvVar: "PLUGIN_REPOSITORY", + }, + cli.StringFlag{ + Name: "username", + Usage: "pypi username", + Value: "guido", + EnvVar: "PLUGIN_USERNAME,PYPI_USERNAME", + }, + cli.StringFlag{ + Name: "password", + Usage: "pypi password", + Value: "secret", + EnvVar: "PLUGIN_PASSWORD,PYPI_PASSWORD", + }, + cli.StringFlag{ + Name: "setupfile", + Usage: "relative location of setup.py file", + Value: "setup.py", + EnvVar: "PLUGIN_SETUPFILE", + }, + cli.StringSliceFlag{ + Name: "distributions", + Usage: "distribution types to deploy", + EnvVar: "PLUGIN_DISTRIBUTIONS", + }, + cli.BoolTFlag{ + Name: "skip_build", + Usage: "skip build and only upload pre-build packages", + EnvVar: "PLUGIN_SKIP_BUILD", + }, } + + app.Run(os.Args) } -// Deploy creates a PyPI configuration file and uploads a module. -func (v *Params) Deploy(w *drone.Workspace) error { - err := v.CreateConfig() - if err != nil { - return err +func run(c *cli.Context) { + plugin := Plugin{ + Repository: c.String("repository"), + Username: c.String("username"), + Password: c.String("password"), + SetupFile: c.String("setupfile"), + Distributions: c.StringSlice("distributions"), + SkipBuild: c.Bool("skip_build"), } - err = v.UploadDist(w) - if err != nil { - return err - } - return nil -} -// CreateConfig creates a PyPI configuration file in the home directory of -// the current user. -func (v *Params) CreateConfig() error { - f, err := os.Create(path.Join(os.Getenv("HOME"), ".pypirc")) - if err != nil { - return err + if err := plugin.Exec(); err != nil { + fmt.Println(err) + os.Exit(1) } - defer f.Close() - buf := bufio.NewWriter(f) - err = v.WriteConfig(buf) - if err != nil { - return err - } - buf.Flush() - return nil -} - -// UploadDist executes a distutils command to upload a python module. -func (v *Params) UploadDist(w *drone.Workspace) error { - cmd := v.Upload() - cmd.Dir = w.Path - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - fmt.Println("$", strings.Join(cmd.Args, " ")) - err := cmd.Run() - if err != nil { - return err - } - return nil -} - -// WriteConfig writes a .pypirc to a supplied io.Writer. -func (v *Params) WriteConfig(w io.Writer) error { - repository := "https://pypi.python.org/pypi" - if v.Repository != nil { - repository = *v.Repository - } - username := "guido" - if v.Username != nil { - username = *v.Username - } - password := "secret" - if v.Password != nil { - password = *v.Password - } - _, err := io.WriteString(w, fmt.Sprintf(`[distutils] -index-servers = - pypi - -[pypi] -repository: %s -username: %s -password: %s -`, repository, username, password)) - return err -} - -// Upload creates a distutils upload command. -func (v *Params) Upload() *exec.Cmd { - distributions := []string{"sdist"} - if len(v.Distributions) > 0 { - distributions = v.Distributions - } - args := []string{"setup.py"} - for i := range distributions { - args = append(args, distributions[i]) - } - args = append(args, "upload") - args = append(args, "-r") - args = append(args, "pypi") - return exec.Command("python", args...) } diff --git a/main_test.go b/main_test.go deleted file mode 100644 index 7e0bb92..0000000 --- a/main_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package main - -import ( - "bytes" - "os" - "strings" - "testing" - - "github.com/drone/drone-go/drone" -) - -// TestPublish checks if this module can successfully publish a PyPI -// package. A simple module is included in the `testdata` directory. -// -// To run this test against the PyPI test server: -// -// 1. register a new account (https://wiki.python.org/moin/TestPyPI) -// 2. Export DRONE_PYPI_PATH, DRONE_PYPI_REPOSITORY, DRONE_PYPI_USERNAME, -// DRONE_PYPI_PASSWORD, and DRONE_PYPI_DISTRIBUTIONS -// 3. Run the test suite -// -// For example: -// -// $ export DRONE_PYPI_PATH=testdata -// $ export DRONE_PYPI_REPOSITORY=https://testpypi.python.org/pypi -// $ export DRONE_PYPI_USERNAME=drone_pypi_test -// $ export DRONE_PYPI_PASSWORD=$uper$ecretPassword -// $ export DRONE_PYPI_DISTRIBUTIONS=sdist -// $ go test -run TestPublish -// -// > NOTE: PyPI will refuse to upload the same version of a module twice, -// > however setup.py still returns zero to the shell so this appears as a -// > successful test. -func TestPublish(t *testing.T) { - w := drone.Workspace{Path: os.Getenv("DRONE_PYPI_PATH")} - repository := os.Getenv("DRONE_PYPI_REPOSITORY") - username := os.Getenv("DRONE_PYPI_USERNAME") - password := os.Getenv("DRONE_PYPI_PASSWORD") - v := Params{ - Repository: &repository, - Username: &username, - Password: &password, - Distributions: strings.Split(os.Getenv("DRONE_PYPI_DISTRIBUTIONS"), " "), - } - if w.Path == "" { - t.Skip("DRONE_PYPI_PATH not set") - } - err := v.Deploy(&w) - if err != nil { - t.Error(err) - } -} - -// TestConfig checks if a PyPI configuration file can be generated. -func TestConfig(t *testing.T) { - testdata := []struct { - repository *string - username *string - password *string - exp string - }{ - { - nil, - nil, - nil, - `[distutils] -index-servers = - pypi - -[pypi] -repository: https://pypi.python.org/pypi -username: guido -password: secret -`, - }, - { - sPtr("https://pypi.example.com"), - nil, - nil, - `[distutils] -index-servers = - pypi - -[pypi] -repository: https://pypi.example.com -username: guido -password: secret -`, - }, - { - nil, - sPtr("jqhacker"), - sPtr("supersecret"), - `[distutils] -index-servers = - pypi - -[pypi] -repository: https://pypi.python.org/pypi -username: jqhacker -password: supersecret -`, - }, - } - for i, data := range testdata { - v := Params{ - Repository: data.repository, - Username: data.username, - Password: data.password, - Distributions: []string{}, - } - var b bytes.Buffer - v.WriteConfig(&b) - if b.String() != data.exp { - t.Errorf("Case %d: Expected %s, got %s\n", i, data.exp, b.String()) - } - } -} - -// TestUpload checks if a distutils upload command can be properly -// formatted. -func TestUpload(t *testing.T) { - testdata := []struct { - distributions []string - exp []string - }{ - { - []string{}, - []string{"python", "setup.py", "sdist", "upload", "-r", "pypi"}, - }, - { - []string{"sdist", "bdist_wheel"}, - []string{"python", "setup.py", "sdist", "bdist_wheel", "upload", "-r", "pypi"}, - }, - } - for i, data := range testdata { - v := Params{Distributions: data.distributions} - c := v.Upload() - if len(c.Args) != len(data.exp) { - t.Errorf("Case %d: Expected %d, got %d", i, len(data.exp), len(c.Args)) - } - for i := range c.Args { - if c.Args[i] != data.exp[i] { - t.Errorf("Case %d: Expected %s, got %s", i, strings.Join(data.exp, " "), strings.Join(c.Args, " ")) - } - } - } -} - -func sPtr(s string) *string { - return &s -} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..796ccf3 --- /dev/null +++ b/plugin.go @@ -0,0 +1,65 @@ +package main + +import ( + "log" + "os/exec" + + "github.com/pkg/errors" +) + +// Plugin defines the PyPi plugin parameters +type Plugin struct { + Repository string + Username string + Password string + SetupFile string + Distributions []string + SkipBuild bool +} + +func (p Plugin) buildCommand() *exec.Cmd { + // Set the default of distributions in here + // as CLI package still has issues with string slice defaults + distributions := []string{"sdist"} + if len(p.Distributions) > 0 { + distributions = p.Distributions + } + args := []string{p.SetupFile} + for i := range distributions { + args = append(args, distributions[i]) + } + return exec.Command("python3", args...) +} + +func (p Plugin) uploadCommand() *exec.Cmd { + args := []string{} + args = append(args, "upload") + args = append(args, "--repository-url") + args = append(args, p.Repository) + args = append(args, "--username") + args = append(args, p.Username) + args = append(args, "--password") + args = append(args, p.Password) + args = append(args, "dist/*") + + return exec.Command("twine", args...) +} + +// Exec runs the plugin - doing the necessary setup.py modifications +func (p Plugin) Exec() error { + if !p.SkipBuild { + out, err := p.buildCommand().CombinedOutput() + if err != nil { + return errors.Wrap(err, string(out)) + } + log.Printf("Output: %s", out) + } + + out, err := p.uploadCommand().CombinedOutput() + if err != nil { + return errors.Wrap(err, string(out)) + } + log.Printf("Output: %s", out) + + return nil +} diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 0000000..f0e22af --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "os" + "strings" + "testing" +) + +func TestPublish(t *testing.T) { + repository := os.Getenv("PLUGIN_REPOSITORY") + username := os.Getenv("PLUGIN_USERNAME") + password := os.Getenv("PLUGIN_PASSWORD") + plugin := Plugin{ + Repository: repository, + Username: username, + Password: password, + SetupFile: "testdata/setup.py", + Distributions: strings.Split(os.Getenv("PLUGIN_DISTRIBUTIONS"), " "), + SkipBuild: false, + } + err := plugin.Exec() + if err != nil { + t.Error(err) + } +} + +// TestUpload checks if a distutils upload command can be properly +// generated and formatted. +func TestUpload(t *testing.T) { + testdata := []struct { + distributions []string + exp []string + }{ + { + []string{}, + []string{"python3", "testdata/setup.py", "sdist"}, + }, + { + []string{"sdist", "bdist_wheel"}, + []string{"python3", "testdata/setup.py", "sdist", "bdist_wheel"}, + }, + } + for i, data := range testdata { + p := Plugin{Distributions: data.distributions, SetupFile: "testdata/setup.py"} + c := p.buildCommand() + if len(c.Args) != len(data.exp) { + t.Errorf("Case %d: Expected %d, got %d", i, len(data.exp), len(c.Args)) + } + for i := range c.Args { + if c.Args[i] != data.exp[i] { + t.Errorf("Case %d: Expected %s, got %s", i, strings.Join(data.exp, " "), strings.Join(c.Args, " ")) + } + } + } +} diff --git a/testdata/.gitignore b/testdata/.gitignore deleted file mode 100644 index 5ae5013..0000000 --- a/testdata/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.egg-info/ -build/ -dist/ diff --git a/testdata/README b/testdata/README index ac60f68..880cc08 100644 --- a/testdata/README +++ b/testdata/README @@ -1 +1 @@ -A simple test module +Test package for Drone 0.8+ Drone-pypi plugin \ No newline at end of file diff --git a/testdata/config.ini b/testdata/config.ini new file mode 100644 index 0000000..ab0c4cd --- /dev/null +++ b/testdata/config.ini @@ -0,0 +1,68 @@ +[app:main] +use = egg:pypicloud + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +pypi.storage = file +storage.dir = /var/lib/pypicloud/packages + +# user: stevearc, pass: gunface +user.stevearc = $5$rounds=80000$yiWi67QBJLDTvbI/$d6qIG/bIoM3hp0lxH8v/vzxg8Qc4CJbxbxiUH4MlnE7 + +# For beaker +session.encrypt_key = replaceme +session.validate_key = replaceme +session.secure = false + +### +# wsgi server configuration +### + +[uwsgi] +paste = config:%p +paste-logger = %p +master = true +uid = pypicloud +gid = pypicloud +processes = 20 +reload-mercy = 15 +worker-reload-mercy = 15 +max-requests = 1000 +enable-threads = true +http = 0.0.0.0:8080 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, boto + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_boto] +level = WARN +qualname = boto +handlers = + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)s %(asctime)s [%(name)s] %(message)s diff --git a/testdata/drone_pypi_test/__init__.py b/testdata/drone_pypi_test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testdata/setup.py b/testdata/setup.py index 1af5b8c..2e3226f 100644 --- a/testdata/setup.py +++ b/testdata/setup.py @@ -1,28 +1,12 @@ from setuptools import setup -import subprocess - - -def get_version(): - try: - git = subprocess.Popen( - ["git", "describe", "--long"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - except Exception: - return "0.0.0" - val = git.communicate()[0] - if git.returncode != 0: - return "0.0.0" - l = val.strip().split("-") - return l[0] + "." + l[1] setup( - name="drone-pypi", - version=get_version(), - description="Module for testing drone-pypi.", - url="http://github.com/drone-plugins/drone-pypi", - packages=["drone_pypi"], - maintainer="Drone Contributors", - maintainer_email="support@drone.io", + name='drone-pypi-testbuild', + version='0.1.0', + description='Testing drone-pypi publishes, no other purpose.', + url='https://github.com/xoxys/drone-pypi', + packages=['testdata/drone_pypi_test'], + maintainer='Robert Kaussow', + maintainer_email="xoxys@rknet.org", )