mirror of
https://github.com/drone-plugins/drone-manifest.git
synced 2026-06-14 05:12:46 +08:00
Update plugin to new conventions
Instead of downloading and using the manifest tool mimic the behavior of the CLI commands used. This is now possible with the v2 of the manifest tool library which can be used in this manner. Use the new coding conventions for the plugin.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2023, the Drone Plugins project authors.
|
||||
// Please see the AUTHORS file for details. All rights reserved.
|
||||
// Use of this source code is governed by an Apache 2.0 license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type (
|
||||
legacyRepo struct {
|
||||
Owner string
|
||||
Name string
|
||||
Branch string
|
||||
}
|
||||
|
||||
legacyBuild struct {
|
||||
Path string
|
||||
Tag string
|
||||
Event string
|
||||
Number int
|
||||
Commit string
|
||||
Ref string
|
||||
Branch string
|
||||
Author string
|
||||
Pull string
|
||||
Message string
|
||||
DeployTo string
|
||||
Status string
|
||||
Link string
|
||||
Started int64
|
||||
Created int64
|
||||
Tags []string
|
||||
}
|
||||
|
||||
legacyJob struct {
|
||||
Started int64
|
||||
}
|
||||
|
||||
legacyPlugin struct {
|
||||
Repo legacyRepo
|
||||
Build legacyBuild
|
||||
Job legacyJob
|
||||
}
|
||||
)
|
||||
|
||||
func toLegacyPlugin(args *Args) legacyPlugin {
|
||||
return legacyPlugin{
|
||||
Repo: legacyRepo{
|
||||
Owner: args.Repo.Namespace,
|
||||
Name: args.Repo.Name,
|
||||
Branch: args.Repo.Branch,
|
||||
},
|
||||
Build: legacyBuild{
|
||||
Path: getEnv("DRONE_WORKSPACE", ""),
|
||||
Tag: args.Tag.Name,
|
||||
Number: args.Build.Number,
|
||||
Event: args.Build.Event,
|
||||
Status: args.Build.Status,
|
||||
Commit: args.Commit.Rev,
|
||||
Ref: args.Commit.Ref,
|
||||
Branch: args.Commit.Branch,
|
||||
Pull: strconv.FormatInt(int64(args.PullRequest.Number), 10), // c.String("commit.pull"),
|
||||
Started: args.Build.Started,
|
||||
Created: args.Build.Created,
|
||||
Tags: args.Tags,
|
||||
},
|
||||
Job: legacyJob{
|
||||
Started: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2023, the Drone Plugins project authors.
|
||||
// Please see the AUTHORS file for details. All rights reserved.
|
||||
// Use of this source code is governed by an Apache 2.0 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"`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright (c) 2023, the Drone Plugins project authors.
|
||||
// Please see the AUTHORS file for details. All rights reserved.
|
||||
// Use of this source code is governed by an Apache 2.0 license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/drone-plugins/drone-manifest/tagging"
|
||||
|
||||
"github.com/drone/drone-go/drone"
|
||||
"github.com/drone/drone-template-lib/template"
|
||||
"github.com/estesp/manifest-tool/v2/pkg/registry"
|
||||
"github.com/estesp/manifest-tool/v2/pkg/types"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Args provides plugin execution arguments.
|
||||
type (
|
||||
Args struct {
|
||||
Pipeline
|
||||
|
||||
// Level defines the plugin log level.
|
||||
Level string `envconfig:"PLUGIN_LOG_LEVEL"`
|
||||
|
||||
// Skip verification of certificates
|
||||
SkipVerify bool `envconfig:"PLUGIN_SKIP_VERIFY"`
|
||||
|
||||
// Lint plugin
|
||||
Lint bool `envconfig:"PLUGIN_LINT" default:"true"`
|
||||
|
||||
// Plugin specific
|
||||
Username string `envconfig:"PLUGIN_USERNAME"`
|
||||
Password string `envconfig:"PLUGIN_PASSWORD"`
|
||||
Platforms []string `envconfig:"PLUGIN_PLATFORMS"`
|
||||
Target string `envconfig:"PLUGIN_TARGET"`
|
||||
Template string `envconfig:"PLUGIN_TEMPLATE"`
|
||||
Spec string `envconfig:"PLUGIN_SPEC"`
|
||||
IgnoreMissing bool `envconfig:"PLUGIN_IGNORE_MISSING"`
|
||||
Tags []string `envconfig:"PLUGIN_TAGS"`
|
||||
AutoTag bool `envconfig:"PLUGIN_AUTO_TAG"`
|
||||
}
|
||||
)
|
||||
|
||||
var errConfiguration = errors.New("configuration error")
|
||||
|
||||
// Exec executes the plugin.
|
||||
func Exec(ctx context.Context, args *Args) error {
|
||||
linter := ""
|
||||
|
||||
if args.Lint {
|
||||
issues, warnings := lintArgs(args)
|
||||
linter = fmt.Sprintf("lint: %d issue(s) found\n%s", issues, warnings)
|
||||
logrus.Info(linter)
|
||||
}
|
||||
|
||||
err := verifyArgs(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in the configuration: %w", err)
|
||||
}
|
||||
|
||||
// Auto tag behavior
|
||||
if args.AutoTag {
|
||||
if tagging.UseDefaultTag(args.Commit.Ref, args.Repo.Branch) {
|
||||
args.Tags = tagging.DefaultTags(args.Commit.Ref)
|
||||
} else {
|
||||
logrus.Infof("skipping automated tags for %s", args.Commit.Ref)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get the yaml to push
|
||||
var yamlFunc func(*Args) (types.YAMLInput, error)
|
||||
if args.Spec != "" {
|
||||
yamlFunc = yamlFromSpec
|
||||
} else {
|
||||
yamlFunc = yamlFromArgs
|
||||
}
|
||||
|
||||
yamlInput, err := yamlFunc(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create manifest spec: %w", err)
|
||||
}
|
||||
|
||||
logrus.Info("pushing manifest")
|
||||
|
||||
digest, length, err := registry.PushManifestList(
|
||||
args.Username, // --username
|
||||
args.Password, // --password
|
||||
yamlInput, // --from-spec
|
||||
args.IgnoreMissing, // --ignore-missing
|
||||
args.SkipVerify, // --insecure
|
||||
false, // --plain-http
|
||||
types.Docker, // --type
|
||||
"", // --docker-cfg
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not push manifest list: %w", err)
|
||||
}
|
||||
|
||||
logrus.Infof("manifest pushed: digest %s %d", digest, length)
|
||||
|
||||
// Create the card data
|
||||
cardData := struct {
|
||||
Image string `json:"image"`
|
||||
Digest string `json:"digest"`
|
||||
Linter string `json:"linter"`
|
||||
}{
|
||||
Image: yamlInput.Image,
|
||||
Digest: digest,
|
||||
Linter: linter,
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(cardData)
|
||||
card := drone.CardInput{
|
||||
Schema: "https://drone-plugins.github.io/drone-manifest/card.json",
|
||||
Data: data,
|
||||
}
|
||||
writeCard(args.Card.Path, &card)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lintArgs(args *Args) (issues int, warnings string) {
|
||||
issues = 0
|
||||
var warningsBuilder strings.Builder
|
||||
|
||||
if value, present := os.LookupEnv("PLUGIN_INSECURE"); present {
|
||||
warningsBuilder.WriteString("remove insecure from config and use skip_verify instead")
|
||||
args.SkipVerify = value == "true"
|
||||
issues++
|
||||
}
|
||||
|
||||
return issues, warningsBuilder.String()
|
||||
}
|
||||
|
||||
func verifyArgs(args *Args) error {
|
||||
if args.Username == "" {
|
||||
return fmt.Errorf("no username provided: %w", errConfiguration)
|
||||
}
|
||||
|
||||
if args.Password == "" {
|
||||
return fmt.Errorf("no password provided: %w", errConfiguration)
|
||||
}
|
||||
|
||||
if args.Spec == "" {
|
||||
if len(args.Platforms) == 0 {
|
||||
return fmt.Errorf("no platforms provided: %w", errConfiguration)
|
||||
}
|
||||
|
||||
if args.Target == "" {
|
||||
return fmt.Errorf("no target provided: %w", errConfiguration)
|
||||
}
|
||||
|
||||
if args.Template == "" {
|
||||
return fmt.Errorf("no template provided: %w", errConfiguration)
|
||||
}
|
||||
} else if len(args.Platforms) != 0 || args.Target != "" || args.Template != "" {
|
||||
return fmt.Errorf("both spec and arguments provided: %w", errConfiguration)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func yamlFromSpec(args *Args) (types.YAMLInput, error) {
|
||||
var yamlInput types.YAMLInput
|
||||
var raw []byte
|
||||
|
||||
// if spec is not a valid file, assume inlining
|
||||
if _, err := os.Stat(args.Spec); os.IsNotExist(err) {
|
||||
raw = []byte(args.Spec)
|
||||
} else { // otherwise read it
|
||||
raw, err = os.ReadFile(args.Spec)
|
||||
|
||||
if err != nil {
|
||||
return yamlInput, fmt.Errorf("failed to read template: %w", errConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
// Render using the old plugin format
|
||||
p := toLegacyPlugin(args)
|
||||
yamlFile, err := template.RenderTrim(string(raw), p)
|
||||
if err != nil {
|
||||
return yamlInput, fmt.Errorf("can't render template: %w", err)
|
||||
}
|
||||
|
||||
// Modified from https://github.com/estesp/manifest-tool/blob/main/v2/cmd/manifest-tool/push.go
|
||||
err = yaml.Unmarshal([]byte(yamlFile), &yamlInput)
|
||||
if err != nil {
|
||||
return yamlInput, fmt.Errorf("can't unmarshal to yaml: %w", err)
|
||||
}
|
||||
|
||||
return yamlInput, nil
|
||||
}
|
||||
|
||||
func yamlFromArgs(args *Args) (types.YAMLInput, error) {
|
||||
// Modified from https://github.com/estesp/manifest-tool/blob/main/v2/cmd/manifest-tool/push.go
|
||||
srcImages := []types.ManifestEntry{}
|
||||
|
||||
for _, platform := range args.Platforms {
|
||||
osArchArr := strings.Split(platform, "/")
|
||||
if len(osArchArr) != 2 && len(osArchArr) != 3 {
|
||||
return types.YAMLInput{}, fmt.Errorf("platforms must be a string slice where one value is of the form 'os/arch': %w", errConfiguration)
|
||||
}
|
||||
variant := ""
|
||||
os, arch := osArchArr[0], osArchArr[1]
|
||||
if len(osArchArr) == 3 {
|
||||
variant = osArchArr[2]
|
||||
}
|
||||
srcImages = append(srcImages, types.ManifestEntry{
|
||||
Image: strings.Replace(strings.Replace(strings.Replace(args.Template, "ARCH", arch, 1), "OS", os, 1), "VARIANT", variant, 1),
|
||||
Platform: ocispec.Platform{
|
||||
OS: os,
|
||||
Architecture: arch,
|
||||
Variant: variant,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return types.YAMLInput{
|
||||
Image: args.Target,
|
||||
Tags: args.Tags,
|
||||
Manifests: srcImages,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2023, the Drone Plugins project authors.
|
||||
// Please see the AUTHORS file for details. All rights reserved.
|
||||
// Use of this source code is governed by an Apache 2.0 license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
//nolint:errcheck
|
||||
func writeCard(path string, card interface{}) {
|
||||
data, _ := json.Marshal(card)
|
||||
|
||||
switch {
|
||||
case path == "/dev/stdout":
|
||||
writeCardTo(os.Stdout, data)
|
||||
case path == "/dev/stderr":
|
||||
writeCardTo(os.Stderr, data)
|
||||
case path != "":
|
||||
os.WriteFile(path, data, 0o644) //nolint:gomnd,gosec
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
func writeCardTo(out io.Writer, data []byte) {
|
||||
encoded := base64.StdEncoding.EncodeToString(data)
|
||||
|
||||
io.WriteString(out, "\u001B]1338;")
|
||||
io.WriteString(out, encoded)
|
||||
io.WriteString(out, "\u001B]0m")
|
||||
io.WriteString(out, "\n")
|
||||
}
|
||||
Reference in New Issue
Block a user