github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/release/release.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  	"os"
    10  	"runtime"
    11  	"strings"
    12  
    13  	gh "github.com/keybase/client/go/release/github"
    14  	"github.com/keybase/client/go/release/update"
    15  	"github.com/keybase/client/go/release/version"
    16  	"github.com/keybase/client/go/release/winbuild"
    17  	"gopkg.in/alecthomas/kingpin.v2"
    18  )
    19  
    20  func githubToken(required bool) string {
    21  	token := os.Getenv("GITHUB_TOKEN")
    22  	if token == "" && required {
    23  		log.Fatal("No GITHUB_TOKEN set")
    24  	}
    25  	return token
    26  }
    27  
    28  func keybaseToken(required bool) string {
    29  	token := os.Getenv("KEYBASE_TOKEN")
    30  	if token == "" && required {
    31  		log.Fatal("No KEYBASE_TOKEN set")
    32  	}
    33  	return token
    34  }
    35  
    36  func tag(version string) string {
    37  	return fmt.Sprintf("v%s", version)
    38  }
    39  
    40  var (
    41  	app               = kingpin.New("release", "Release tool for build and release scripts")
    42  	latestVersionCmd  = app.Command("latest-version", "Get latest version of a Github repo")
    43  	latestVersionUser = latestVersionCmd.Flag("user", "Github user").Required().String()
    44  	latestVersionRepo = latestVersionCmd.Flag("repo", "Repository name").Required().String()
    45  
    46  	platformCmd = app.Command("platform", "Get the OS platform name")
    47  
    48  	urlCmd     = app.Command("url", "Get the github release URL for a repo")
    49  	urlUser    = urlCmd.Flag("user", "Github user").Required().String()
    50  	urlRepo    = urlCmd.Flag("repo", "Repository name").Required().String()
    51  	urlVersion = urlCmd.Flag("version", "Version").Required().String()
    52  
    53  	createCmd     = app.Command("create", "Create a Github release")
    54  	createRepo    = createCmd.Flag("repo", "Repository name").Required().String()
    55  	createVersion = createCmd.Flag("version", "Version").Required().String()
    56  
    57  	uploadCmd     = app.Command("upload", "Upload a file to a Github release")
    58  	uploadRepo    = uploadCmd.Flag("repo", "Repository name").Required().String()
    59  	uploadVersion = uploadCmd.Flag("version", "Version").Required().String()
    60  	uploadSrc     = uploadCmd.Flag("src", "Source file").Required().ExistingFile()
    61  	uploadDest    = uploadCmd.Flag("dest", "Destination file").String()
    62  
    63  	downloadCmd     = app.Command("download", "Download a file from a Github release")
    64  	downloadRepo    = downloadCmd.Flag("repo", "Repository name").Required().String()
    65  	downloadVersion = downloadCmd.Flag("version", "Version").Required().String()
    66  	downloadSrc     = downloadCmd.Flag("src", "Source file").Required().ExistingFile()
    67  
    68  	updateJSONCmd         = app.Command("update-json", "Generate update.json file for updater")
    69  	updateJSONVersion     = updateJSONCmd.Flag("version", "Version").Required().String()
    70  	updateJSONSrc         = updateJSONCmd.Flag("src", "Source file").ExistingFile()
    71  	updateJSONURI         = updateJSONCmd.Flag("uri", "URI for location of files").URL()
    72  	updateJSONSignature   = updateJSONCmd.Flag("signature", "Signature file").ExistingFile()
    73  	updateJSONDescription = updateJSONCmd.Flag("description", "Description file").ExistingFile()
    74  	updateJSONProps       = updateJSONCmd.Flag("prop", "Properties to include").Strings()
    75  
    76  	indexHTMLCmd        = app.Command("index-html", "Generate index.html for s3 bucket")
    77  	indexHTMLBucketName = indexHTMLCmd.Flag("bucket-name", "Bucket name to index").Required().String()
    78  	indexHTMLPrefixes   = indexHTMLCmd.Flag("prefixes", "Prefixes to include (comma-separated)").Required().String()
    79  	indexHTMLSuffix     = indexHTMLCmd.Flag("suffix", "Suffix of files").String()
    80  	indexHTMLDest       = indexHTMLCmd.Flag("dest", "Write to file").String()
    81  	indexHTMLUpload     = indexHTMLCmd.Flag("upload", "Upload to S3").String()
    82  
    83  	parseVersionCmd    = app.Command("version-parse", "Parse a sematic version string")
    84  	parseVersionString = parseVersionCmd.Arg("version", "Semantic version to parse").Required().String()
    85  
    86  	promoteReleasesCmd        = app.Command("promote-releases", "Promote releases")
    87  	promoteReleasesBucketName = promoteReleasesCmd.Flag("bucket-name", "Bucket name to use").Required().String()
    88  	promoteReleasesPlatform   = promoteReleasesCmd.Flag("platform", "Platform (darwin, linux, windows)").Required().String()
    89  
    90  	promoteAReleaseCmd        = app.Command("promote-a-release", "Promote a specific release")
    91  	releaseToPromote          = promoteAReleaseCmd.Flag("release", "Specific release to promote to public").Required().String()
    92  	promoteAReleaseBucketName = promoteAReleaseCmd.Flag("bucket-name", "Bucket name to use").Required().String()
    93  	promoteAReleasePlatform   = promoteAReleaseCmd.Flag("platform", "Platform (darwin, linux, windows)").Required().String()
    94  	promoteAReleaseDryRun     = promoteAReleaseCmd.Flag("dry-run", "Announce what would be done without doing it").Bool()
    95  
    96  	brokenReleaseCmd          = app.Command("broken-release", "Mark a release as broken")
    97  	brokenReleaseName         = brokenReleaseCmd.Flag("release", "Release to mark as broken").Required().String()
    98  	brokenReleaseBucketName   = brokenReleaseCmd.Flag("bucket-name", "Bucket name to use").Required().String()
    99  	brokenReleasePlatformName = brokenReleaseCmd.Flag("platform", "Platform (darwin, linux, windows)").Required().String()
   100  
   101  	promoteTestReleasesCmd        = app.Command("promote-test-releases", "Promote test releases")
   102  	promoteTestReleasesBucketName = promoteTestReleasesCmd.Flag("bucket-name", "Bucket name to use").Required().String()
   103  	promoteTestReleasesPlatform   = promoteTestReleasesCmd.Flag("platform", "Platform (darwin, linux, windows)").Required().String()
   104  	promoteTestReleasesRelease    = promoteTestReleasesCmd.Flag("release", "Specific release to promote to test").String()
   105  
   106  	updatesReportCmd        = app.Command("updates-report", "Summary of updates/releases")
   107  	updatesReportBucketName = updatesReportCmd.Flag("bucket-name", "Bucket name to use").Required().String()
   108  
   109  	saveLogCmd        = app.Command("save-log", "Save log")
   110  	saveLogBucketName = saveLogCmd.Flag("bucket-name", "Bucket name to use").Required().String()
   111  	saveLogPath       = saveLogCmd.Flag("path", "File to save").Required().String()
   112  	saveLogNoErr      = saveLogCmd.Flag("noerr", "No error status on failure").Bool()
   113  	saveLogMaxSize    = saveLogCmd.Flag("maxsize", "Max size, (default 102400)").Default("102400").Int64()
   114  
   115  	latestCommitCmd      = app.Command("latest-commit", "Latests commit we can use to safely build from")
   116  	latestCommitRepo     = latestCommitCmd.Flag("repo", "Repository name").Required().String()
   117  	latestCommitContexts = latestCommitCmd.Flag("context", "Context to check for success").Required().Strings()
   118  
   119  	waitForCICmd      = app.Command("wait-ci", "Waits on a the latest commit being successful in CI")
   120  	waitForCIRepo     = waitForCICmd.Flag("repo", "Repository name").Required().String()
   121  	waitForCICommit   = waitForCICmd.Flag("commit", "Commit").Required().String()
   122  	waitForCIContexts = waitForCICmd.Flag("context", "Context to check for success").Required().Strings()
   123  	waitForCIDelay    = waitForCICmd.Flag("delay", "Delay between checks").Default("1m").Duration()
   124  	waitForCITimeout  = waitForCICmd.Flag("timeout", "Delay between checks").Default("1h").Duration()
   125  
   126  	announceBuildCmd      = app.Command("announce-build", "Inform the API server of the existence of a new build")
   127  	announceBuildA        = announceBuildCmd.Flag("build-a", "The first of the two IDs comprising the new build").Required().String()
   128  	announceBuildB        = announceBuildCmd.Flag("build-b", "The second of the two IDs comprising the new build").Required().String()
   129  	announceBuildPlatform = announceBuildCmd.Flag("platform", "Platform (darwin, linux, windows)").Required().String()
   130  
   131  	setBuildInTestingCmd        = app.Command("set-build-in-testing", "Enroll or unenroll a build in smoketesting")
   132  	setBuildInTestingA          = setBuildInTestingCmd.Flag("build-a", "The first build's ID").Required().String()
   133  	setBuildInTestingPlatform   = setBuildInTestingCmd.Flag("platform", "Platform (darwin, linux, windows)").Required().String()
   134  	setBuildInTestingEnable     = setBuildInTestingCmd.Flag("enable", "Enroll the build in smoketesting (boolish string)").Required().String()
   135  	setBuildInTestingMaxTesters = setBuildInTestingCmd.Flag("max-testers", "Max number of testers for this build").Required().Int()
   136  
   137  	ciStatusesCmd    = app.Command("ci-statuses", "List statuses for CI")
   138  	ciStatusesRepo   = ciStatusesCmd.Flag("repo", "Repository name").Required().String()
   139  	ciStatusesCommit = ciStatusesCmd.Flag("commit", "Commit").Required().String()
   140  
   141  	getWinBuildNumberCmd      = app.Command("winbuildnumber", "Atomically retrieve and increment build number for given version")
   142  	getWinBuildNumberVersion  = getWinBuildNumberCmd.Flag("version", "Major version, e.g. 1.0.30").Required().String()
   143  	getWinBuildNumberBotID    = getWinBuildNumberCmd.Flag("botid", "bot ID").Default("1").String()
   144  	getWinBuildNumberPlatform = getWinBuildNumberCmd.Flag("platform", "platform").Default("1").String()
   145  )
   146  
   147  func main() {
   148  	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
   149  	case latestVersionCmd.FullCommand():
   150  		tag, err := gh.LatestTag(*latestVersionUser, *latestVersionRepo, githubToken(false))
   151  		if err != nil {
   152  			log.Fatal(err)
   153  		}
   154  		if strings.HasPrefix(tag.Name, "v") {
   155  			version := tag.Name[1:]
   156  			fmt.Printf("%s", version)
   157  		}
   158  	case platformCmd.FullCommand():
   159  		fmt.Printf("%s", runtime.GOOS)
   160  
   161  	case urlCmd.FullCommand():
   162  		release, err := gh.ReleaseOfTag(*urlUser, *urlRepo, tag(*urlVersion), githubToken(false))
   163  		if _, ok := err.(*gh.ErrNotFound); ok {
   164  			// No release
   165  		} else if err != nil {
   166  			log.Fatal(err)
   167  		} else {
   168  			fmt.Printf("%s", release.URL)
   169  		}
   170  	case createCmd.FullCommand():
   171  		err := gh.CreateRelease(githubToken(true), *createRepo, tag(*createVersion), tag(*createVersion))
   172  		if err != nil {
   173  			log.Fatal(err)
   174  		}
   175  	case uploadCmd.FullCommand():
   176  		if *uploadDest == "" {
   177  			uploadDest = uploadSrc
   178  		}
   179  		log.Printf("Uploading %s as %s (%s)", *uploadSrc, *uploadDest, tag(*uploadVersion))
   180  		err := gh.Upload(githubToken(true), *uploadRepo, tag(*uploadVersion), *uploadDest, *uploadSrc)
   181  		if err != nil {
   182  			log.Fatal(err)
   183  		}
   184  	case downloadCmd.FullCommand():
   185  		defaultSrc := fmt.Sprintf("keybase-%s-%s.tgz", *downloadVersion, runtime.GOOS)
   186  		if *downloadSrc == "" {
   187  			downloadSrc = &defaultSrc
   188  		}
   189  		log.Printf("Downloading %s (%s)", *downloadSrc, tag(*downloadVersion))
   190  		err := gh.DownloadAsset(githubToken(false), *downloadRepo, tag(*downloadVersion), *downloadSrc)
   191  		if err != nil {
   192  			log.Fatal(err)
   193  		}
   194  	case updateJSONCmd.FullCommand():
   195  		out, err := update.EncodeJSON(*updateJSONVersion, tag(*updateJSONVersion), *updateJSONDescription, *updateJSONProps, *updateJSONSrc, *updateJSONURI, *updateJSONSignature)
   196  		if err != nil {
   197  			log.Fatal(err)
   198  		}
   199  		fmt.Fprintf(os.Stdout, "%s\n", out)
   200  	case indexHTMLCmd.FullCommand():
   201  		err := update.WriteHTML(*indexHTMLBucketName, *indexHTMLPrefixes, *indexHTMLSuffix, *indexHTMLDest, *indexHTMLUpload)
   202  		if err != nil {
   203  			log.Fatal(err)
   204  		}
   205  	case parseVersionCmd.FullCommand():
   206  		versionFull, versionShort, date, commit, err := version.Parse(*parseVersionString)
   207  		if err != nil {
   208  			log.Fatal(err)
   209  		}
   210  		log.Printf("%s\n", versionFull)
   211  		log.Printf("%s\n", versionShort)
   212  		log.Printf("%s\n", date)
   213  		log.Printf("%s\n", commit)
   214  	case promoteReleasesCmd.FullCommand():
   215  		const dryRun bool = false
   216  		release, err := update.PromoteReleases(*promoteReleasesBucketName, *promoteReleasesPlatform)
   217  		if err != nil {
   218  			log.Fatal(err)
   219  		}
   220  		err = update.CopyLatest(*promoteReleasesBucketName, *promoteReleasesPlatform, dryRun)
   221  		if err != nil {
   222  			log.Fatal(err)
   223  		}
   224  		if release == nil {
   225  			log.Print("Not notifying API server of release")
   226  		} else {
   227  			releaseTime, err := update.KBWebPromote(keybaseToken(true), release.Version, *promoteReleasesPlatform, dryRun)
   228  			if err != nil {
   229  				log.Fatal(err)
   230  			}
   231  			log.Printf("Release time set to %v for build %v", releaseTime, release.Version)
   232  		}
   233  	case promoteAReleaseCmd.FullCommand():
   234  		release, err := update.PromoteARelease(*releaseToPromote, *promoteAReleaseBucketName, *promoteAReleasePlatform, *promoteAReleaseDryRun)
   235  		if err != nil {
   236  			log.Fatal(err)
   237  		}
   238  		err = update.CopyLatest(*promoteAReleaseBucketName, *promoteAReleasePlatform, *promoteAReleaseDryRun)
   239  		if err != nil {
   240  			log.Fatal(err)
   241  		}
   242  		if release == nil {
   243  			log.Fatal("No release found")
   244  		} else {
   245  			_, err := update.KBWebPromote(keybaseToken(!*promoteAReleaseDryRun), release.Version, *promoteAReleasePlatform, *promoteAReleaseDryRun)
   246  			if err != nil {
   247  				log.Fatal(err)
   248  			}
   249  		}
   250  	case promoteTestReleasesCmd.FullCommand():
   251  		err := update.PromoteTestReleases(*promoteTestReleasesBucketName, *promoteTestReleasesPlatform, *promoteTestReleasesRelease)
   252  		if err != nil {
   253  			log.Fatal(err)
   254  		}
   255  	case updatesReportCmd.FullCommand():
   256  		err := update.Report(*updatesReportBucketName, os.Stdout)
   257  		if err != nil {
   258  			log.Fatal(err)
   259  		}
   260  	case brokenReleaseCmd.FullCommand():
   261  		_, err := update.ReleaseBroken(*brokenReleaseName, *brokenReleaseBucketName, *brokenReleasePlatformName)
   262  		if err != nil {
   263  			log.Fatal(err)
   264  		}
   265  	case saveLogCmd.FullCommand():
   266  
   267  		url, err := update.SaveLog(*saveLogBucketName, *saveLogPath, *saveLogMaxSize)
   268  		if err != nil {
   269  			if *saveLogNoErr {
   270  				log.Printf("%s", err)
   271  				return
   272  			}
   273  			log.Fatal(err)
   274  		}
   275  		fmt.Fprintf(os.Stdout, "%s\n", url)
   276  	case latestCommitCmd.FullCommand():
   277  		commit, err := gh.LatestCommit(githubToken(true), *latestCommitRepo, *latestCommitContexts)
   278  		if err != nil {
   279  			log.Fatal(err)
   280  		}
   281  		fmt.Printf("%s", commit.SHA)
   282  	case waitForCICmd.FullCommand():
   283  		err := gh.WaitForCI(githubToken(true), *waitForCIRepo, *waitForCICommit, *waitForCIContexts, *waitForCIDelay, *waitForCITimeout)
   284  		if err != nil {
   285  			log.Fatal(err)
   286  		}
   287  	case announceBuildCmd.FullCommand():
   288  		err := update.AnnounceBuild(keybaseToken(true), *announceBuildA, *announceBuildB, *announceBuildPlatform)
   289  		if err != nil {
   290  			log.Fatal(err)
   291  		}
   292  	case setBuildInTestingCmd.FullCommand():
   293  		err := update.SetBuildInTesting(keybaseToken(true), *setBuildInTestingA, *setBuildInTestingPlatform, *setBuildInTestingEnable, *setBuildInTestingMaxTesters)
   294  		if err != nil {
   295  			log.Fatal(err)
   296  		}
   297  	case ciStatusesCmd.FullCommand():
   298  		err := gh.CIStatuses(githubToken(true), *ciStatusesRepo, *ciStatusesCommit)
   299  		if err != nil {
   300  			log.Fatal(err)
   301  		}
   302  	case getWinBuildNumberCmd.FullCommand():
   303  		err := winbuild.GetNextBuildNumber(keybaseToken(true), *getWinBuildNumberVersion, *getWinBuildNumberBotID, *getWinBuildNumberPlatform)
   304  		if err != nil {
   305  			log.Fatal(err)
   306  		}
   307  	}
   308  
   309  }