From c9b663f487e8950ff0bad06be364725fa41896c6 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 15 Mar 2026 11:40:06 +0700 Subject: [PATCH] Initial commit --- .gitignore | 3 + LICENSE | 202 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 71 ++++++++++++++++ docker/Dockerfile | 12 +++ docs/card.json | 83 +++++++++++++++++++ docs/index.json | 0 go.mod | 10 +++ go.sum | 17 ++++ main.go | 45 ++++++++++ plugin/pipeline.go | 140 +++++++++++++++++++++++++++++++ plugin/plugin.go | 62 ++++++++++++++ 11 files changed, 645 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker/Dockerfile create mode 100644 docs/card.json create mode 100644 docs/index.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 plugin/pipeline.go create mode 100644 plugin/plugin.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e54935 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +release/ +vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..933c7f3 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +A plugin to generate SBOMs (Software Bill of Materials) using [anchore/syft](https://github.com/anchore/syft). + +# Usage + +The following settings changes this plugin's behavior. + +* select_catalogers (optional) comma-separated list of catalogers to use. +* output (optional) comma-separated list of output formats (e.g. `spdx-json=report.json,cyclonedx-json`). +* source_name (optional) name to use as the source in the SBOM. + +The source version is automatically derived from `DRONE_TAG` if set, otherwise `DRONE_COMMIT_SHA`. + +Below is an example `.drone.yml` that uses this plugin. + +```yaml +kind: pipeline +name: default + +steps: +- name: sbom + image: drone-plugins/drone-syft + pull: if-not-exists + settings: + output: spdx-json=sbom.spdx.json + source_name: my-project +``` + +Below is an example with multiple outputs and cataloger selection. + +```yaml +kind: pipeline +name: default + +steps: +- name: sbom + image: drone-plugins/drone-syft + pull: if-not-exists + settings: + select_catalogers: dpkg,rpm + output: spdx-json=sbom.spdx.json,cyclonedx-json=sbom.cdx.json + source_name: my-project +``` + +# Building + +Build the plugin binary: + +```text +scripts/build.sh +``` + +Build the plugin image: + +```text +docker build -t drone-plugins/drone-syft -f docker/Dockerfile . +``` + +# Testing + +Execute the plugin from your current working directory: + +```text +docker run --rm \ + -e DRONE_COMMIT_SHA=8f51ad7884c5eb69c11d260a31da7a745e6b78e2 \ + -e DRONE_COMMIT_BRANCH=master \ + -e DRONE_BUILD_NUMBER=43 \ + -e DRONE_BUILD_STATUS=success \ + -w /drone/src \ + -v $(pwd):/drone/src \ + drone-plugins/drone-syft +``` diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..9697473 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1-alpine AS builder + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -o /bin/plugin . + +FROM anchore/syft + +COPY --from=builder /bin/plugin /bin/plugin +ENTRYPOINT ["/bin/plugin"] diff --git a/docs/card.json b/docs/card.json new file mode 100644 index 0000000..12cdcd9 --- /dev/null +++ b/docs/card.json @@ -0,0 +1,83 @@ +{ + "type": "AdaptiveCard", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "type": "Image", + "url": "https://anchore.com/wp-content/uploads/2021/08/anchore-logo-2021.svg", + "size": "Small" + } + ], + "width": "auto" + }, + { + "type": "Column", + "items": [ + { + "type": "TextBlock", + "text": "Plugin: Syft", + "wrap": true, + "size": "Small", + "weight": "Bolder", + "isSubtle": false, + "spacing": "Small" + }, + { + "type": "TextBlock", + "text": "Source: ${$root.sourceName}", + "wrap": true, + "size": "Small", + "weight": "Lighter", + "isSubtle": true, + "spacing": "Small" + } + ], + "width": "stretch" + } + ], + "style": "default" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "size": "Small", + "text": "Version: ${$root.sourceVersion}", + "wrap": true, + "spacing": "None" + } + ] + }, + { + "type": "Column", + "width": "auto", + "separator": true, + "spacing": "Medium", + "items": [ + { + "type": "TextBlock", + "size": "Small", + "text": "Packages: ${formatNumber($root.packageCount, 0)}", + "wrap": true, + "spacing": "None" + } + ] + } + ], + "style": "default", + "separator": true + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5" +} diff --git a/docs/index.json b/docs/index.json new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0fa2ed9 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/drone-plugins/drone-syft + +go 1.25 + +require ( + github.com/kelseyhightower/envconfig v1.4.0 + github.com/sirupsen/logrus v1.9.3 +) + +require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9e3e5ef --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +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/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +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.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/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..de824d3 --- /dev/null +++ b/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + + "github.com/drone-plugins/drone-syft/plugin" + + "github.com/kelseyhightower/envconfig" + "github.com/sirupsen/logrus" +) + +func main() { + logrus.SetFormatter(new(formatter)) + + var args plugin.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/pipeline.go b/plugin/pipeline.go new file mode 100644 index 0000000..d019405 --- /dev/null +++ b/plugin/pipeline.go @@ -0,0 +1,140 @@ +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"` + } + + // 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..dbac34a --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,62 @@ +package plugin + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" +) + +// Args provides plugin execution arguments. +type Args struct { + Pipeline + Level string `envconfig:"PLUGIN_LOG_LEVEL"` + SelectCatalogers []string `envconfig:"PLUGIN_SELECT_CATALOGERS"` + Output []string `envconfig:"PLUGIN_OUTPUT"` + SourceName string `envconfig:"PLUGIN_SOURCE_NAME"` +} + +// Exec executes the plugin. +func Exec(ctx context.Context, args Args) error { + // Derive source version: prefer tag, fall back to commit SHA. + sourceVersion := args.Tag.Name + if sourceVersion == "" { + sourceVersion = args.Commit.Rev + } + + cmdArgs := []string{"scan", "dir:."} + + for _, c := range args.SelectCatalogers { + cmdArgs = append(cmdArgs, "--select-catalogers", c) + } + + for _, o := range args.Output { + cmdArgs = append(cmdArgs, "--output", o) + } + + if args.SourceName != "" { + cmdArgs = append(cmdArgs, "--source-name", args.SourceName) + } + + if sourceVersion != "" { + cmdArgs = append(cmdArgs, "--source-version", sourceVersion) + } + + cmd := exec.CommandContext(ctx, "syft", cmdArgs...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + trace(cmd) + + if err := cmd.Run(); err != nil { + return err + } + + return nil +} + +// trace writes each command to stdout with the command wrapped in an xml +// tag so that it can be extracted and displayed in the logs. +func trace(cmd *exec.Cmd) { + fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " ")) +}