Browse Source

feat: Support repositories and permissions in GitHub generator (#4039)

* feat: Support repositories and permissions in GitHub generator

Signed-off-by: konradasb <konradasb0@gmail.com>

* fix: Correct typo ommited->omitted

Signed-off-by: konradasb <konradasb0@gmail.com>

* fix: Optimize http req body

Signed-off-by: konradasb <konradasb0@gmail.com>

* fix: Optimize body var usage

Signed-off-by: konradasb <konradasb0@gmail.com>

* fix: Correct typo marshalling->marshaling

Signed-off-by: konradasb <konradasb0@gmail.com>

---------

Signed-off-by: konradasb <konradasb0@gmail.com>
Konradas Bunikis 1 year ago
parent
commit
c51ad8d98f

+ 5 - 0
apis/generators/v1alpha1/generator_github.go

@@ -25,6 +25,11 @@ type GithubAccessTokenSpec struct {
 	URL       string `json:"url,omitempty"`
 	AppID     string `json:"appID"`
 	InstallID string `json:"installID"`
+	// List of repositories the token will have access to. If omitted, defaults to all repositories the GitHub App
+	// is installed to.
+	Repositories []string `json:"repositories,omitempty"`
+	// Map of permissions the token will have. If omitted, defaults to all permissions the GitHub App has.
+	Permissions map[string]string `json:"permissions,omitempty"`
 	// Auth configures how ESO authenticates with a Github instance.
 	Auth GithubAuth `json:"auth"`
 }

+ 12 - 0
apis/generators/v1alpha1/zz_generated.deepcopy.go

@@ -627,6 +627,18 @@ func (in *GithubAccessTokenList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GithubAccessTokenSpec) DeepCopyInto(out *GithubAccessTokenSpec) {
 	*out = *in
+	if in.Repositories != nil {
+		in, out := &in.Repositories, &out.Repositories
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.Permissions != nil {
+		in, out := &in.Permissions, &out.Permissions
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
 	in.Auth.DeepCopyInto(&out.Auth)
 }
 

+ 13 - 0
config/crds/bases/generators.external-secrets.io_githubaccesstokens.yaml

@@ -79,6 +79,19 @@ spec:
                 type: object
               installID:
                 type: string
+              permissions:
+                additionalProperties:
+                  type: string
+                description: Map of permissions the token will have. If omitted, defaults
+                  to all permissions the GitHub App has.
+                type: object
+              repositories:
+                description: |-
+                  List of repositories the token will have access to. If omitted, defaults to all repositories the GitHub App
+                  is installed to.
+                items:
+                  type: string
+                type: array
               url:
                 description: URL configures the Github instance URL. Defaults to https://github.com/.
                 type: string

+ 12 - 0
deploy/crds/bundle.yaml

@@ -11782,6 +11782,18 @@ spec:
                   type: object
                 installID:
                   type: string
+                permissions:
+                  additionalProperties:
+                    type: string
+                  description: Map of permissions the token will have. If omitted, defaults to all permissions the GitHub App has.
+                  type: object
+                repositories:
+                  description: |-
+                    List of repositories the token will have access to. If omitted, defaults to all repositories the GitHub App
+                    is installed to.
+                  items:
+                    type: string
+                  type: array
                 url:
                   description: URL configures the Github instance URL. Defaults to https://github.com/.
                   type: string

+ 4 - 0
docs/snippets/generator-github.yaml

@@ -13,6 +13,10 @@ spec:
   appID: "0000000" # (1)
   installID: "00000000" # (5)
   url: "" # (Default https://api.github.com.)
+  repositories: # Optional
+    - "Hello-World"
+  permissions: # Optional
+    contents: read
   auth:
     privateKey:
       secretRef:

+ 36 - 7
pkg/generator/github/github.go

@@ -15,11 +15,13 @@ limitations under the License.
 package github
 
 import (
+	"bytes"
 	"context"
 	"crypto/rsa"
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io"
 	"net/http"
 	"time"
 
@@ -37,11 +39,13 @@ type Generator struct {
 }
 
 type Github struct {
-	HTTP       *http.Client
-	Kube       client.Client
-	Namespace  string
-	URL        string
-	InstallTkn string
+	HTTP         *http.Client
+	Kube         client.Client
+	Namespace    string
+	URL          string
+	InstallTkn   string
+	Repositories []string
+	Permissions  map[string]string
 }
 
 const (
@@ -80,8 +84,27 @@ func (g *Generator) generate(
 	if err != nil {
 		return nil, fmt.Errorf("error creating request: %w", err)
 	}
+
+	payload := make(map[string]interface{})
+	if gh.Permissions != nil {
+		payload["permissions"] = gh.Permissions
+	}
+	if len(gh.Repositories) > 0 {
+		payload["repositories"] = gh.Repositories
+	}
+
+	var body io.Reader = http.NoBody
+	if len(payload) > 0 {
+		bodyBytes, err := json.Marshal(payload)
+		if err != nil {
+			return nil, fmt.Errorf("error marshaling payload: %w", err)
+		}
+
+		body = bytes.NewReader(bodyBytes)
+	}
+
 	// Github api expects POST request
-	req, err := http.NewRequestWithContext(ctx, http.MethodPost, gh.URL, http.NoBody)
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, gh.URL, body)
 	if err != nil {
 		return nil, fmt.Errorf("error creating request: %w", err)
 	}
@@ -120,7 +143,13 @@ func newGHClient(ctx context.Context, k client.Client, n string, hc *http.Client
 	if err != nil {
 		return nil, fmt.Errorf(errParseSpec, err)
 	}
-	gh := &Github{Kube: k, Namespace: n, HTTP: hc}
+	gh := &Github{
+		Kube:         k,
+		Namespace:    n,
+		HTTP:         hc,
+		Repositories: res.Spec.Repositories,
+		Permissions:  res.Spec.Permissions,
+	}
 
 	ghPath := fmt.Sprintf("/app/installations/%s/access_tokens", res.Spec.InstallID)
 	gh.URL = defaultGithubAPI + ghPath

+ 10 - 2
pkg/generator/github/github_test.go

@@ -38,7 +38,7 @@ const (
 func testHTTPSrv(t *testing.T, r []byte) *httptest.Server {
 	return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		assert.Equal(t, "POST", req.Method, "Expected POST request")
-		assert.Empty(t, req.Body)
+		assert.NotEmpty(t, req.Body)
 		assert.NotEmpty(t, req.Header.Get("Authorization"))
 		assert.Equal(t, "application/vnd.github.v3+json", req.Header.Get("Accept"))
 
@@ -60,9 +60,13 @@ func TestGenerate(t *testing.T) {
 		"token": "ghs_16C7e42F292c6912E7710c838347Ae178B4a",
 		"expires_at": "2016-07-11T22:14:10Z",
 		"permissions": {
-		  "issues": "write",
 		  "contents": "read"
 		},
+		"repositories": [
+			{
+				"id": 10000
+			}
+		],
 		"repository_selection": "selected"
 	  }`)
 
@@ -103,6 +107,10 @@ spec:
   appID: "0000000"
   installID: "00000000"
   URL: %q
+  repositories:
+  - "Hello-World"
+  permissions:
+    contents: "read"
   auth:
     privateKey:
       secretRef: