Skip to content

Commit 04a52c2

Browse files
wlynchwillnorris
authored andcommitted
Add App Manifest support.
This adds support for creating GitHub Apps programmatically. See https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest for more details. CreateApp was added to scrape, since it is effectively emulating the POST the UI makes, and doesn't actually interact with api.github.com. Fixes #1334.
1 parent ebb45fa commit 04a52c2

File tree

7 files changed

+281
-0
lines changed

7 files changed

+281
-0
lines changed

github/apps_manifest.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2019 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"context"
10+
"fmt"
11+
)
12+
13+
const (
14+
mediaTypeAppManifestPreview = "application/vnd.github.fury-preview+json"
15+
)
16+
17+
// AppConfig describes the configuration of a GitHub App.
18+
type AppConfig struct {
19+
ID *int64 `json:"id,omitempty"`
20+
NodeID *string `json:"node_id,omitempty"`
21+
Owner *User `json:"owner,omitempty"`
22+
Name *string `json:"name,omitempty"`
23+
Description *string `json:"description,omitempty"`
24+
ExternalURL *string `json:"external_url,omitempty"`
25+
HTMLURL *string `json:"html_url,omitempty"`
26+
CreatedAt *Timestamp `json:"created_at,omitempty"`
27+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
28+
ClientID *string `json:"client_id,omitempty"`
29+
ClientSecret *string `json:"client_secret,omitempty"`
30+
WebhookSecret *string `json:"webhook_secret,omitempty"`
31+
PEM *string `json:"pem,omitempty"`
32+
}
33+
34+
// CompleteAppManifest completes the App manifest handshake flow for the given
35+
// code.
36+
//
37+
// GitHub API docs: https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/#3-you-exchange-the-temporary-code-to-retrieve-the-app-configuration
38+
func (s *AppsService) CompleteAppManifest(ctx context.Context, code string) (*AppConfig, *Response, error) {
39+
u := fmt.Sprintf("app-manifests/%s/conversions", code)
40+
req, err := s.client.NewRequest("POST", u, nil)
41+
if err != nil {
42+
return nil, nil, err
43+
}
44+
req.Header.Set("Accept", mediaTypeAppManifestPreview)
45+
46+
cfg := new(AppConfig)
47+
resp, err := s.client.Do(ctx, req, cfg)
48+
if err != nil {
49+
return nil, resp, err
50+
}
51+
52+
return cfg, resp, nil
53+
}

github/apps_manifest_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2019 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"net/http"
12+
"reflect"
13+
"testing"
14+
)
15+
16+
const (
17+
manifestJSON = `{
18+
"id": 1,
19+
"client_id": "a" ,
20+
"client_secret": "b",
21+
"webhook_secret": "c",
22+
"pem": "key"
23+
}
24+
`
25+
)
26+
27+
func TestGetConfig(t *testing.T) {
28+
client, mux, _, teardown := setup()
29+
defer teardown()
30+
31+
mux.HandleFunc("/app-manifests/code/conversions", func(w http.ResponseWriter, r *http.Request) {
32+
testMethod(t, r, "POST")
33+
testHeader(t, r, "Accept", mediaTypeAppManifestPreview)
34+
fmt.Fprint(w, manifestJSON)
35+
})
36+
37+
cfg, _, err := client.Apps.CompleteAppManifest(context.Background(), "code")
38+
if err != nil {
39+
t.Errorf("AppManifest.GetConfig returned error: %v", err)
40+
}
41+
42+
want := &AppConfig{
43+
ID: Int64(1),
44+
ClientID: String("a"),
45+
ClientSecret: String("b"),
46+
WebhookSecret: String("c"),
47+
PEM: String("key"),
48+
}
49+
50+
if !reflect.DeepEqual(cfg, want) {
51+
t.Errorf("GetConfig returned %+v, want %+v", cfg, want)
52+
}
53+
}

github/github-accessors.go

Lines changed: 104 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scrape/apps.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99
package scrape
1010

1111
import (
12+
"bytes"
13+
"encoding/json"
1214
"errors"
15+
"net/http"
1316
"strconv"
1417
"strings"
1518

1619
"github.com/PuerkitoBio/goquery"
20+
"github.com/google/go-github/v28/github"
1721
)
1822

1923
// AppRestrictionsEnabled returns whether the specified organization has
@@ -103,3 +107,40 @@ type OAuthApp struct {
103107
State OAuthAppReviewState
104108
RequestedBy string
105109
}
110+
111+
// AppManifest represents a GitHub App manifest, used for preconfiguring
112+
// GitHub App configuration.
113+
type AppManifest struct {
114+
// The name of the GitHub App.
115+
Name *string `json:"name,omitempty"`
116+
//Required. The homepage of your GitHub App.
117+
URL *string `json:"url,omitempty"`
118+
// Required. The configuration of the GitHub App's webhook.
119+
HookAttributes map[string]string `json:"hook_attributes,omitempty"`
120+
// The full URL to redirect to after the person installs the GitHub App.
121+
RedirectURL *string `json:"redirect_url,omitempty"`
122+
// A description of the GitHub App.
123+
Description *string `json:"description,omitempty"`
124+
// Set to true when your GitHub App is available to the public or false when
125+
// it is only accessible to the owner of the app.
126+
Public *bool `json:"public,omitempty"`
127+
// The list of events the GitHub App subscribes to.
128+
DefaultEvents []string `json:"default_events,omitempty"`
129+
// The set of permissions needed by the GitHub App.
130+
DefaultPermissions *github.InstallationPermissions `json:"default_permissions,omitempty"`
131+
}
132+
133+
// CreateApp creates a new GitHub App with the given manifest configuration.
134+
func (c *Client) CreateApp(m *AppManifest) (*http.Response, error) {
135+
u, err := c.baseURL.Parse("/settings/apps/new")
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
body, err := json.Marshal(map[string]*AppManifest{"manifest": m})
141+
if err != nil {
142+
return nil, err
143+
}
144+
145+
return c.Client.Post(u.String(), "json", bytes.NewReader(body))
146+
}

scrape/apps_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-github/v28/github"
89
)
910

1011
func Test_AppRestrictionsEnabled(t *testing.T) {
@@ -84,3 +85,21 @@ func Test_ListOAuthApps(t *testing.T) {
8485
}
8586

8687
}
88+
89+
func Test_CreateApp(t *testing.T) {
90+
client, mux, cleanup := setup()
91+
defer cleanup()
92+
93+
mux.HandleFunc("/apps/settings/new", func(w http.ResponseWriter, r *http.Request) {
94+
w.WriteHeader(http.StatusCreated)
95+
})
96+
97+
if _, err := client.CreateApp(&AppManifest{
98+
URL: github.String("https://example.com"),
99+
HookAttributes: map[string]string{
100+
"url": "https://example.com/hook",
101+
},
102+
}); err != nil {
103+
t.Fatalf("CreateApp: %v", err)
104+
}
105+
}

scrape/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.13
55
require (
66
github.com/PuerkitoBio/goquery v1.5.0
77
github.com/google/go-cmp v0.3.1
8+
github.com/google/go-github/v28 v28.1.1
89
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
910
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582
1011
)

scrape/go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@ github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP
22
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
33
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
44
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
5+
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
56
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
67
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
8+
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
9+
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
10+
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
11+
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
712
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
813
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
14+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
915
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1016
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1117
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
18+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1219
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
1320
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
21+
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
22+
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1423
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1524
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
25+
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=

0 commit comments

Comments
 (0)