Skip to content

Commit

Permalink
Conditionally publish deb packages (#9496)
Browse files Browse the repository at this point in the history
This patch makes a couple changes:

  1. deb archives are not published to apt if they're not the latest
     release ever
  2. both rpm and deb archives are no longer published to yum / apt if
     they contain any pre-release indicator or build metadata
  3. nothing is published if the commit isn't tagged.

Contributes to #8166

(cherry picked from commit 8540533)
  • Loading branch information
wadells committed Jan 20, 2022
1 parent eb95301 commit 3f735c1
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 19 deletions.
42 changes: 23 additions & 19 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4824,6 +4824,11 @@ clone:
disable: true

steps:
- name: Check if commit is tagged
image: alpine
commands:
- "[ -n ${DRONE_TAG} ] || (echo 'DRONE_TAG is not set. Is the commit tagged?' && exit 1)"

- name: Download artifacts from S3
image: amazon/aws-cli
environment:
Expand Down Expand Up @@ -4977,25 +4982,14 @@ steps:
# NOTE: all mandatory steps for a release promotion need to go BEFORE this
# step, as there is a chance that everything afterwards will be skipped.
#
# this step exits early and skips all remanining steps in the pipeline if the
# tag looks like a pre-release, to avoid publishing RPMs for pre-release builds.
- name: Determine whether RPM/DEB packages should be published to repos
image: docker
# this step exits early and skips all remaining steps in the pipeline if the
# tag looks like a pre-release, to avoid pushing pre-release RPMs and DEBs to
# our yum / apt repos.
- name: Check if tag is prerelease
image: golang:1.17-alpine
commands:
- |
if [ "${DRONE_REPO}" != "gravitational/teleport" ]; then
echo "---> Not publishing ${DRONE_REPO} packages to repos"
exit 78
fi
# length will be 0 after filtering if this is a pre-release, >0 otherwise
FILTERED_TAG_LENGTH=$(echo ${DRONE_TAG} | egrep -v '(alpha|beta|dev|rc)' | wc -c)
if [ $$FILTERED_TAG_LENGTH -eq 0 ]; then
echo "---> ${DRONE_TAG} looks like a pre-release, not publishing packages to repos"
# exit pipeline early with success status
exit 78
else
echo "---> Publishing packages to repos for ${DRONE_TAG}"
fi
- cd build.assets/version-check
- go run . -tag ${DRONE_TAG} -check prerelease || (echo '---> Not publishing ${DRONE_REPO} packages to RPM and DEB repos' && exit 78)

- name: "RPM: Download RPM repository"
image: amazon/aws-cli
Expand Down Expand Up @@ -5077,6 +5071,16 @@ steps:
commands:
- aws s3 sync /rpmrepo/teleport/ s3://$AWS_S3_BUCKET/teleport/

# This step skips all remaining steps in the pipeline if the tag
# is not the highest semver *ever* released, to avoid publishing DEBs
# that would cause apt users to downgrade. For more info see:
# https://github.com/gravitational/teleport/issues/8166
- name: Check if tag is latest
image: golang:1.17-alpine
commands:
- cd build.assets/version-check
- go run . -tag ${DRONE_TAG} -check latest || (echo '---> Not publishing ${DRONE_REPO} packages to DEB repo' && exit 78)

- name: Download DEB repo contents
image: amazon/aws-cli
environment:
Expand Down Expand Up @@ -5187,6 +5191,6 @@ volumes:
name: drone-s3-debrepo-pvc
---
kind: signature
hmac: 5d861c7d8f933ddfe0aa50d83324c5d141445d9ea76e441d6e28afc69c1dad80
hmac: 0d025b4aadc93900485333d2e94bc9f9334b977e5b5f99c4d33d3aef63633ec8

...
17 changes: 17 additions & 0 deletions build.assets/version-check/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module github.com/gravitational/teleport/build.assets/version-check

go 1.17

require github.com/google/go-github/v41 v41.0.0

require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/gravitational/trace v1.1.15 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
)
40 changes: 40 additions & 0 deletions build.assets/version-check/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
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/gravitational/trace v1.1.15 h1:dfaFcARt110nCX6RSvrcRUbvRawEYAasXyCqnhXo0Xg=
github.com/gravitational/trace v1.1.15/go.mod h1:RvdOUHE4SHqR3oXlFFKnGzms8a5dugHygGw1bqDstYI=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
151 changes: 151 additions & 0 deletions build.assets/version-check/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
Copyright 2022 Gravitational, Inc.
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.
*/

// Command version-check validates that a tag is not a prerelease
// or that it is the latest version ever. version-check exits non-zero
// if tag fails this check. This allows us to avoid updating "latest"
// packages or tags when publishing releases for older branches.
package main

import (
"context"
"flag"
"log"
"sort"
"strings"
"time"

"golang.org/x/mod/semver"

"github.com/gravitational/trace"

go_github "github.com/google/go-github/v41/github"
)

func main() {
tag, check, err := parseFlags()
if err != nil {
log.Fatalf("Failed to parse flags: %v.", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

switch check {
case "latest":
err = checkLatest(ctx, tag, newGitHub())
case "prerelease":
err = checkPrerelease(tag)
default:
log.Fatalf("invalid check: %v", check)
}

if err != nil {
log.Fatalf("Check failed: %v.", err)
}
}

func parseFlags() (string, string, error) {
tag := flag.String("tag", "", "tag to validate")
check := flag.String("check", "", "check to run [latest, prerelease]")
flag.Parse()

if *tag == "" {
return "", "", trace.BadParameter("tag missing")
}
if *check == "" {
return "", "", trace.BadParameter("check missing")
}
switch *check {
case "latest", "prerelease":
default:
return "", "", trace.BadParameter("invalid check: %v", *check)
}

return *tag, *check, nil
}

func checkLatest(ctx context.Context, tag string, gh github) error {
releases, err := gh.ListReleases(ctx, "gravitational", "teleport")
if err != nil {
return trace.Wrap(err)
}
sort.SliceStable(releases, func(i int, j int) bool {
return releases[i] > releases[j]
})
if len(releases) == 0 {
return trace.BadParameter("failed to find any releases on GitHub")
}

if semver.Compare(tag, releases[0]) <= 0 {
return trace.BadParameter("found newer version of release, not releasing. Latest release: %v, tag: %v", releases[0], tag)
}

return nil
}

func checkPrerelease(tag string) error {
if semver.Prerelease(tag) != "" { // https://semver.org/#spec-item-9
return trace.BadParameter("version is pre-release: %v", tag)
}
if strings.Contains(tag, "+") { // https://semver.org/#spec-item-10
return trace.BadParameter("version contains build metadata: %v", tag)
}
return nil
}

type github interface {
ListReleases(ctx context.Context, organization string, repository string) ([]string, error)
}

type ghClient struct {
client *go_github.Client
}

func newGitHub() *ghClient {
return &ghClient{
client: go_github.NewClient(nil),
}
}

func (c *ghClient) ListReleases(ctx context.Context, organization string, repository string) ([]string, error) {
var releases []string

opt := &go_github.ListOptions{
Page: 0,
PerPage: 100,
}
for n := 0; n < 100; n++ {
page, resp, err := c.client.Repositories.ListReleases(ctx,
organization,
repository,
opt)
if err != nil {
return nil, trace.Wrap(err)
}

for _, p := range page {
releases = append(releases, p.GetTagName())
}

if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}

return releases, nil
}
Loading

0 comments on commit 3f735c1

Please sign in to comment.