From f13d7e965178e091f42465a7040a2f94489d65c4 Mon Sep 17 00:00:00 2001 From: Hamcha Date: Mon, 28 Oct 2019 14:31:40 +0100 Subject: [PATCH] First version --- .gitignore | 2 + README.md | 47 ++++++++++++++++++ go.mod | 8 ++++ go.sum | 16 +++++++ main.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ template.yml | 21 ++++++++ 6 files changed, 227 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 template.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0628f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.exe +.drone.yml \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d4cd7b1 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# drone-add + +Easily add Drone.io to projects using Drone APIs and YAML templates. + +## Getting started + +### Install drone-add + +```sh +go get -u git.fromouter.space/Hamcha/drone-add +``` + +or, if you have cloned the project locally, just `cd` to it and: + +```sh +go install . +``` + +### Set environment variables + +To authenticate with Drone you'll need to get your user token. +If you have the `drone` CLI tool, the environment variables are the same (`DRONE_SERVER` / `DRONE_TOKEN`). + +If you have drone secret you want to add, set them as environment variables using this format: + +`DRONE_SECRET_SECRET_NAME` → `secret_name` + +You can use template literals in your secrets, for example: + +`DRONE_SECRET_DOCKER_REPO = "my-custom-registry.tld/{{.Namespace}}/{{.Name}}"` with project `testorg/my-repo` will create a secret called `docker_repo` with value `my-custom-registry.tld/testorg/my-repo` + +### Change the template + +If you have not downloaded the project locally, download the template file from [here](https://git.fromouter.space/Hamcha/drone-add/raw/branch/master/template.yml). + +Change the template to fit what a starting `.drone.yml` for your projects would be like. +Usually this means just populating common steps (like publish and deployment steps). + +You can also use template literals in the template. + +### Run the tool + +Usage: + +```sh +drone-add [-t template.yml] [-o .drone.yml] namespace/repository +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4664e0f --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module git.fromouter.space/Hamcha/drone-add + +go 1.13 + +require ( + github.com/drone/drone-go v1.1.0 + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..74e7ce2 --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= +github.com/drone/drone-go v1.1.0 h1:2mritc5b7PhQWvILNyzaImZMRWVbMmmZ5Q0UDwwO7SI= +github.com/drone/drone-go v1.1.0/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4e9f1bb --- /dev/null +++ b/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + "text/template" + + "github.com/drone/drone-go/drone" + "golang.org/x/oauth2" +) + +const secretEnvPrefix = "DRONE_SECRET_" + +func main() { + tplpath := flag.String("t", "template.yml", "Template file") + outpath := flag.String("o", ".drone.yml", "Output .drone.yml") + flag.Parse() + + // Get drone server and token + + server := os.Getenv("DRONE_SERVER") + if server == "" { + fatal("DRONE_SERVER missing or empty") + } + + token := os.Getenv("DRONE_TOKEN") + if token == "" { + fatal("DRONE_TOKEN missing or empty") + } + + // Authenticate and connect to drone.io server + + config := new(oauth2.Config) + auther := config.Client( + oauth2.NoContext, + &oauth2.Token{ + AccessToken: token, + }, + ) + + client := drone.NewClient(server, auther) + + user, err := client.Self() + if err != nil { + fatal("Connection to drone.io failed: %s", err.Error()) + } + + // Get project + + projectname := flag.Arg(0) + if projectname == "" { + fatal("Repository name missing or empty") + } + + parts := strings.SplitN(projectname, "/", 2) + if len(parts) < 2 { + parts = []string{user.Login, projectname} + } + namespace := parts[0] + reponame := parts[1] + + // Enable repository on Drone.io + repo, err := client.RepoEnable(namespace, reponame) + if err != nil { + fatal("Error enabling repository %s/%s: %s", namespace, reponame, err.Error()) + } + + // Get secrets + + for _, env := range os.Environ() { + // Check for the magic prefix + if !strings.HasPrefix(env, secretEnvPrefix) { + continue + } + + // Split key=val + parts := strings.SplitN(env, "=", 2) + + // Ignore weird values + if len(parts) < 2 { + continue + } + + secretName := strings.ToLower(parts[0][len(secretEnvPrefix):]) + secretValue := parts[1] + + tpl, err := template.New("secret-" + secretName).Parse(secretValue) + if err != nil { + fatal("Secret %s is an invalid template: %s", parts[0], err.Error()) + } + + var builder strings.Builder + err = tpl.Execute(&builder, repo) + if err != nil { + fatal("Error while building secret %s: %s", secretName, err.Error()) + } + + // Add secret to project + client.SecretCreate(repo.Namespace, repo.Name, &drone.Secret{ + Name: secretName, + Data: secretValue, + }) + } + + // Read template file + + tpl, err := template.ParseFiles(*tplpath) + if err != nil { + fatal("Cannot read template file: %s", err.Error()) + } + + // Create output file + + outfile, err := os.Create(*outpath) + if err != nil { + fatal("Cannot create output file: %s", err.Error()) + } + defer outfile.Close() + + // Generate output from template + + err = tpl.Execute(outfile, repo) + if err != nil { + fatal("Error processing template: %s", err.Error()) + } +} + +func fatal(msg string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "FATAL: "+msg+"\n", args...) + os.Exit(1) +} diff --git a/template.yml b/template.yml new file mode 100644 index 0000000..5a2df3a --- /dev/null +++ b/template.yml @@ -0,0 +1,21 @@ +kind: pipeline +name: default + +steps: + - name: publish + image: plugins/docker + settings: + auto_tag: true + registry: + from_secret: docker_registry + repo: + from_secret: docker_repo + username: + from_secret: docker_username + password: + from_secret: docker_password + when: + event: + - push + - tag + branch: master