Skip to content

Commit 2be72e9

Browse files
committed
feat: added organization app and fixed organization member fields
1 parent 3e8f858 commit 2be72e9

File tree

13 files changed

+714
-60
lines changed

13 files changed

+714
-60
lines changed

cloudql/github/plugin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
6565
"github_repository_webhook": tableGithubRepositoryWebhook(),
6666
"github_artifact_ai_model": tableGitHubArtifactAIModel(),
6767
"github_organization_role": tableGitHubOrganizationRole(),
68+
"github_organization_app": tableGitHubOrganizationApp(),
6869
},
6970
}
7071
for key, table := range p.TableMap {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package github
2+
3+
import (
4+
opengovernance "github.com/opengovern/og-describer-github/discovery/pkg/es"
5+
"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
6+
"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
7+
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"
8+
)
9+
10+
func tableGitHubOrganizationApp() *plugin.Table {
11+
return &plugin.Table{
12+
Name: "github_organization_app",
13+
Description: "GitHub organization applications installed in a specific organization.",
14+
List: &plugin.ListConfig{
15+
Hydrate: opengovernance.ListOrganizationApp,
16+
},
17+
Get: &plugin.GetConfig{
18+
KeyColumns: []*plugin.KeyColumn{
19+
{Name: "id", Require: plugin.Required},
20+
},
21+
Hydrate: opengovernance.GetOrganizationApp,
22+
},
23+
Columns: commonColumns([]*plugin.Column{
24+
{Name: "id", Type: proto.ColumnType_INT, Description: "The unique identifier of the app.",
25+
Transform: transform.FromField("Description.ID")},
26+
27+
{Name: "client_id", Type: proto.ColumnType_STRING, Description: "The client ID of the app.",
28+
Transform: transform.FromField("Description.ClientID")},
29+
30+
{Name: "repository_selection", Type: proto.ColumnType_STRING, Description: "Specifies if the app has access to all or selected repositories.",
31+
Transform: transform.FromField("Description.RepositorySelection")},
32+
33+
{Name: "access_tokens_url", Type: proto.ColumnType_STRING, Description: "URL to generate an installation access token.",
34+
Transform: transform.FromField("Description.AccessTokensURL")},
35+
36+
{Name: "repositories_url", Type: proto.ColumnType_STRING, Description: "URL to list repositories accessible to the app.",
37+
Transform: transform.FromField("Description.RepositoriesURL")},
38+
39+
{Name: "html_url", Type: proto.ColumnType_STRING, Description: "URL to view the app's installation page on GitHub.",
40+
Transform: transform.FromField("Description.HTMLURL")},
41+
42+
{Name: "app_id", Type: proto.ColumnType_INT, Description: "The ID of the GitHub app.",
43+
Transform: transform.FromField("Description.AppID")},
44+
45+
{Name: "app_slug", Type: proto.ColumnType_STRING, Description: "The slug of the GitHub app.",
46+
Transform: transform.FromField("Description.AppSlug")},
47+
48+
{Name: "target_id", Type: proto.ColumnType_INT, Description: "The ID of the target organization.",
49+
Transform: transform.FromField("Description.TargetID")},
50+
51+
{Name: "target_type", Type: proto.ColumnType_STRING, Description: "The type of the target entity (e.g., Organization).",
52+
Transform: transform.FromField("Description.TargetType")},
53+
54+
{Name: "permissions", Type: proto.ColumnType_JSON, Description: "Permissions granted to the app.",
55+
Transform: transform.FromField("Description.Permissions")},
56+
57+
{Name: "events", Type: proto.ColumnType_JSON, Description: "Events the app is subscribed to.",
58+
Transform: transform.FromField("Description.Events")},
59+
60+
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when the app was created.",
61+
Transform: transform.FromField("Description.CreatedAt")},
62+
63+
{Name: "updated_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when the app was last updated.",
64+
Transform: transform.FromField("Description.UpdatedAt")},
65+
66+
{Name: "single_file_name", Type: proto.ColumnType_STRING, Description: "Name of the single file the app has access to, if applicable.",
67+
Transform: transform.FromField("Description.SingleFileName")},
68+
69+
{Name: "has_multiple_single_files", Type: proto.ColumnType_BOOL, Description: "Indicates if the app has access to multiple single files.",
70+
Transform: transform.FromField("Description.HasMultipleSingleFiles")},
71+
72+
{Name: "single_file_paths", Type: proto.ColumnType_JSON, Description: "Paths of single files the app has access to.",
73+
Transform: transform.FromField("Description.SingleFilePaths")},
74+
75+
{Name: "suspended_by", Type: proto.ColumnType_JSON, Description: "Information about the user who suspended the app, if applicable.",
76+
Transform: transform.FromField("Description.SuspendedBy")},
77+
78+
{Name: "suspended_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when the app was suspended, if applicable.",
79+
Transform: transform.FromField("Description.SuspendedAt")},
80+
}),
81+
}
82+
}

cloudql/github/table_github_organization_member.go

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,42 @@ import (
99
)
1010

1111
func gitHubOrganizationMemberColumns() []*plugin.Column {
12-
tableCols := []*plugin.Column{
13-
{
14-
Name: "organization",
15-
Type: proto.ColumnType_STRING,
16-
Description: "The organization the member is associated with.",
17-
Transform: transform.FromField("Description.Organization")},
18-
{
19-
Name: "role",
20-
Type: proto.ColumnType_STRING,
21-
Description: "The role this user has in the organization. Returns null if information is not available to viewer.",
22-
Transform: transform.FromField("Description.Role")},
23-
{
24-
Name: "has_two_factor_enabled",
25-
Type: proto.ColumnType_BOOL,
12+
cols := []*plugin.Column{
13+
{Name: "has_two_factor_enabled", Type: proto.ColumnType_BOOL,
2614
Transform: transform.FromField("Description.HasTwoFactorEnabled")},
27-
{
28-
Name: "login_id",
29-
Type: proto.ColumnType_STRING,
30-
Description: "login id",
31-
Transform: transform.FromField("Description.LoginID")},
15+
{Name: "organization", Type: proto.ColumnType_STRING, Description: "The team organization",
16+
Transform: transform.FromField("Description.Organization")},
17+
{Name: "role", Type: proto.ColumnType_STRING, Description: "The team member's role (MEMBER, MAINTAINER).",
18+
Transform: transform.FromField("Description.Role")},
19+
{Name: "login_id", Type: proto.ColumnType_STRING, Description: "Unique identifier for the user login.",
20+
Transform: transform.FromField("Description.LoginID")},
21+
{Name: "login", Type: proto.ColumnType_STRING, Description: "The login name of the user.",
22+
Transform: transform.FromField("Description.Login")},
23+
{Name: "id", Type: proto.ColumnType_INT, Description: "The ID of the user.",
24+
Transform: transform.FromField("Description.ID")},
25+
{Name: "name", Type: proto.ColumnType_STRING, Description: "The name of the user.",
26+
Transform: transform.FromField("Description.Name")},
27+
{Name: "node_id", Type: proto.ColumnType_STRING, Description: "The node ID of the user.",
28+
Transform: transform.FromField("Description.NodeID")},
29+
{Name: "email", Type: proto.ColumnType_STRING, Description: "The email of the user.",
30+
Transform: transform.FromField("Description.Email")},
31+
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when user was created.",
32+
Transform: transform.FromField("Description.CreatedAt")},
33+
{Name: "updated_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when user was updated.",
34+
Transform: transform.FromField("Description.UpdatedAt")},
35+
{Name: "company", Type: proto.ColumnType_STRING, Description: "The company on the users profile.",
36+
Transform: transform.FromField("Description.Company")},
37+
{Name: "is_site_admin", Type: proto.ColumnType_BOOL, Description: "If true, user is a site administrator.",
38+
Transform: transform.FromField("Description.IsSiteAdmin")},
39+
{Name: "location", Type: proto.ColumnType_STRING, Description: "The location of the user.",
40+
Transform: transform.FromField("Description.Location")},
41+
{Name: "website_url", Type: proto.ColumnType_STRING, Description: "",
42+
Transform: transform.FromField("Description.WebsiteURL")},
43+
{Name: "status", Type: proto.ColumnType_BOOL, Description: "",
44+
Transform: transform.FromField("Description.Status")},
3245
}
3346

34-
return append(tableCols, sharedUserColumns()...)
47+
return cols
3548
}
3649

3750
func tableGitHubOrganizationMember() *plugin.Table {
@@ -41,6 +54,10 @@ func tableGitHubOrganizationMember() *plugin.Table {
4154
List: &plugin.ListConfig{
4255
Hydrate: opengovernance.ListOrgMembers,
4356
},
57+
Get: &plugin.GetConfig{
58+
KeyColumns: plugin.SingleColumn("id"),
59+
Hydrate: opengovernance.GetOrgMembers,
60+
},
4461
Columns: commonColumns(gitHubOrganizationMemberColumns()),
4562
}
4663
}

cloudql/github/table_github_team_member.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ func gitHubTeamMemberColumns() []*plugin.Column {
3535
{Name: "login", Type: proto.ColumnType_STRING, Description: "The login name of the user.",
3636
Transform: transform.FromField("Description.Login")},
3737
{Name: "id", Type: proto.ColumnType_INT, Description: "The ID of the user.",
38-
Transform: transform.FromField("Description.Id")},
38+
Transform: transform.FromField("Description.ID")},
3939
{Name: "name", Type: proto.ColumnType_STRING, Description: "The name of the user.",
4040
Transform: transform.FromField("Description.Name")},
4141
{Name: "node_id", Type: proto.ColumnType_STRING, Description: "The node ID of the user.",
42-
Transform: transform.FromField("Description.NodeId")},
42+
Transform: transform.FromField("Description.NodeID")},
4343
{Name: "email", Type: proto.ColumnType_STRING, Description: "The email of the user.",
4444
Transform: transform.FromField("Description.Email")},
4545
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when user was created.",
46-
Transform: transform.FromField("Description.CreatedAt").NullIfZero().Transform(convertTimestamp)},
46+
Transform: transform.FromField("Description.CreatedAt")},
4747
{Name: "company", Type: proto.ColumnType_STRING, Description: "The company on the users profile.",
4848
Transform: transform.FromField("Description.Company")},
4949
{Name: "interaction_ability", Type: proto.ColumnType_JSON, Description: "The interaction ability settings for this user.",

cloudql/github/table_github_team_repository.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ func tableGitHubTeamRepository() *plugin.Table {
3131
Transform: transform.FromField("Description.RepositoryFullName")},
3232

3333
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "",
34-
Transform: transform.FromField("Description.CreatedAt").NullIfZero().Transform(convertTimestamp)},
34+
Transform: transform.FromField("Description.CreatedAt")},
3535

3636
{Name: "updated_at", Type: proto.ColumnType_TIMESTAMP, Description: "",
37-
Transform: transform.FromField("Description.UpdatedAt").NullIfZero().Transform(convertTimestamp)},
37+
Transform: transform.FromField("Description.UpdatedAt")},
3838
}),
3939
}
4040
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package describers
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"github.com/opengovern/og-describer-github/discovery/pkg/models"
8+
model "github.com/opengovern/og-describer-github/discovery/provider"
9+
resilientbridge "github.com/opengovern/resilient-bridge"
10+
"github.com/opengovern/resilient-bridge/adapters"
11+
"strconv"
12+
"time"
13+
)
14+
15+
func ListOrganizationApps(ctx context.Context,
16+
githubClient model.GitHubClient,
17+
organizationName string,
18+
stream *models.StreamSender) ([]models.Resource, error) {
19+
sdk := resilientbridge.NewResilientBridge()
20+
sdk.RegisterProvider("github", adapters.NewGitHubAdapter(githubClient.Token), &resilientbridge.ProviderConfig{
21+
UseProviderLimits: true,
22+
MaxRetries: 3,
23+
BaseBackoff: 0,
24+
})
25+
26+
var values []models.Resource
27+
28+
endpoint := fmt.Sprintf("/orgs/%s/installations", organizationName)
29+
req := &resilientbridge.NormalizedRequest{
30+
Method: "GET",
31+
Endpoint: endpoint,
32+
Headers: map[string]string{"Accept": "application/vnd.github+json"},
33+
}
34+
35+
resp, err := sdk.Request("github", req)
36+
if err != nil {
37+
return nil, fmt.Errorf("error fetching repos: %w", err)
38+
}
39+
if resp.StatusCode != 200 {
40+
return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(resp.Data))
41+
}
42+
43+
var appsResponse OrganizationAppsResponse
44+
if err := json.Unmarshal(resp.Data, &appsResponse); err != nil {
45+
return nil, fmt.Errorf("error decoding repos list: %w", err)
46+
}
47+
48+
for _, app := range appsResponse.Installations {
49+
account := model.Account{
50+
Login: app.Account.Login,
51+
ID: app.Account.ID,
52+
NodeID: app.Account.NodeID,
53+
AvatarURL: app.Account.AvatarURL,
54+
GravatarID: app.Account.GravatarID,
55+
URL: app.Account.URL,
56+
HTMLURL: app.Account.HTMLURL,
57+
FollowersURL: app.Account.FollowersURL,
58+
FollowingURL: app.Account.FollowingURL,
59+
GistsURL: app.Account.GistsURL,
60+
StarredURL: app.Account.StarredURL,
61+
SubscriptionsURL: app.Account.SubscriptionsURL,
62+
OrganizationsURL: app.Account.OrganizationsURL,
63+
ReposURL: app.Account.ReposURL,
64+
EventsURL: app.Account.EventsURL,
65+
ReceivedEventsURL: app.Account.ReceivedEventsURL,
66+
Type: app.Account.Type,
67+
UserViewType: app.Account.UserViewType,
68+
SiteAdmin: app.Account.SiteAdmin,
69+
}
70+
var suspendedBy *model.Account
71+
if app.SuspendedBy != nil {
72+
suspendedBy = &model.Account{
73+
Login: app.SuspendedBy.Login,
74+
ID: app.SuspendedBy.ID,
75+
NodeID: app.SuspendedBy.NodeID,
76+
AvatarURL: app.SuspendedBy.AvatarURL,
77+
GravatarID: app.SuspendedBy.GravatarID,
78+
URL: app.SuspendedBy.URL,
79+
HTMLURL: app.SuspendedBy.HTMLURL,
80+
FollowersURL: app.SuspendedBy.FollowersURL,
81+
FollowingURL: app.SuspendedBy.FollowingURL,
82+
GistsURL: app.SuspendedBy.GistsURL,
83+
StarredURL: app.SuspendedBy.StarredURL,
84+
SubscriptionsURL: app.SuspendedBy.SubscriptionsURL,
85+
OrganizationsURL: app.SuspendedBy.OrganizationsURL,
86+
ReposURL: app.SuspendedBy.ReposURL,
87+
EventsURL: app.SuspendedBy.EventsURL,
88+
ReceivedEventsURL: app.SuspendedBy.ReceivedEventsURL,
89+
Type: app.SuspendedBy.Type,
90+
UserViewType: app.SuspendedBy.UserViewType,
91+
SiteAdmin: app.SuspendedBy.SiteAdmin,
92+
}
93+
}
94+
value := models.Resource{
95+
ID: strconv.Itoa(int(app.ID)),
96+
Name: app.AppSlug,
97+
Description: model.OrganizationAppDescription{
98+
ID: app.ID,
99+
ClientID: app.ClientID,
100+
Account: account,
101+
RepositorySelection: app.RepositorySelection,
102+
AccessTokensURL: app.AccessTokensURL,
103+
RepositoriesURL: app.RepositoriesURL,
104+
HTMLURL: app.HTMLURL,
105+
AppID: app.AppID,
106+
AppSlug: app.AppSlug,
107+
TargetID: app.TargetID,
108+
TargetType: app.TargetType,
109+
Permissions: app.Permissions,
110+
Events: app.Events,
111+
CreatedAt: app.CreatedAt,
112+
UpdatedAt: app.UpdatedAt,
113+
SingleFileName: app.SingleFileName,
114+
HasMultipleSingleFiles: app.HasMultipleSingleFiles,
115+
SingleFilePaths: app.SingleFilePaths,
116+
SuspendedBy: suspendedBy,
117+
SuspendedAt: app.SuspendedAt,
118+
},
119+
}
120+
if stream != nil {
121+
if err := (*stream)(value); err != nil {
122+
return nil, err
123+
}
124+
} else {
125+
values = append(values, value)
126+
}
127+
}
128+
129+
return values, nil
130+
}
131+
132+
type OrganizationAppsResponse struct {
133+
Installations []OrganizationApp `json:"installations"`
134+
}
135+
136+
type OrganizationApp struct {
137+
ID int64 `json:"id"`
138+
ClientID string `json:"client_id"`
139+
Account AccountJSON `json:"account"`
140+
RepositorySelection string `json:"repository_selection"`
141+
AccessTokensURL string `json:"access_tokens_url"`
142+
RepositoriesURL string `json:"repositories_url"`
143+
HTMLURL string `json:"html_url"`
144+
AppID int `json:"app_id"`
145+
AppSlug string `json:"app_slug"`
146+
TargetID int64 `json:"target_id"`
147+
TargetType string `json:"target_type"`
148+
Permissions map[string]string `json:"permissions"`
149+
Events []string `json:"events"`
150+
CreatedAt time.Time `json:"created_at"`
151+
UpdatedAt time.Time `json:"updated_at"`
152+
SingleFileName *string `json:"single_file_name"`
153+
HasMultipleSingleFiles bool `json:"has_multiple_single_files"`
154+
SingleFilePaths []string `json:"single_file_paths"`
155+
SuspendedBy *AccountJSON `json:"suspended_by"`
156+
SuspendedAt *time.Time `json:"suspended_at"`
157+
}
158+
159+
type AccountJSON struct {
160+
Login string `json:"login"`
161+
ID int64 `json:"id"`
162+
NodeID string `json:"node_id"`
163+
AvatarURL string `json:"avatar_url"`
164+
GravatarID string `json:"gravatar_id"`
165+
URL string `json:"url"`
166+
HTMLURL string `json:"html_url"`
167+
FollowersURL string `json:"followers_url"`
168+
FollowingURL string `json:"following_url"`
169+
GistsURL string `json:"gists_url"`
170+
StarredURL string `json:"starred_url"`
171+
SubscriptionsURL string `json:"subscriptions_url"`
172+
OrganizationsURL string `json:"organizations_url"`
173+
ReposURL string `json:"repos_url"`
174+
EventsURL string `json:"events_url"`
175+
ReceivedEventsURL string `json:"received_events_url"`
176+
Type string `json:"type"`
177+
UserViewType string `json:"user_view_type"`
178+
SiteAdmin bool `json:"site_admin"`
179+
}

0 commit comments

Comments
 (0)