feat: Add authenticated GitHub access and structured logging
This commit introduces two key improvements to the application: 1. **Authenticated GitHub API Access:** The GitHub client now uses a personal access token (PAT) from the `GITHUB_TOKEN` environment variable if it is available. This increases the rate limit for GitHub API requests, making the tool more robust for users who need to collect a large number of repositories. 2. **Structured Logging:** The application now uses the standard library's `slog` package for structured logging. A `--verbose` flag has been added to the root command to control the log level, allowing for more detailed output when needed. This makes the application's output more consistent and easier to parse.
This commit is contained in:
parent
4fc86ee175
commit
beaeb04f03
11 changed files with 90 additions and 48 deletions
14
cmd/all.go
14
cmd/all.go
|
|
@ -3,6 +3,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -19,26 +20,27 @@ var allCmd = &cobra.Command{
|
||||||
Long: `Collect all public repositories from a user or organization and store them in a DataNode.`,
|
Long: `Collect all public repositories from a user or organization and store them in a DataNode.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
log := cmd.Context().Value("logger").(*slog.Logger)
|
||||||
repos, err := github.GetPublicRepos(context.Background(), args[0])
|
repos, err := github.GetPublicRepos(context.Background(), args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
log.Error("failed to get public repos", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
outputDir, _ := cmd.Flags().GetString("output")
|
outputDir, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
for _, repoURL := range repos {
|
for _, repoURL := range repos {
|
||||||
fmt.Printf("Cloning %s...\n", repoURL)
|
log.Info("cloning repository", "url", repoURL)
|
||||||
|
|
||||||
dn, err := vcs.CloneGitRepository(repoURL)
|
dn, err := vcs.CloneGitRepository(repoURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error cloning %s: %s\n", repoURL, err)
|
log.Error("failed to clone repository", "url", repoURL, "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := dn.ToTar()
|
data, err := dn.ToTar()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error serializing DataNode for %s: %v\n", repoURL, err)
|
log.Error("failed to serialize datanode", "url", repoURL, "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,7 +48,7 @@ var allCmd = &cobra.Command{
|
||||||
outputFile := fmt.Sprintf("%s/%s.dat", outputDir, repoName)
|
outputFile := fmt.Sprintf("%s/%s.dat", outputDir, repoName)
|
||||||
err = os.WriteFile(outputFile, data, 0644)
|
err = os.WriteFile(outputFile, data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error writing DataNode for %s to file: %v\n", repoURL, err)
|
log.Error("failed to write datanode to file", "url", repoURL, "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +56,6 @@ var allCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(allCmd)
|
RootCmd.AddCommand(allCmd)
|
||||||
allCmd.PersistentFlags().String("output", ".", "Output directory for the DataNodes")
|
allCmd.PersistentFlags().String("output", ".", "Output directory for the DataNodes")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ var collectCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(collectCmd)
|
RootCmd.AddCommand(collectCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -23,6 +23,7 @@ var collectGithubReleaseCmd = &cobra.Command{
|
||||||
Long: `Download the latest release of a file from GitHub releases. If the file or URL has a version number, it will check for a higher version and download it if found.`,
|
Long: `Download the latest release of a file from GitHub releases. If the file or URL has a version number, it will check for a higher version and download it if found.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
log := cmd.Context().Value("logger").(*slog.Logger)
|
||||||
repoURL := args[0]
|
repoURL := args[0]
|
||||||
outputDir, _ := cmd.Flags().GetString("output")
|
outputDir, _ := cmd.Flags().GetString("output")
|
||||||
pack, _ := cmd.Flags().GetBool("pack")
|
pack, _ := cmd.Flags().GetBool("pack")
|
||||||
|
|
@ -31,25 +32,25 @@ var collectGithubReleaseCmd = &cobra.Command{
|
||||||
|
|
||||||
owner, repo, err := borg_github.ParseRepoFromURL(repoURL)
|
owner, repo, err := borg_github.ParseRepoFromURL(repoURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error parsing repository URL: %v\n", err)
|
log.Error("failed to parse repository url", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
release, err := borg_github.GetLatestRelease(owner, repo)
|
release, err := borg_github.GetLatestRelease(owner, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error getting latest release: %v\n", err)
|
log.Error("failed to get latest release", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Found latest release: %s\n", release.GetTagName())
|
log.Info("found latest release", "tag", release.GetTagName())
|
||||||
|
|
||||||
if version != "" {
|
if version != "" {
|
||||||
if !semver.IsValid(version) {
|
if !semver.IsValid(version) {
|
||||||
fmt.Printf("Invalid version string: %s\n", version)
|
log.Error("invalid version string", "version", version)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if semver.Compare(release.GetTagName(), version) <= 0 {
|
if semver.Compare(release.GetTagName(), version) <= 0 {
|
||||||
fmt.Printf("Latest release (%s) is not newer than the provided version (%s).\n", release.GetTagName(), version)
|
log.Info("latest release is not newer than the provided version", "latest", release.GetTagName(), "provided", version)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,24 +58,24 @@ var collectGithubReleaseCmd = &cobra.Command{
|
||||||
if pack {
|
if pack {
|
||||||
dn := datanode.New()
|
dn := datanode.New()
|
||||||
for _, asset := range release.Assets {
|
for _, asset := range release.Assets {
|
||||||
fmt.Printf("Downloading asset: %s\n", asset.GetName())
|
log.Info("downloading asset", "name", asset.GetName())
|
||||||
resp, err := http.Get(asset.GetBrowserDownloadURL())
|
resp, err := http.Get(asset.GetBrowserDownloadURL())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error downloading asset: %v\n", err)
|
log.Error("failed to download asset", "name", asset.GetName(), "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
_, err = io.Copy(&buf, resp.Body)
|
_, err = io.Copy(&buf, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error reading asset: %v\n", err)
|
log.Error("failed to read asset", "name", asset.GetName(), "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dn.AddData(asset.GetName(), buf.Bytes())
|
dn.AddData(asset.GetName(), buf.Bytes())
|
||||||
}
|
}
|
||||||
tar, err := dn.ToTar()
|
tar, err := dn.ToTar()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error creating DataNode: %v\n", err)
|
log.Error("failed to create datanode", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
outputFile := outputDir
|
outputFile := outputDir
|
||||||
|
|
@ -83,13 +84,13 @@ var collectGithubReleaseCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
err = os.WriteFile(outputFile, tar, 0644)
|
err = os.WriteFile(outputFile, tar, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error writing DataNode: %v\n", err)
|
log.Error("failed to write datanode", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("DataNode saved to %s\n", outputFile)
|
log.Info("datanode saved", "path", outputFile)
|
||||||
} else {
|
} else {
|
||||||
if len(release.Assets) == 0 {
|
if len(release.Assets) == 0 {
|
||||||
fmt.Println("No assets found in the latest release.")
|
log.Info("no assets found in the latest release")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var assetToDownload *gh.ReleaseAsset
|
var assetToDownload *gh.ReleaseAsset
|
||||||
|
|
@ -101,20 +102,20 @@ var collectGithubReleaseCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if assetToDownload == nil {
|
if assetToDownload == nil {
|
||||||
fmt.Printf("Asset '%s' not found in the latest release.\n", file)
|
log.Error("asset not found in the latest release", "asset", file)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assetToDownload = release.Assets[0]
|
assetToDownload = release.Assets[0]
|
||||||
}
|
}
|
||||||
outputPath := filepath.Join(outputDir, assetToDownload.GetName())
|
outputPath := filepath.Join(outputDir, assetToDownload.GetName())
|
||||||
fmt.Printf("Downloading asset: %s\n", assetToDownload.GetName())
|
log.Info("downloading asset", "name", assetToDownload.GetName())
|
||||||
err = borg_github.DownloadReleaseAsset(assetToDownload, outputPath)
|
err = borg_github.DownloadReleaseAsset(assetToDownload, outputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error downloading asset: %v\n", err)
|
log.Error("failed to download asset", "name", assetToDownload.GetName(), "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Asset downloaded to %s\n", outputPath)
|
log.Info("asset downloaded", "path", outputPath)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExecute(t *testing.T) {
|
func TestExecute(t *testing.T) {
|
||||||
// This test simply checks that the Execute function can be called without error.
|
// This test simply checks that the Execute function can be called without error.
|
||||||
// It doesn't actually test any of the application's functionality.
|
// It doesn't actually test any of the application's functionality.
|
||||||
rootCmd.SetArgs([]string{})
|
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
t.Cleanup(func() {
|
Level: slog.LevelDebug,
|
||||||
rootCmd.SetArgs(nil)
|
}))
|
||||||
})
|
if err := Execute(log); err != nil {
|
||||||
if err := Execute(); err != nil {
|
|
||||||
t.Errorf("Execute() failed: %v", err)
|
t.Errorf("Execute() failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
22
cmd/root.go
22
cmd/root.go
|
|
@ -1,11 +1,14 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// RootCmd represents the base command when called without any subcommands
|
||||||
var rootCmd = &cobra.Command{
|
var RootCmd = &cobra.Command{
|
||||||
Use: "borg-data-collector",
|
Use: "borg-data-collector",
|
||||||
Short: "A tool for collecting and managing data.",
|
Short: "A tool for collecting and managing data.",
|
||||||
Long: `Borg Data Collector is a command-line tool for cloning Git repositories,
|
Long: `Borg Data Collector is a command-line tool for cloning Git repositories,
|
||||||
|
|
@ -14,18 +17,11 @@ packaging their contents into a single file, and managing the data within.`,
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
func Execute() error {
|
func Execute(log *slog.Logger) error {
|
||||||
return rootCmd.Execute()
|
RootCmd.SetContext(context.WithValue(context.Background(), "logger", log))
|
||||||
|
return RootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Here you will define your flags and configuration settings.
|
RootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose logging")
|
||||||
// Cobra supports persistent flags, which, if defined here,
|
|
||||||
// will be global for your application.
|
|
||||||
|
|
||||||
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.borg-data-collector.yaml)")
|
|
||||||
|
|
||||||
// Cobra also supports local flags, which will only run
|
|
||||||
// when this action is called directly.
|
|
||||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,6 @@ var serveCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(serveCmd)
|
RootCmd.AddCommand(serveCmd)
|
||||||
serveCmd.PersistentFlags().String("port", "8080", "Port to serve the PWA on")
|
serveCmd.PersistentFlags().String("port", "8080", "Port to serve the PWA on")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/spf13/cobra v1.10.1
|
github.com/spf13/cobra v1.10.1
|
||||||
golang.org/x/mod v0.29.0
|
golang.org/x/mod v0.29.0
|
||||||
golang.org/x/net v0.46.0
|
golang.org/x/net v0.46.0
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -21,6 +22,7 @@ require (
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
|
@ -35,5 +37,7 @@ require (
|
||||||
golang.org/x/crypto v0.43.0 // indirect
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/term v0.36.0 // indirect
|
golang.org/x/term v0.36.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -37,6 +37,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
|
@ -108,6 +110,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -129,7 +132,10 @@ golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|
|
||||||
8
main.go
8
main.go
|
|
@ -1,15 +1,17 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/Snider/Borg/cmd"
|
"github.com/Snider/Borg/cmd"
|
||||||
|
"github.com/Snider/Borg/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := cmd.Execute(); err != nil {
|
verbose, _ := cmd.RootCmd.PersistentFlags().GetBool("verbose")
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
log := logger.New(verbose)
|
||||||
|
if err := cmd.Execute(log); err != nil {
|
||||||
|
log.Error("fatal error", "err", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repo struct {
|
type Repo struct {
|
||||||
|
|
@ -16,7 +19,19 @@ func GetPublicRepos(ctx context.Context, userOrOrg string) ([]string, error) {
|
||||||
return GetPublicReposWithAPIURL(ctx, "https://api.github.com", userOrOrg)
|
return GetPublicReposWithAPIURL(ctx, "https://api.github.com", userOrOrg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newAuthenticatedClient(ctx context.Context) *http.Client {
|
||||||
|
token := os.Getenv("GITHUB_TOKEN")
|
||||||
|
if token == "" {
|
||||||
|
return http.DefaultClient
|
||||||
|
}
|
||||||
|
ts := oauth2.StaticTokenSource(
|
||||||
|
&oauth2.Token{AccessToken: token},
|
||||||
|
)
|
||||||
|
return oauth2.NewClient(ctx, ts)
|
||||||
|
}
|
||||||
|
|
||||||
func GetPublicReposWithAPIURL(ctx context.Context, apiURL, userOrOrg string) ([]string, error) {
|
func GetPublicReposWithAPIURL(ctx context.Context, apiURL, userOrOrg string) ([]string, error) {
|
||||||
|
client := newAuthenticatedClient(ctx)
|
||||||
var allCloneURLs []string
|
var allCloneURLs []string
|
||||||
url := fmt.Sprintf("%s/users/%s/repos", apiURL, userOrOrg)
|
url := fmt.Sprintf("%s/users/%s/repos", apiURL, userOrOrg)
|
||||||
|
|
||||||
|
|
@ -26,7 +41,7 @@ func GetPublicReposWithAPIURL(ctx context.Context, apiURL, userOrOrg string) ([]
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "Borg-Data-Collector")
|
req.Header.Set("User-Agent", "Borg-Data-Collector")
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
pkg/logger/logger.go
Normal file
16
pkg/logger/logger.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(verbose bool) *slog.Logger {
|
||||||
|
level := slog.LevelInfo
|
||||||
|
if verbose {
|
||||||
|
level = slog.LevelDebug
|
||||||
|
}
|
||||||
|
return slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
|
Level: level,
|
||||||
|
}))
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue