From 19209aa282256964f7a194b1965a51930d0e7c8b Mon Sep 17 00:00:00 2001 From: Senthil Kumar T Date: Thu, 14 Nov 2024 14:01:38 +0530 Subject: [PATCH] nexus plugin changes --- .gitignore | 3 + .idea/workspace.xml | 60 +++++ LICENSE.md | 55 +++++ docker/Dockerfile | 10 + docker/Dockerfile.linux.arm | 10 + docker/Dockerfile.linux.arm64 | 10 + docker/manifest.tmpl | 31 +++ drone-output.txt | 1 + go.mod | 12 + go.sum | 39 +++ main.go | 48 ++++ plugin/defs.go | 45 ++++ plugin/nexus_plugin.go | 445 ++++++++++++++++++++++++++++++++++ plugin/nexus_plugin_test.go | 185 ++++++++++++++ plugin/pipeline.go | 149 ++++++++++++ plugin/plugin.go | 62 +++++ plugin/plugin_test.go | 11 + plugin/util.go | 154 ++++++++++++ scripts/build.sh | 18 ++ scripts/upgrade.sh | 3 + 20 files changed, 1351 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/workspace.xml create mode 100644 LICENSE.md create mode 100644 docker/Dockerfile create mode 100644 docker/Dockerfile.linux.arm create mode 100644 docker/Dockerfile.linux.arm64 create mode 100644 docker/manifest.tmpl create mode 100644 drone-output.txt create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 plugin/defs.go create mode 100644 plugin/nexus_plugin.go create mode 100644 plugin/nexus_plugin_test.go create mode 100644 plugin/pipeline.go create mode 100644 plugin/plugin.go create mode 100644 plugin/plugin_test.go create mode 100644 plugin/util.go create mode 100755 scripts/build.sh create mode 100755 scripts/upgrade.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7be4581 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +release/ +drone-nexus-publish diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..22f860c --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + { + "associatedIndex": 0 +} + + + + + + + + + + + + + + + true + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c5402b9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,55 @@ +# Blue Oak Model License + +Version 1.0.0 + +## Purpose + +This license gives everyone as much permission to work with +this software as possible, while protecting contributors +from liability. + +## Acceptance + +In order to receive this license, you must agree to its +rules. The rules of this license are both obligations +under that agreement and conditions to your license. +You must not do anything with this software that triggers +a rule that you cannot or will not follow. + +## Copyright + +Each contributor licenses you to do everything with this +software that would otherwise infringe that contributor's +copyright in it. + +## Notices + +You must ensure that everyone who gets a copy of +any part of this software from you, with or without +changes, also gets the text of this license or a link to +. + +## Excuse + +If anyone notifies you in writing that you have not +complied with [Notices](#notices), you can keep your +license by taking all practical steps to comply within 30 +days after the notice. If you do not do so, your license +ends immediately. + +## Patent + +Each contributor licenses you to do everything with this +software that would otherwise infringe any patent claims +they can license or become able to license. + +## Reliability + +No contributor can revoke this license. + +## No Liability + +***As far as the law allows, this software comes as is, +without any warranty or condition, and no contributor +will be liable to anyone for any damages related to this +software or this license, under any kind of legal claim.*** diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..6d75094 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:3.6 as alpine +RUN apk add -U --no-cache ca-certificates + +FROM alpine:3.6 +ENV GODEBUG netdns=go + +COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +ADD release/linux/amd64/drone-nexus-publish /bin/ +ENTRYPOINT ["/bin/drone-nexus-publish"] \ No newline at end of file diff --git a/docker/Dockerfile.linux.arm b/docker/Dockerfile.linux.arm new file mode 100644 index 0000000..993c2e9 --- /dev/null +++ b/docker/Dockerfile.linux.arm @@ -0,0 +1,10 @@ +FROM alpine:3.6 as alpine +RUN apk add -U --no-cache ca-certificates + +FROM alpine:3.6 +ENV GODEBUG netdns=go + +COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +ADD release/linux/arm/plugin /bin/ +ENTRYPOINT ["/bin/plugin"] \ No newline at end of file diff --git a/docker/Dockerfile.linux.arm64 b/docker/Dockerfile.linux.arm64 new file mode 100644 index 0000000..1223423 --- /dev/null +++ b/docker/Dockerfile.linux.arm64 @@ -0,0 +1,10 @@ +FROM alpine:3.6 as alpine +RUN apk add -U --no-cache ca-certificates + +FROM alpine:3.6 +ENV GODEBUG netdns=go + +COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +ADD release/linux/arm64/plugin /bin/ +ENTRYPOINT ["/bin/plugin"] \ No newline at end of file diff --git a/docker/manifest.tmpl b/docker/manifest.tmpl new file mode 100644 index 0000000..9c73495 --- /dev/null +++ b/docker/manifest.tmpl @@ -0,0 +1,31 @@ +image: lhns/nexus-publish:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} +{{/if}} +manifests: + - + image: lhns/nexus-publish:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 + platform: + architecture: amd64 + os: linux + - + image: lhns/nexus-publish:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 + platform: + variant: v8 + architecture: arm64 + os: linux + - + image: lhns/nexus-publish:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm + platform: + variant: v7 + architecture: arm + os: linux + - + image: lhns/nexus-publish:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm + platform: + variant: v6 + architecture: arm + os: linux \ No newline at end of file diff --git a/drone-output.txt b/drone-output.txt new file mode 100644 index 0000000..af5fb42 --- /dev/null +++ b/drone-output.txt @@ -0,0 +1 @@ +UPLOAD_STATUS=Success diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bb7553c --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/harness-community/drone-nexus-publish + +go 1.12 + +require ( + github.com/datadrivers/go-nexus-client v1.13.0 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.9.0 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8580276 --- /dev/null +++ b/go.sum @@ -0,0 +1,39 @@ +github.com/datadrivers/go-nexus-client v1.13.0 h1:8/EMpdfUImdmMLCUf/KGB2883Ryw6pAfAMtjyLQKlGE= +github.com/datadrivers/go-nexus-client v1.13.0/go.mod h1:sPjBOxF7idUoiJoa730L3JyKZodjT0LDAvVF8u4kOLU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..20c8037 --- /dev/null +++ b/main.go @@ -0,0 +1,48 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package main + +import ( + "context" + "github.com/harness-community/drone-nexus-publish/plugin" + plg "github.com/harness-community/drone-nexus-publish/plugin" + "github.com/kelseyhightower/envconfig" + "github.com/sirupsen/logrus" +) + +func main() { + logrus.SetFormatter(new(formatter)) + + var args plg.Args + if err := envconfig.Process("", &args); err != nil { + logrus.Fatalln(err) + } + + switch args.Level { + case "debug": + logrus.SetFormatter(textFormatter) + logrus.SetLevel(logrus.DebugLevel) + case "trace": + logrus.SetFormatter(textFormatter) + logrus.SetLevel(logrus.TraceLevel) + } + + if _, err := plugin.Exec(context.Background(), args); err != nil { + logrus.Fatalln(err) + } +} + +// default formatter that writes logs without including timestamp +// or level information. +type formatter struct{} + +func (*formatter) Format(entry *logrus.Entry) ([]byte, error) { + return []byte(entry.Message), nil +} + +// text formatter that writes logs with level information +var textFormatter = &logrus.TextFormatter{ + DisableTimestamp: true, +} diff --git a/plugin/defs.go b/plugin/defs.go new file mode 100644 index 0000000..c9dd3a0 --- /dev/null +++ b/plugin/defs.go @@ -0,0 +1,45 @@ +package plugin + +type Plugin interface { + Init(args *Args) error + SetBuildRoot(buildRootPath string) error + DeInit() error + ValidateAndProcessArgs(args Args) error + DoPostArgsValidationSetup(args Args) error + Run() error + WriteOutputVariables() error + PersistResults() error + IsQuiet() bool + InspectProcessArgs(argNamesList []string) (map[string]interface{}, error) +} + +type Args struct { + Pipeline + EnvPluginInputArgs + Level string `envconfig:"PLUGIN_LOG_LEVEL"` +} + +type EnvPluginInputArgs struct { + NexusVersion string `envconfig:"PLUGIN_NEXUS_VERSION"` + Protocol string `envconfig:"PLUGIN_PROTOCOL"` + GroupId string `envconfig:"PLUGIN_GROUP_ID"` + Repository string `envconfig:"PLUGIN_REPOSITORY"` + Artifact string `envconfig:"PLUGIN_ARTIFACTS"` + Username string `envconfig:"PLUGIN_USERNAME"` + Password string `envconfig:"PLUGIN_PASSWORD"` + + // For backward compatibility + ServerUrl string `envconfig:"PLUGIN_SERVER_URL"` + Filename string `envconfig:"PLUGIN_FILENAME"` + Format string `envconfig:"PLUGIN_FORMAT"` + Attributes string `envconfig:"PLUGIN_ATTRIBUTES"` +} + +type Artifact struct { + File string `yaml:"file"` + Classifier string `yaml:"classifier"` + ArtifactId string `yaml:"artifactId"` + Type string `yaml:"type"` + Version string `yaml:"version"` + GroupId string `yaml:"groupId"` +} diff --git a/plugin/nexus_plugin.go b/plugin/nexus_plugin.go new file mode 100644 index 0000000..d8eb5c1 --- /dev/null +++ b/plugin/nexus_plugin.go @@ -0,0 +1,445 @@ +package plugin + +import ( + "bytes" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "regexp" + "strings" + + "gopkg.in/yaml.v2" +) + +type HttpClient interface { + Do(req *http.Request) (*http.Response, error) +} + +type NexusPlugin struct { + InputArgs *Args + IsMultiFileUpload bool + PluginProcessingInfo + NexusPluginResponse + HttpClient HttpClient +} + +type PluginProcessingInfo struct { + UserName string + Password string + ServerUrl string + Version string + Format string + Repository string + GroupId string + Artifacts []Artifact +} + +type NexusPluginResponse struct { + Failed []FailedArtifact `json:"failed"` +} + +type FailedArtifact struct { + File string `json:"file"` + ArtifactId string `json:"artifactId"` + Err string `json:"err"` +} + +func (n *NexusPlugin) Run() error { + LogPrintln(n, "Starting Nexus Plugin Run") + + if n.HttpClient == nil { + n.HttpClient = &http.Client{} + } + + for _, artifact := range n.Artifacts { + filePath := artifact.File + file, err := os.Open(filePath) + if err != nil { + n.addFailedArtifact(artifact, fmt.Sprintf("could not open file: %v", err)) + continue + } + + if n.Version == "nexus2" { + artifactURL := n.prepareNexus2ArtifactURL(artifact) + if err := n.uploadFileNexus2(artifactURL, file, filePath); err != nil { + n.addFailedArtifact(artifact, fmt.Sprintf("upload failed: %v", err)) + err := file.Close() + if err != nil { + LogPrintln(n, "Error closing file: ", err.Error()) + } + continue + } + } else if n.Version == "nexus3" { + if err := n.uploadFileNexus3(artifact, filePath); err != nil { + n.addFailedArtifact(artifact, fmt.Sprintf("upload failed: %v", err)) + err := file.Close() + if err != nil { + LogPrintln(n, "Error closing file: ", err.Error()) + } + continue + } + } + err = file.Close() + if err != nil { + LogPrintln(n, "Error closing file: ", err.Error()) + } + + fmt.Println("Successfully uploaded artifact:", filePath) + } + + if len(n.Failed) > 0 { + return GetNewError("NexusPlugin Error in Run: some artifacts failed to upload") + } + + return nil +} + +func (n *NexusPlugin) WriteOutputVariables() error { + + type EnvKvPair struct { + Key string + Value interface{} + } + var kvPairs []EnvKvPair + + if len(n.Failed) == 0 { + LogPrintln(n, "All artifacts uploaded successfully") + kvPairs = append(kvPairs, EnvKvPair{Key: "UPLOAD_STATUS", Value: "Success"}) + } else { + kvPairs = append(kvPairs, EnvKvPair{Key: "UPLOAD_STATUS", Value: n.Failed}) + } + + var retErr error = nil + + for _, kvPair := range kvPairs { + err := WriteEnvVariableAsString(kvPair.Key, kvPair.Value) + if err != nil { + retErr = err + } + } + + return retErr +} + +func (n *NexusPlugin) Init(args *Args) error { + n.InputArgs = args + return nil +} + +func (n *NexusPlugin) SetBuildRoot(buildRootPath string) error { + return nil +} + +func (n *NexusPlugin) DeInit() error { + return nil +} + +func (n *NexusPlugin) ValidateAndProcessArgs(args Args) error { + LogPrintln(n, "NexusPlugin BuildAndValidateArgs") + + err := n.DetermineIsMultiFileUpload(args) + if err != nil { + LogPrintln(n, "NexusPlugin Error in ValidateAndProcessArgs: "+err.Error()) + return err + } + + if n.IsMultiFileUpload { + err = n.IsMultiFileUploadArgsOk(args) + if err != nil { + LogPrintln(n, "NexusPlugin Error in ValidateAndProcessArgs: "+err.Error()) + return err + } + } else { + err = n.IsSingleFileUploadArgsOk(args) + if err != nil { + LogPrintln(n, "NexusPlugin Error in ValidateAndProcessArgs: "+err.Error()) + return err + } + } + + return nil +} + +func (n *NexusPlugin) DetermineIsMultiFileUpload(args Args) error { + LogPrintln(n, "NexusPlugin DetermineIsMultiFileUpload") + + switch { + case args.Attributes != "" && args.Artifact == "": + n.IsMultiFileUpload = false + case args.Artifact != "" && args.Attributes == "": + n.IsMultiFileUpload = true + case args.Attributes == "" && args.Artifact == "": + return GetNewError("Error in DetermineCompatibilityMode: both 'Attributes' and 'Artifact' cannot be empty") + default: + return GetNewError("Error in DetermineCompatibilityMode: both 'Attributes' and 'Artifact' provided, which is ambiguous") + } + + return nil +} + +func (n *NexusPlugin) IsMultiFileUploadArgsOk(args Args) error { + LogPrintln(n, "NexusPlugin IsMultiFileUploadArgsOk") + + requiredArgs := map[string]string{ + "username": args.Username, + "password": args.Password, + "protocol": args.Protocol, + "nexusUrl": args.ServerUrl, + "nexusVersion": args.NexusVersion, + "repository": args.Repository, + "groupId": args.GroupId, + "format": args.Format, + } + + for field, value := range requiredArgs { + if value == "" { + return GetNewError("Error in IsMultiFileUploadArgsOk: " + field + " cannot be empty") + } + } + + n.UserName = args.Username + n.Password = args.Password + n.Repository = args.Repository + n.ServerUrl = args.Protocol + "://" + args.ServerUrl + n.GroupId = args.GroupId + n.Version = args.NexusVersion + n.Format = args.Format + + // Unmarshalling YAML artifact data + var artifacts []Artifact + if err := yaml.Unmarshal([]byte(args.Artifact), &artifacts); err != nil { + return GetNewError("Error in IsMultiFileUploadArgsOk: Error decoding YAML: " + err.Error()) + } + + var filteredArtifacts []Artifact + for _, artifact := range artifacts { + missingFields := []string{} + if artifact.ArtifactId == "" { + missingFields = append(missingFields, "ArtifactId") + } + if artifact.File == "" { + missingFields = append(missingFields, "File") + } + if artifact.Type == "" { + missingFields = append(missingFields, "Type") + } + if artifact.Version == "" { + missingFields = append(missingFields, "Version") + } + if artifact.GroupId == "" { + artifact.GroupId = args.GroupId + } + if len(missingFields) > 0 { + n.addFailedArtifact(artifact, fmt.Sprintf("Missing fields: %s", strings.Join(missingFields, ", "))) + } else { + // Add to filtered list if all fields are valid + filteredArtifacts = append(filteredArtifacts, artifact) + } + } + + n.Artifacts = filteredArtifacts + return nil +} + +func (n *NexusPlugin) IsSingleFileUploadArgsOk(args Args) error { + LogPrintln(n, "NexusPlugin IsSingleFileUploadArgsOk") + + requiredArgs := map[string]string{ + "Username": args.Username, + "Password": args.Password, + "ServerUrl": args.ServerUrl, + "Filename": args.Filename, + "Format": args.Format, + "Repository": args.Repository, + } + + for field, value := range requiredArgs { + if value == "" { + return GetNewError("Error in IsSingleFileUploadArgsOk: " + field + " cannot be empty") + } + } + + requiredFields := []string{"CgroupId", "Cversion", "Aextension", "Aclassifier"} + values := make(map[string]string) + + pattern := regexp.MustCompile(`-(CgroupId|CartifactId|Cversion|Aextension|Aclassifier)=(\S+)`) + matches := pattern.FindAllStringSubmatch(args.Attributes, -1) + + for _, match := range matches { + if len(match) == 3 { + values[match[1]] = match[2] + } + } + + // Check if all required fields are present + for _, field := range requiredFields { + if values[field] == "" { + return GetNewError("Error in IsSingleFileUploadArgsOk: " + field + " cannot be empty") + } + } + n.UserName = args.Username + n.Password = args.Password + n.Repository = args.Repository + n.ServerUrl = args.ServerUrl + n.Format = args.Format + n.GroupId = values["CgroupId"] + n.Version = "nexus3" + n.Artifacts = []Artifact{ + { + File: args.Filename, + Classifier: values["Aclassifier"], + ArtifactId: values["CartifactId"], + Type: values["Aextension"], + Version: values["Cversion"], + GroupId: values["CgroupId"], + }, + } + + return nil +} + +func (n *NexusPlugin) DoPostArgsValidationSetup(args Args) error { + return nil +} + +func (n *NexusPlugin) PersistResults() error { + return nil +} + +func (n *NexusPlugin) IsQuiet() bool { + return false +} + +func (n *NexusPlugin) InspectProcessArgs(argNamesList []string) (map[string]interface{}, error) { + return nil, nil +} + +func GetNewNexusPlugin() NexusPlugin { + return NexusPlugin{} +} + +func (n *NexusPlugin) prepareNexus2ArtifactURL(artifact Artifact) string { + switch n.Format { + case "maven2": + return fmt.Sprintf("%s/repository/%s/%s/%s/%s/%s-%s.%s", + n.ServerUrl, n.Repository, artifact.GroupId, artifact.ArtifactId, artifact.Version, + artifact.ArtifactId, artifact.Version, artifact.Type) + + case "yum": + return fmt.Sprintf("%s/repository/%s/%s/%s", + n.ServerUrl, n.Repository, artifact.ArtifactId, artifact.Version) + + case "raw": + return fmt.Sprintf("%s/repository/%s/%s/%s.%s", + n.ServerUrl, n.Repository, artifact.GroupId, artifact.ArtifactId, artifact.Type) + + default: + LogPrintln(n, "Unsupported format for direct upload:", n.Format) + return "" + } +} + +func (n *NexusPlugin) uploadFileNexus2(url string, content io.Reader, filePath string) error { + req, err := http.NewRequest("PUT", url, content) + if err != nil { + return err + } + + req.SetBasicAuth(n.UserName, n.Password) + req.Header.Set("Content-Type", "application/octet-stream") + + resp, err := n.HttpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + fmt.Println("File upload failed status ", resp.StatusCode) + return fmt.Errorf("Upload failed with status %d", resp.StatusCode) + } + return nil +} + +func (n *NexusPlugin) uploadFileNexus3(artifact Artifact, filePath string) error { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + var url string + var assetFieldName string + + switch n.Format { + case "maven2": + _ = writer.WriteField("maven2.groupId", artifact.GroupId) + _ = writer.WriteField("maven2.artifactId", artifact.ArtifactId) + _ = writer.WriteField("maven2.version", artifact.Version) + assetFieldName = "maven2.asset1" + _ = writer.WriteField("maven2.asset1.extension", artifact.Type) + + case "raw": + _ = writer.WriteField("raw.directory", artifact.GroupId) + assetFieldName = "raw.asset1" + _ = writer.WriteField("raw.asset1.filename", fmt.Sprintf("%s.%s", artifact.ArtifactId, artifact.Type)) + + default: + assetFieldName = fmt.Sprintf("%s.asset", n.Format) + } + + fileWriter, err := writer.CreateFormFile(assetFieldName, artifact.File) + if err != nil { + LogPrintln(n, "Error CreateFormFile: ", err.Error()) + return err + } + file, err := os.Open(artifact.File) + if err != nil { + LogPrintln(n, "Error os.Open(artifact.File): ", err.Error()) + return err + } + defer file.Close() + _, err = io.Copy(fileWriter, file) + if err != nil { + LogPrintln(n, "Error io.Copy(fileWriter, file): ", err.Error()) + return err + } + + err = writer.Close() + if err != nil { + LogPrintln(n, "Error writer.Close(): ", err.Error()) + return err + } + + url = fmt.Sprintf("%s/service/rest/v1/components?repository=%s", n.ServerUrl, n.Repository) + + req, err := http.NewRequest("POST", url, body) + if err != nil { + LogPrintln(n, "Error http.NewRequest: ", err.Error()) + return err + } + + req.SetBasicAuth(n.UserName, n.Password) + req.Header.Set("Content-Type", writer.FormDataContentType()) + + resp, err := n.HttpClient.Do(req) + if err != nil { + LogPrintln(n, "Error n.HttpClient.Do(req): ", err.Error()) + return err + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + LogPrintln(n, "Error upload failed with status: ", resp.StatusCode) + return fmt.Errorf("Upload failed with status %d", resp.StatusCode) + } + + return nil +} + +func (n *NexusPlugin) addFailedArtifact(artifact Artifact, errMsg string) { + n.Failed = append(n.Failed, FailedArtifact{ + File: artifact.File, + ArtifactId: artifact.ArtifactId, + Err: errMsg, + }) +} diff --git a/plugin/nexus_plugin_test.go b/plugin/nexus_plugin_test.go new file mode 100644 index 0000000..103873a --- /dev/null +++ b/plugin/nexus_plugin_test.go @@ -0,0 +1,185 @@ +package plugin + +import ( + "io/ioutil" + "net/http" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockHttpClient struct { + mock.Mock +} + +func (m *MockHttpClient) Do(req *http.Request) (*http.Response, error) { + args := m.Called(req) + return args.Get(0).(*http.Response), args.Error(1) +} + +// Utility function to create a temporary file for testing +func createTempFile(content string) (string, error) { + tmpFile, err := ioutil.TempFile("", "testfile_*.zip") + if err != nil { + return "", err + } + if _, err := tmpFile.Write([]byte(content)); err != nil { + tmpFile.Close() + return "", err + } + if err := tmpFile.Close(); err != nil { + return "", err + } + return tmpFile.Name(), nil +} + +func TestNexusPlugin_Run_UploadFailed(t *testing.T) { + mockClient := new(MockHttpClient) + mockResp := &http.Response{ + StatusCode: 500, + Body: ioutil.NopCloser(strings.NewReader("Internal Server Error")), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil) + + tmpFile, err := createTempFile("testfile.zip") + assert.NoError(t, err) + defer os.Remove(tmpFile) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + GroupId: "group", + Version: "1.0.0", + Artifacts: []Artifact{ + { + File: tmpFile, + ArtifactId: "artifact123", + Type: "zip", + }, + }, + }, + HttpClient: mockClient, + } + + err = plugin.Run() + + assert.NotNil(t, err) + assert.Len(t, plugin.Failed, 1) + assert.Equal(t, tmpFile, plugin.Failed[0].File) + assert.Equal(t, "artifact123", plugin.Failed[0].ArtifactId) + assert.Contains(t, plugin.Failed[0].Err, "upload failed") + mockClient.AssertExpectations(t) +} + +// The following tests validate argument processing without needing an actual file + +func TestNexusPlugin_ValidateAndProcessArgs_MultiFileUpload_Success(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + Protocol: "https", + ServerUrl: "nexus.example.com", + NexusVersion: "3", + Repository: "repo", + GroupId: "group", + Format: "maven2", + Artifact: "[{ \"artifactId\": \"artifact123\", \"file\": \"testfile.zip\", \"type\": \"zip\", \"version\": \"1\" }]", + }, + } + + plugin := NexusPlugin{} + err := plugin.ValidateAndProcessArgs(args) + + assert.Nil(t, err) + assert.Len(t, plugin.Artifacts, 1) + assert.Equal(t, "testfile.zip", plugin.Artifacts[0].File) + assert.Equal(t, "artifact123", plugin.Artifacts[0].ArtifactId) +} + +func TestNexusPlugin_ValidateAndProcessArgs_SingleFileUpload_Success(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Filename: "testfile.zip", + Format: "zip", + Repository: "repo", + Attributes: "-CgroupId=group -CartifactId=artifact123 -Cversion=1.0.0 -Aextension=zip -Aclassifier=classifier", + }, + } + + plugin := NexusPlugin{} + err := plugin.ValidateAndProcessArgs(args) + + assert.Nil(t, err) + assert.Len(t, plugin.Artifacts, 1) + assert.Equal(t, "testfile.zip", plugin.Artifacts[0].File) + assert.Equal(t, "artifact123", plugin.Artifacts[0].ArtifactId) +} + +func TestNexusPlugin_ValidateAndProcessArgs_MissingArguments(t *testing.T) { + args := Args{ + EnvPluginInputArgs: EnvPluginInputArgs{ + Username: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Filename: "testfile.zip", + Format: "zip", + Repository: "repo", + }, + } + + plugin := NexusPlugin{} + err := plugin.ValidateAndProcessArgs(args) + + assert.NotNil(t, err) + assert.Equal(t, "Error in DetermineCompatibilityMode: both 'Attributes' and 'Artifact' cannot be empty", err.Error()) +} + +func TestNexusPlugin_Run_MultiFileUpload_Success(t *testing.T) { + mockClient := new(MockHttpClient) + mockResp := &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("Success")), + } + mockClient.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil).Maybe() + + tmpFile1, err := createTempFile("file1.zip") + assert.NoError(t, err) + defer os.Remove(tmpFile1) + + tmpFile2, err := createTempFile("file2.zip") + assert.NoError(t, err) + defer os.Remove(tmpFile2) + + plugin := NexusPlugin{ + PluginProcessingInfo: PluginProcessingInfo{ + UserName: "testUser", + Password: "testPass", + ServerUrl: "https://nexus.example.com", + Repository: "repo", + GroupId: "group", + Version: "1.0.0", + Format: "maven2", + Artifacts: []Artifact{ + {File: tmpFile1, ArtifactId: "artifact1", Type: "zip", Version: "1"}, + {File: tmpFile2, ArtifactId: "artifact2", Type: "zip", Version: "1"}, + }, + }, + HttpClient: mockClient, + } + + err = plugin.Run() + + assert.Nil(t, err) + assert.Empty(t, plugin.Failed) + mockClient.AssertExpectations(t) +} diff --git a/plugin/pipeline.go b/plugin/pipeline.go new file mode 100644 index 0000000..af82a23 --- /dev/null +++ b/plugin/pipeline.go @@ -0,0 +1,149 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +// Pipeline provides Pipeline metadata from the environment. +type Pipeline struct { + // Build provides build metadata. + Build struct { + Branch string `envconfig:"DRONE_BUILD_BRANCH"` + Number int `envconfig:"DRONE_BUILD_NUMBER"` + Parent int `envconfig:"DRONE_BUILD_PARENT"` + Event string `envconfig:"DRONE_BUILD_EVENT"` + Action string `envconfig:"DRONE_BUILD_ACTION"` + Status string `envconfig:"DRONE_BUILD_STATUS"` + Created int64 `envconfig:"DRONE_BUILD_CREATED"` + Started int64 `envconfig:"DRONE_BUILD_STARTED"` + Finished int64 `envconfig:"DRONE_BUILD_FINISHED"` + Link string `envconfig:"DRONE_BUILD_LINK"` + } + + // Calver provides the calver details parsed from the + // git tag. If the git tag is empty or is not a valid + // calver, the values will be empty. + Calver struct { + Version string `envconfig:"DRONE_CALVER"` + Short string `envconfig:"DRONE_CALVER_SHORT"` + MajorMinor string `envconfig:"DRONE_CALVER_MAJOR_MINOR"` + Major string `envconfig:"DRONE_CALVER_MAJOR"` + Minor string `envconfig:"DRONE_CALVER_MINOR"` + Micro string `envconfig:"DRONE_CALVER_MICRO"` + Modifier string `envconfig:"DRONE_CALVER_MODIFIER"` + } + + // Card provides adaptive card configuration options. + Card struct { + Path string `envconfig:"DRONE_CARD_PATH"` + } + + // Commit provides the commit metadata. + Commit struct { + Rev string `envconfig:"DRONE_COMMIT_SHA"` + Before string `envconfig:"DRONE_COMMIT_BEFORE"` + After string `envconfig:"DRONE_COMMIT_AFTER"` + Ref string `envconfig:"DRONE_COMMIT_REF"` + Branch string `envconfig:"DRONE_COMMIT_BRANCH"` + Source string `envconfig:"DRONE_COMMIT_SOURCE"` + Target string `envconfig:"DRONE_COMMIT_TARGET"` + Link string `envconfig:"DRONE_COMMIT_LINK"` + Message string `envconfig:"DRONE_COMMIT_MESSAGE"` + + Author struct { + Username string `envconfig:"DRONE_COMMIT_AUTHOR"` + Name string `envconfig:"DRONE_COMMIT_AUTHOR_NAME"` + Email string `envconfig:"DRONE_COMMIT_AUTHOR_EMAIL"` + Avatar string `envconfig:"DRONE_COMMIT_AUTHOR_AVATAR"` + } + } + + // Deploy provides the deployment metadata. + Deploy struct { + ID string `envconfig:"DRONE_DEPLOY_TO"` + Target string `envconfig:"DRONE_DEPLOY_ID"` + } + + // Failed provides a list of failed steps and failed stages + // for the current pipeline. + Failed struct { + Steps []string `envconfig:"DRONE_FAILED_STEPS"` + Stages []string `envconfig:"DRONE_FAILED_STAGES"` + } + + // Git provides the git repository metadata. + Git struct { + HTTPURL string `envconfig:"DRONE_GIT_HTTP_URL"` + SSHURL string `envconfig:"DRONE_GIT_SSH_URL"` + } + + // PullRequest provides the pull request metadata. + PullRequest struct { + Number int `envconfig:"DRONE_PULL_REQUEST"` + } + + // Repo provides the repository metadata. + Repo struct { + Branch string `envconfig:"DRONE_REPO_BRANCH"` + Link string `envconfig:"DRONE_REPO_LINK"` + Namespace string `envconfig:"DRONE_REPO_NAMESPACE"` + Name string `envconfig:"DRONE_REPO_NAME"` + Private bool `envconfig:"DRONE_REPO_PRIVATE"` + Remote string `envconfig:"DRONE_GIT_HTTP_URL"` + SCM string `envconfig:"DRONE_REPO_SCM"` + Slug string `envconfig:"DRONE_REPO"` + Visibility string `envconfig:"DRONE_REPO_VISIBILITY"` + } + + // Stage provides the stage metadata. + Stage struct { + Kind string `envconfig:"DRONE_STAGE_KIND"` + Type string `envconfig:"DRONE_STAGE_TYPE"` + Name string `envconfig:"DRONE_STAGE_NAME"` + Number int `envconfig:"DRONE_STAGE_NUMBER"` + Machine string `envconfig:"DRONE_STAGE_MACHINE"` + OS string `envconfig:"DRONE_STAGE_OS"` + Arch string `envconfig:"DRONE_STAGE_ARCH"` + Variant string `envconfig:"DRONE_STAGE_VARIANT"` + Status string `envconfig:"DRONE_STAGE_STATUS"` + Started int64 `envconfig:"DRONE_STAGE_STARTED"` + Finished int64 `envconfig:"DRONE_STAGE_FINISHED"` + DependsOn []string `envconfig:"DRONE_STAGE_DEPENDS_ON"` + } + + // Step provides the step metadata. + Step struct { + Number int `envconfig:"DRONE_STEP_NUMBER"` + Name string `envconfig:"DRONE_STEP_NAME"` + } + + // Semver provides the semver details parsed from the + // git tag. If the git tag is empty or is not a valid + // semver, the values will be empty and the error field + // will be populated with the parsing error. + Semver struct { + Version string `envconfig:"DRONE_SEMVER"` + Short string `envconfig:"DRONE_SEMVER_SHORT"` + Major string `envconfig:"DRONE_SEMVER_MAJOR"` + Minor string `envconfig:"DRONE_SEMVER_MINOR"` + Patch string `envconfig:"DRONE_SEMVER_PATCH"` + Build string `envconfig:"DRONE_SEMVER_BUILD"` + PreRelease string `envconfig:"DRONE_SEMVER_PRERELEASE"` + Error string `envconfig:"DRONE_SEMVER_ERROR"` + } + + // System provides the Drone system metadata, including + // the system version of details required to create the + // drone website address. + System struct { + Proto string `envconfig:"DRONE_SYSTEM_PROTO"` + Host string `envconfig:"DRONE_SYSTEM_HOST"` + Hostname string `envconfig:"DRONE_SYSTEM_HOSTNAME"` + Version string `envconfig:"DRONE_SYSTEM_VERSION"` + } + + // Tag provides the git tag details. + Tag struct { + Name string `envconfig:"DRONE_TAG"` + } +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000..343c11e --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,62 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +import ( + "context" +) + +func GetNewPlugin(ctx context.Context, args Args) (Plugin, error) { + + nxp := GetNewNexusPlugin() + return &nxp, nil +} + +func Exec(ctx context.Context, args Args) (Plugin, error) { + + plugin, err := GetNewPlugin(ctx, args) + if err != nil { + return plugin, err + } + + err = plugin.Init(&args) + if err != nil { + return plugin, err + } + defer func(p Plugin) { + err := p.DeInit() + if err != nil { + LogPrintln(p, "Error in DeInit: "+err.Error()) + } + }(plugin) + + err = plugin.ValidateAndProcessArgs(args) + if err != nil { + return plugin, err + } + + err = plugin.DoPostArgsValidationSetup(args) + if err != nil { + return plugin, err + } + + err = plugin.Run() + + err2 := plugin.WriteOutputVariables() + if err2 != nil { + LogPrintln(plugin, "Writing output variable UPLOAD_STATUS failed "+err2.Error()) + } + if err != nil { + LogPrintln(plugin, "Upload failed "+err.Error()) + return plugin, err + } + + err = plugin.PersistResults() + if err != nil { + return plugin, err + } + + return plugin, nil +} diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go new file mode 100644 index 0000000..cf5d77a --- /dev/null +++ b/plugin/plugin_test.go @@ -0,0 +1,11 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +import "testing" + +func TestPlugin(t *testing.T) { + t.Skip() +} diff --git a/plugin/util.go b/plugin/util.go new file mode 100644 index 0000000..90511e9 --- /dev/null +++ b/plugin/util.go @@ -0,0 +1,154 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + + "github.com/sirupsen/logrus" +) + +func GetNewError(s string) error { + return errors.New(s) +} + +func LogPrintln(p Plugin, args ...interface{}) { + + if !IsDevTestingMode() { + return + } + + if p != nil { + if p.IsQuiet() { + return + } + } + + logrus.Println(append([]interface{}{"Plugin Info:"}, args...)...) +} + +func LogPrintf(p Plugin, format string, v ...interface{}) { + + if !IsDevTestingMode() { + return + } + + if p != nil { + if p.IsQuiet() { + return + } + } + logrus.Printf(format, v...) +} + +func IsDirExists(dir string) (bool, error) { + info, err := os.Stat(dir) + if os.IsNotExist(err) { + return false, err + } + return info.IsDir(), nil +} + +func CreateDir(absolutePath string) error { + if absolutePath == "" || absolutePath == "." || absolutePath == ".." { + return nil + } + + err := os.MkdirAll(absolutePath, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to create directory %s: %w", absolutePath, err) + } + return nil +} + +func GetOutputVariablesStorageFilePath() string { + if IsDevTestingMode() { + return filepath.Join("/tmp", "drone-output") + } + return os.Getenv("DRONE_OUTPUT") +} + +func ReadFileAsString(filePath string) (string, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + return string(data), nil +} + +func WriteEnvVariableAsString(key string, value interface{}) error { + + if GetOutputVariablesStorageFilePath() == "" { + return GetNewError("Output file path is empty, check env var DRONE_OUTPUT") + } + + outputFile, err := os.OpenFile(GetOutputVariablesStorageFilePath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to open output file: %w", err) + } + defer outputFile.Close() + + valueStr := fmt.Sprintf("%v", value) + + _, err = fmt.Fprintf(outputFile, "%s=%s\n", key, valueStr) + if err != nil { + return fmt.Errorf("failed to write to env: %w", err) + } + + return nil +} + +func IsDevTestingMode() bool { + return os.Getenv("DEV_TEST_d6c9b463090c") == "true" +} + +func StructToJSONWithEnvKeys(v interface{}) (string, error) { + val := reflect.ValueOf(v) + typ := reflect.TypeOf(v) + + data := make(map[string]interface{}) + + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + key := field.Tag.Get("envconfig") + if key != "" { + data[key] = val.Field(i).Interface() + } + } + + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + return "", err + } + + return string(jsonData), nil +} + +func GetTestWorkSpaceDir() string { + + nexusWorkSpaceDir := os.Getenv(DefaultWorkSpaceDirEnvVarKey) + if nexusWorkSpaceDir == "" { + nexusWorkSpaceDir = TestWorkSpaceDir + } + + return nexusWorkSpaceDir +} + +func GetTestBuildRootDir() string { + return GetTestWorkSpaceDir() +} + +const ( + DefaultWorkSpaceDirEnvVarKey = "DRONE_WORKSPACE" + TestWorkSpaceDir = "../test/tmp_workspace" +) + +// +// diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..12ab915 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# force go modules +export GOPATH="" + +# disable cgo +export CGO_ENABLED=0 + +set -e +set -x + +# linux +GOOS=linux GOARCH=amd64 go build -o release/linux/amd64/drone-nexus-publish +#GOOS=linux GOARCH=arm64 go build -o release/linux/arm64/drone-nexus-publish +#GOOS=linux GOARCH=arm go build -o release/linux/arm/drone-nexus-publish + +# windows +#GOOS=windows go build -o release/windows/amd64/drone-nexus-publish.exe diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh new file mode 100755 index 0000000..dd5e50b --- /dev/null +++ b/scripts/upgrade.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +curl https://raw.githubusercontent.com/drone/boilr-plugin/master/template/plugin/pipeline.go --output plugin/pipeline.go