Skip to content

Commit f15c68c

Browse files
authored
Fixes error parsing app id / client id through provisioning via environment variables (#478)
Fixes error parsing app id / client id through provisioning via environment variables. Fixes #477
1 parent b66c3c6 commit f15c68c

File tree

4 files changed

+165
-33
lines changed

4 files changed

+165
-33
lines changed

.changeset/gorgeous-moles-clean.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'grafana-github-datasource': patch
3+
---
4+
5+
Fixes error parsing app id / client id through provisioning via environment variables. Fixes [#477](https://github.com/grafana/github-datasource/issues/477)

pkg/github/client/client.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,33 +54,21 @@ var runnerPerMinuteRate = map[string]float64{
5454

5555
// New instantiates a new GitHub API client.
5656
func New(ctx context.Context, settings models.Settings) (*Client, error) {
57-
if settings.SelectedAuthType == "github-app" {
57+
if settings.SelectedAuthType == models.AuthTypeGithubApp {
5858
return createAppClient(settings)
5959
}
60-
61-
if settings.SelectedAuthType == "personal-access-token" {
60+
if settings.SelectedAuthType == models.AuthTypePAT {
6261
return createAccessTokenClient(ctx, settings)
6362
}
64-
6563
return nil, backend.DownstreamError(errors.New("access token or app token are required"))
6664
}
6765

6866
func createAppClient(settings models.Settings) (*Client, error) {
69-
appId, err := strconv.ParseInt(settings.AppId, 10, 64)
70-
if err != nil {
71-
return nil, backend.DownstreamError(errors.New("error parsing app id"))
72-
}
73-
74-
installationId, err := strconv.ParseInt(settings.InstallationId, 10, 64)
75-
if err != nil {
76-
return nil, backend.DownstreamError(errors.New("error parsing installation id"))
77-
}
78-
7967
transport, err := httpclient.GetDefaultTransport()
8068
if err != nil {
8169
return nil, backend.DownstreamError(errors.New("error: http.DefaultTransport is not of type *http.Transport"))
8270
}
83-
itr, err := ghinstallation.New(transport, appId, installationId, []byte(settings.PrivateKey))
71+
itr, err := ghinstallation.New(transport, settings.AppIdInt64, settings.InstallationIdInt64, []byte(settings.PrivateKey))
8472
if err != nil {
8573
return nil, backend.DownstreamError(errors.New("error creating token source"))
8674
}

pkg/models/settings.go

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,66 @@ package models
22

33
import (
44
"encoding/json"
5+
"fmt"
6+
"strconv"
7+
"strings"
58

69
"github.com/grafana/grafana-plugin-sdk-go/backend"
710
)
811

12+
type AuthType string
13+
14+
const (
15+
AuthTypePAT AuthType = "personal-access-token"
16+
AuthTypeGithubApp AuthType = "github-app"
17+
)
18+
919
type Settings struct {
10-
SelectedAuthType string `json:"selectedAuthType"`
11-
AccessToken string `json:"accessToken"`
12-
PrivateKey string `json:"privateKey"`
13-
AppId string `json:"appId"`
14-
InstallationId string `json:"installationId"`
15-
GitHubURL string `json:"githubUrl"`
16-
CachingEnabled bool `json:"cachingEnabled"`
20+
// General settings
21+
GitHubURL string `json:"githubUrl,omitempty"`
22+
CachingEnabled bool `json:"cachingEnabled,omitempty"`
23+
// Auth type related settings
24+
SelectedAuthType AuthType `json:"selectedAuthType,omitempty"`
25+
// personal-access-token auth related settings
26+
AccessToken string
27+
// github-app auth related settings
28+
AppId json.RawMessage `json:"appId,omitempty"`
29+
AppIdInt64 int64
30+
InstallationId json.RawMessage `json:"installationId,omitempty"`
31+
InstallationIdInt64 int64
32+
PrivateKey string
1733
}
1834

19-
func LoadSettings(settings backend.DataSourceInstanceSettings) (Settings, error) {
20-
s := Settings{}
35+
func LoadSettings(settings backend.DataSourceInstanceSettings) (s Settings, err error) {
2136
if err := json.Unmarshal(settings.JSONData, &s); err != nil {
22-
return Settings{}, err
37+
return s, err
38+
}
39+
if s.SelectedAuthType == AuthTypeGithubApp {
40+
if s.AppIdInt64, err = rawMessageToInt64(s.AppId, "app id"); err != nil {
41+
return s, err
42+
}
43+
if s.InstallationIdInt64, err = rawMessageToInt64(s.InstallationId, "installation id"); err != nil {
44+
return s, err
45+
}
46+
if val, ok := settings.DecryptedSecureJSONData["privateKey"]; ok {
47+
s.PrivateKey = val
48+
}
2349
}
24-
2550
if val, ok := settings.DecryptedSecureJSONData["accessToken"]; ok {
2651
s.AccessToken = val
2752
}
28-
29-
if val, ok := settings.DecryptedSecureJSONData["privateKey"]; ok {
30-
s.PrivateKey = val
31-
}
32-
3353
// Data sources created before the auth type was introduced will have an accessToken but no auth type.
3454
// In this case, we default to personal access token.
3555
if s.AccessToken != "" && s.SelectedAuthType == "" {
36-
s.SelectedAuthType = "personal-access-token"
56+
s.SelectedAuthType = AuthTypePAT
3757
}
38-
3958
return s, nil
4059
}
60+
61+
func rawMessageToInt64(r json.RawMessage, m string) (out int64, err error) {
62+
out, err = strconv.ParseInt(strings.ReplaceAll(string(r), `"`, ""), 10, 64)
63+
if err != nil {
64+
return 0, fmt.Errorf("error parsing %s", m)
65+
}
66+
return out, nil
67+
}

pkg/models/settings_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package models_test
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"testing"
7+
8+
"github.com/grafana/github-datasource/pkg/models"
9+
"github.com/grafana/grafana-plugin-sdk-go/backend"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestLoadSettings(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
settings backend.DataSourceInstanceSettings
18+
jsonData json.RawMessage
19+
decryptedJsonData map[string]string
20+
want models.Settings
21+
wantErr error
22+
}{
23+
{
24+
name: "valid config should not throw error for pat authentication",
25+
jsonData: []byte(`{
26+
"githubUrl" : "https://foo.com"
27+
}`),
28+
decryptedJsonData: map[string]string{"accessToken": "foo"},
29+
want: models.Settings{
30+
GitHubURL: "https://foo.com",
31+
SelectedAuthType: models.AuthTypePAT,
32+
AccessToken: "foo",
33+
},
34+
},
35+
{
36+
name: "valid config should not throw error for github app type authentication",
37+
jsonData: []byte(`{
38+
"githubUrl" : "https://foo.com",
39+
"selectedAuthType" : "github-app",
40+
"appId" : "1111",
41+
"installationId" : "2222"
42+
}`),
43+
decryptedJsonData: map[string]string{"privateKey": "foo"},
44+
want: models.Settings{
45+
GitHubURL: "https://foo.com",
46+
SelectedAuthType: models.AuthTypeGithubApp,
47+
AppId: []byte(`"1111"`),
48+
AppIdInt64: 1111,
49+
InstallationId: []byte(`"2222"`),
50+
InstallationIdInt64: 2222,
51+
PrivateKey: "foo",
52+
},
53+
},
54+
{
55+
name: "valid config should not throw error for github app type authentication - passed as numbers",
56+
jsonData: []byte(`{
57+
"githubUrl" : "https://foo.com",
58+
"selectedAuthType" : "github-app",
59+
"appId" : 1111,
60+
"installationId" : 2222
61+
}`),
62+
decryptedJsonData: map[string]string{"privateKey": "foo"},
63+
want: models.Settings{
64+
GitHubURL: "https://foo.com",
65+
SelectedAuthType: models.AuthTypeGithubApp,
66+
AppId: []byte(`1111`),
67+
AppIdInt64: 1111,
68+
InstallationId: []byte(`2222`),
69+
InstallationIdInt64: 2222,
70+
PrivateKey: "foo",
71+
},
72+
},
73+
{
74+
name: "invalid config should throw error for github app type authentication - app id passed as string literals",
75+
jsonData: []byte(`{
76+
"githubUrl" : "https://foo.com",
77+
"selectedAuthType" : "github-app",
78+
"appId" : "1111xyz",
79+
"installationId" : "2222"
80+
}`),
81+
decryptedJsonData: map[string]string{"privateKey": "foo"},
82+
wantErr: errors.New("error parsing app id"),
83+
},
84+
{
85+
name: "invalid config should throw error for github app type authentication - installation id passed as string literals",
86+
jsonData: []byte(`{
87+
"githubUrl" : "https://foo.com",
88+
"selectedAuthType" : "github-app",
89+
"appId" : "1111",
90+
"installationId" : "2222xyz"
91+
}`),
92+
decryptedJsonData: map[string]string{"privateKey": "foo"},
93+
wantErr: errors.New("error parsing installation id"),
94+
},
95+
}
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
if tt.jsonData == nil {
99+
tt.jsonData = []byte(`{}`)
100+
}
101+
got, err := models.LoadSettings(backend.DataSourceInstanceSettings{JSONData: tt.jsonData, DecryptedSecureJSONData: tt.decryptedJsonData})
102+
if tt.wantErr != nil {
103+
require.NotNil(t, err)
104+
assert.Equal(t, tt.wantErr.Error(), err.Error())
105+
return
106+
}
107+
require.Nil(t, err)
108+
require.NotNil(t, got)
109+
assert.Equal(t, tt.want, got)
110+
})
111+
}
112+
}

0 commit comments

Comments
 (0)