diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e28efc9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + open-pull-requests-limit: 30 + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..650e247 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '32 8 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..55c34b1 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,37 @@ +name: release + +on: + push: + tags: + - '*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.19 + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.PRIVATE_KEY }} + repository: sunggun-yu/homebrew-tap + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..6403237 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,25 @@ +name: test + +on: + push: + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.19 + - name: Run go mod tidy + run: go mod tidy + - name: Run Test + run: go test ./... -coverprofile=coverage.out -covermode=atomic + - name: Upload coverage to Codecov + run: bash <(curl -s https://codecov.io/bash) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..763a6cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# IDEs +.idea +.vscode + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +bin/* +!bin/.keep + +dist/* +!dist/.keep + +# exclude generate secret file +pkg/embeded/secrets + +# Dependency directories (remove the comment below to include it) +vendor/ + +# custom +backup +.history +*.token +*.pem diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..36081b8 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,60 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +project_name: gh-app-access-token + +before: + hooks: + - go mod tidy +builds: + - env: + - CGO_ENABLED=0 + binary: "{{ .ProjectName }}" + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + goarm: + - "6" + - "7" + ignore: + - goos: windows + goarch: arm64 +archives: + - format: tar.gz + wrap_in_directory: "true" + format_overrides: + - goos: windows + format: zip +checksum: + name_template: 'checksums.txt' + algorithm: sha256 +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + - "README" + - "Merge pull request" + - "Merge branch" + +brews: + - name: "gh-app-access-token" + tap: + owner: sunggun-yu + name: homebrew-tap + branch: main + description: "Cli wrapper to set profile based environment variables for your command line execution" + homepage: https://github.com/sunggun-yu/gh-app-access-token-cli + folder: Formula + download_strategy: CurlDownloadStrategy + install: | + bin.install "{{ .ProjectName }}" + test: | + system "#{bin}/{{ .ProjectName }} -v" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0114d52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Sunggun Yu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..23a0482 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# gh-app-access-token-cli + +Simple Cli tool for operating Github App Installation access token + +## Installation + +brew: + +```bash +brew install sunggun-yu/tap/gh-app-access-token +``` + +go install: + +```bash +go install github.com/sunggun-yu/gh-app-access-token@ +``` + +## Usage + +### Generate a Github App access token + +```bash +# generate the Github App access token +gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key [private-key-file-path] + +# generate the Github App access token with file in HOME +gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key $HOME/private-key.pem + +# generate the Github App access token with file in HOME +gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key ~/private-key.pem + +# generate the Github App access token with text in private key file passed into stdin +cat [private-key-file-path] | gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key - + +# generate the Github App access token with private key text passed into stdin +echo "private-key-text" | gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key - +``` + +>⚠️ Note/Warning +> +> it keeps waiting(hang) if there is no stdin. + +### Revoke the Github App access token + +```bash +# revoke token in argument +gh-app-access-token-cli revoke [access token string] + +# revoke the token passed into stdin +cat [access-token-file] | gh-app-access-token-cli revoke - + +# revoke the token passed into stdin +echo "access-token-value" | gh-app-access-token-cli revoke - +``` + +>⚠️ Note/Warning +> +> it keeps waiting(hang) if there is no stdin. diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000..9d60d42 --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,79 @@ +/* +Copyright © 2022 Sunggun Yu +*/ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/sunggun-yu/gh-app-access-token-cli/internal/utils" + "github.com/sunggun-yu/gh-app-access-token-cli/pkg/installation" +) + +// flags struct for show command +type generateCmdFlags struct { + appID int64 + installationID int64 + privateKeyFile string +} + +// generateCmd represents the generate command +func generateCmd() *cobra.Command { + var flags generateCmdFlags + + cmd := &cobra.Command{ + Use: "generate", + Short: "Generate the Github App Installation access token", + Aliases: []string{"gen"}, + Args: cobra.NoArgs, + Example: ` # generate the Github App access token + gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key [private-key-file-path] + + # generate the Github App access token with text in private key file passed into stdin + cat [private-key-file-path] | gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key - + + # generate the Github App access token with private key text passed into stdin + echo "private-key-text" | gh-app-access-token generate \ + --app-id [app-id] \ + --installation-id [installation-id] \ + --private-key -`, + RunE: func(cmd *cobra.Command, args []string) error { + var privateKey []byte + if flags.privateKeyFile == "-" { + // read private key from stdin if flag value is `-` + privateKey = []byte(utils.ReadInOrStdin(cmd)) + } else { + privateKey, _ = os.ReadFile(flags.privateKeyFile) + } + + token, err := installation.GenerateAccessToken(cmd.Context(), flags.appID, flags.installationID, privateKey) + if err != nil { + return err + } + fmt.Printf("%s", strings.TrimSpace(token)) + return nil + }, + } + + cmd.Flags().Int64VarP(&flags.appID, "app-id", "a", 0, "The unique identifier of the Github App") + cmd.Flags().Int64VarP(&flags.installationID, "installation-id", "i", 0, "The unique identifier of the installation") + cmd.Flags().StringVarP(&flags.privateKeyFile, "private-key", "f", "", "The private key file path of Github App") + + cmd.MarkFlagRequired("app-id") + cmd.MarkFlagRequired("installation-id") + cmd.MarkFlagRequired("private-key") + return cmd +} + +func init() { + rootCmd.AddCommand(generateCmd()) +} diff --git a/cmd/revoke.go b/cmd/revoke.go new file mode 100644 index 0000000..da19773 --- /dev/null +++ b/cmd/revoke.go @@ -0,0 +1,45 @@ +/* +Copyright © 2022 Sunggun Yu +*/ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/sunggun-yu/gh-app-access-token-cli/internal/utils" + "github.com/sunggun-yu/gh-app-access-token-cli/pkg/installation" +) + +// revokeCmd func represents the revoke command +func revokeCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "revoke [access token string to revoke]", + Short: "Revoke an access token", + Aliases: []string{"del"}, + Example: ` # revoke token in argument + gh-app-access-token-cli revoke [access token string] + + # revoke the token passed into stdin + cat [access-token-file] | gh-app-access-token-cli revoke - + + # revoke the token passed into stdin + echo "access-token-value" | gh-app-access-token-cli revoke -`, + Args: cobra.MatchAll( + cobra.MinimumNArgs(1), + cobra.MaximumNArgs(1), + ), + RunE: func(cmd *cobra.Command, args []string) error { + token := args[0] + // read token from stdin if arg is `-` + if args[0] == "-" { + token = utils.ReadInOrStdin(cmd) + } + err := installation.RevokeAccessToken(cmd.Context(), token) + return err + }, + } + return cmd +} + +func init() { + rootCmd.AddCommand(revokeCmd()) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..e21bbd6 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,42 @@ +/* +Copyright © 2022 Sunggun Yu +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "gh-app-access-token", + Short: "A simple cli tool for operating Github App Installation access token", +} + +// 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. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +// Set the version of cmd +func SetVersion(version string) { + rootCmd.Version = version +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.gh-app-access-token-cli.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") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7cff89c --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module github.com/sunggun-yu/gh-app-access-token-cli + +go 1.19 + +require ( + github.com/bradleyfalzon/ghinstallation/v2 v2.1.0 + github.com/google/go-github/v48 v48.0.0 + github.com/spf13/cobra v1.6.0 + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be +) + +require ( + github.com/golang-jwt/jwt/v4 v4.4.1 // indirect + github.com/golang/protobuf v1.3.2 // indirect + github.com/google/go-github/v45 v45.2.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect + google.golang.org/appengine v1.6.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..68336f7 --- /dev/null +++ b/go.sum @@ -0,0 +1,45 @@ +github.com/bradleyfalzon/ghinstallation/v2 v2.1.0 h1:5+NghM1Zred9Z078QEZtm28G/kfDfZN/92gkDlLwGVA= +github.com/bradleyfalzon/ghinstallation/v2 v2.1.0/go.mod h1:Xg3xPRN5Mcq6GDqeUVhFbjEWMb4JHCyWEeeBGEYQoTU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +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.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= +github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= +github.com/google/go-github/v48 v48.0.0 h1:9H5fWVXFK6ZsRriyPbjtnFAkJnoj0WKFtTYfpCRrTm8= +github.com/google/go-github/v48 v48.0.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y= +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/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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/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 h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/utils/file.go b/internal/utils/file.go new file mode 100644 index 0000000..112ddf9 --- /dev/null +++ b/internal/utils/file.go @@ -0,0 +1,28 @@ +package utils + +import ( + "os" + "strings" +) + +// FilePath gets file path with expanding home dir `~`, $HOME. also, $PWD +func FilePath(f string) string { + homeDir := func(value string) string { + switch value { + case "HOME": + userHome, _ := os.UserHomeDir() + return userHome + case "PWD": + userHome, _ := os.Getwd() + return userHome + } + return "" + } + + // replace tilde ~ to $HOME and use os.Expand + if strings.HasPrefix(f, "~") { + f = strings.Replace(f, "~", "$HOME", 1) + } + + return os.Expand(f, homeDir) +} diff --git a/internal/utils/stdin.go b/internal/utils/stdin.go new file mode 100644 index 0000000..191be58 --- /dev/null +++ b/internal/utils/stdin.go @@ -0,0 +1,19 @@ +package utils + +import ( + "bytes" + "io" + "strings" + + "github.com/spf13/cobra" +) + +// ReadInOrStdin reads string from command ReadInOrStdin +func ReadInOrStdin(cmd *cobra.Command) string { + var inputReader io.Reader = cmd.InOrStdin() + buf := new(bytes.Buffer) + buf.ReadFrom(inputReader) + // NOTE: it keep waiting if there is no stdin. same like kubectl apply -f with no input + // TODO: set timeout to avoid hang? + return strings.TrimSpace(buf.String()) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..cdcfba6 --- /dev/null +++ b/main.go @@ -0,0 +1,27 @@ +/* +Copyright © 2022 NAME HERE +*/ +package main + +import ( + "fmt" + + "github.com/sunggun-yu/gh-app-access-token-cli/cmd" +) + +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +// Version returns version and build information. it will be injected from ldflags(goreleaser) +func Version() string { + return fmt.Sprintf("%s, commit %s, built at %s", version, commit, date) +} + +func main() { + // set version + cmd.SetVersion(Version()) + cmd.Execute() +} diff --git a/pkg/installation/access_token.go b/pkg/installation/access_token.go new file mode 100644 index 0000000..d1b9a9a --- /dev/null +++ b/pkg/installation/access_token.go @@ -0,0 +1,38 @@ +package installation + +import ( + "context" + "net/http" + + "github.com/bradleyfalzon/ghinstallation/v2" + "github.com/google/go-github/v48/github" + "golang.org/x/oauth2" +) + +// GenerateAccessToken ... +func GenerateAccessToken(ctx context.Context, appID, installationID int64, privateKey []byte) (string, error) { + // Shared transport to reuse TCP connections. + tr := http.DefaultTransport + + // Wrap the shared transport for use with the app ID 1 authenticating with installation ID 99. + itr, err := ghinstallation.New(tr, appID, installationID, privateKey) + if err != nil { + return "", err + } + accessToken, err := itr.Token(ctx) + return accessToken, err +} + +// RevokeAccessToken ... +func RevokeAccessToken(ctx context.Context, accessToken string) error { + // github client auth with input access token + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: accessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + // revoke the token + _, err := client.Apps.RevokeInstallationToken(ctx) + return err +}