Improve extensions build. (#12461)

* enable ee build on pr/master

* step1: of including group sync

* disable commit pinning for now

* fixes broken build

* enable team to ldap group sync

* avoid returning error for missing external handler

* services: allow routes to be added before http server start

* services: allows services to add their own migrations

* moves db migrations to ee code base

* build using master branch in ee

* disable enterprise build in .bra.toml

[skip ci]

* removes team sync extensions

* removes commented line
This commit is contained in:
Carl Bergquist 2018-07-01 16:01:43 +02:00 committed by Torkel Ödegaard
parent 1601f6d17c
commit 861af4cb97
17 changed files with 197 additions and 63 deletions

View File

@ -221,6 +221,8 @@ workflows:
jobs:
- build-all:
filters: *filter-not-release
- build-enterprise:
filters: *filter-not-release
- codespell:
filters: *filter-not-release
- gometalinter:

View File

@ -9,7 +9,14 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
// Register adds http routes
func (hs *HTTPServer) applyRoutes() {
hs.RouteRegister.Register(hs.macaron)
InitAppPluginRoutes(hs.macaron)
hs.macaron.NotFound(NotFoundHandler)
}
func (hs *HTTPServer) registerRoutes() {
macaronR := hs.macaron
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
@ -393,10 +400,4 @@ func (hs *HTTPServer) registerRoutes() {
// streams
//r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream)
r.Register(macaronR)
InitAppPluginRoutes(macaronR)
macaronR.NotFound(NotFoundHandler)
}

View File

@ -132,6 +132,7 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
}
jsonObj := map[string]interface{}{
"enterprise": setting.Enterprise,
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"panels": panels,

View File

@ -33,7 +33,11 @@ import (
)
func init() {
registry.RegisterService(&HTTPServer{})
registry.Register(&registry.Descriptor{
Name: "HTTPServer",
Instance: &HTTPServer{},
InitPriority: registry.High,
})
}
type HTTPServer struct {
@ -54,6 +58,10 @@ func (hs *HTTPServer) Init() error {
hs.log = log.New("http.server")
hs.cache = gocache.New(5*time.Minute, 10*time.Minute)
hs.streamManager = live.NewStreamManager()
hs.macaron = hs.newMacaron()
hs.registerRoutes()
return nil
}
@ -61,10 +69,7 @@ func (hs *HTTPServer) Run(ctx context.Context) error {
var err error
hs.context = ctx
hs.streamManager = live.NewStreamManager()
hs.macaron = hs.newMacaron()
hs.registerRoutes()
hs.applyRoutes()
hs.streamManager.Run(ctx)
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)

View File

@ -66,7 +66,21 @@ func UpsertUser(cmd *m.UpsertUserCommand) error {
}
}
return syncOrgRoles(cmd.Result, extUser)
err = syncOrgRoles(cmd.Result, extUser)
if err != nil {
return err
}
err = bus.Dispatch(&m.SyncTeamsCommand{
User: cmd.Result,
ExternalUser: extUser,
})
if err == bus.ErrHandlerNotFound {
return nil
}
return err
}
func createUser(extUser *m.ExternalUserInfo) (*m.User, error) {

View File

@ -163,6 +163,7 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo
Name: fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName),
Login: ldapUser.Username,
Email: ldapUser.Email,
Groups: ldapUser.MemberOf,
OrgRoles: map[int64]m.RoleType{},
}
@ -194,6 +195,7 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo
ExternalUser: extUser,
SignupAllowed: setting.LdapAllowSignup,
}
err := bus.Dispatch(userQuery)
if err != nil {
return nil, err

View File

@ -1,6 +1,7 @@
package login
import (
"context"
"crypto/tls"
"testing"
@ -14,6 +15,14 @@ func TestLdapAuther(t *testing.T) {
Convey("When translating ldap user to grafana user", t, func() {
var user1 = &m.User{}
bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.UpsertUserCommand) error {
cmd.Result = user1
cmd.Result.Login = "torkelo"
return nil
})
Convey("Given no ldap group map match", func() {
ldapAuther := NewLdapAuthenticator(&LdapServerConf{
LdapGroups: []*LdapGroupToOrgRole{{}},
@ -23,8 +32,6 @@ func TestLdapAuther(t *testing.T) {
So(err, ShouldEqual, ErrInvalidCredentials)
})
var user1 = &m.User{}
ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) {
ldapAuther := NewLdapAuthenticator(&LdapServerConf{
LdapGroups: []*LdapGroupToOrgRole{
@ -96,7 +103,6 @@ func TestLdapAuther(t *testing.T) {
})
Convey("When syncing ldap groups to grafana org roles", t, func() {
ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) {
ldapAuther := NewLdapAuthenticator(&LdapServerConf{
LdapGroups: []*LdapGroupToOrgRole{
@ -322,6 +328,10 @@ func ldapAutherScenario(desc string, fn scenarioFunc) {
bus.AddHandler("test", UpsertUser)
bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.SyncTeamsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *m.GetUserByAuthInfoQuery) error {
sc.getUserByAuthInfoQuery = cmd
sc.getUserByAuthInfoQuery.Result = &m.User{Login: cmd.Login}

View File

@ -42,6 +42,7 @@ type RemoveTeamMemberCommand struct {
type GetTeamMembersQuery struct {
OrgId int64
TeamId int64
UserId int64
Result []*TeamMemberDTO
}

View File

@ -19,6 +19,7 @@ type ExternalUserInfo struct {
Email string
Login string
Name string
Groups []string
OrgRoles map[int64]RoleType
}
@ -70,3 +71,8 @@ type GetAuthInfoQuery struct {
Result *UserAuth
}
type SyncTeamsCommand struct {
ExternalUser *ExternalUserInfo
User *User
}

View File

@ -4,6 +4,8 @@ import (
"context"
"reflect"
"sort"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
type Descriptor struct {
@ -57,13 +59,21 @@ type CanBeDisabled interface {
// BackgroundService should be implemented for services that have
// long running tasks in the background.
type BackgroundService interface {
// Run starts the background process of the service after `Init` have been called
// on all services. The `context.Context` passed into the function should be used
// to subscribe to ctx.Done() so the service can be notified when Grafana shuts down.
Run(ctx context.Context) error
}
// DatabaseMigrator allows the caller to add migrations to
// the migrator passed as argument
type DatabaseMigrator interface {
// AddMigrations allows the service to add migrations to
// the database migrator.
AddMigration(mg *migrator.Migrator)
}
// IsDisabled takes an service and return true if its disabled
func IsDisabled(srv Service) bool {
canBeDisabled, ok := srv.(CanBeDisabled)

View File

@ -50,4 +50,5 @@ func addTeamMigrations(mg *Migrator) {
mg.AddMigration("Add column email to team table", NewAddColumnMigration(teamV1, &Column{
Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190,
}))
}

View File

@ -132,6 +132,13 @@ func (ss *SqlStore) Init() error {
migrator := migrator.NewMigrator(x)
migrations.AddMigrations(migrator)
for _, descriptor := range registry.GetServices() {
sc, ok := descriptor.Instance.(registry.DatabaseMigrator)
if ok {
sc.AddMigration(migrator)
}
}
if err := migrator.Start(); err != nil {
return fmt.Errorf("Migration failed err: %v", err)
}

View File

@ -268,7 +268,15 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error {
query.Result = make([]*m.TeamMemberDTO, 0)
sess := x.Table("team_member")
sess.Join("INNER", "user", fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
sess.Where("team_member.org_id=? and team_member.team_id=?", query.OrgId, query.TeamId)
if query.OrgId != 0 {
sess.Where("team_member.org_id=?", query.OrgId)
}
if query.TeamId != 0 {
sess.Where("team_member.team_id=?", query.TeamId)
}
if query.UserId != 0 {
sess.Where("team_member.user_id=?", query.UserId)
}
sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login")
sess.Asc("user.login", "user.email")

View File

@ -22,6 +22,7 @@ class Settings {
disableUserSignUp: boolean;
loginHint: any;
loginError: any;
enterprise: boolean;
constructor(options) {
var defaults = {

View File

@ -1,22 +1,22 @@
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<h3 class="page-sub-heading">Team Details</h3>
<h3 class="page-sub-heading">Team Details</h3>
<form name="teamDetailsForm" class="gf-form-group">
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">Name</span>
<input type="text" required ng-model="ctrl.team.name" class="gf-form-input max-width-22">
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">
Email
<info-popover mode="right-normal">
This is optional and is primarily used for allowing custom team avatars.
</info-popover>
</span>
<input class="gf-form-input max-width-22" type="email" ng-model="ctrl.team.email" placeholder="email@test.com">
</div>
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">
Email
<info-popover mode="right-normal">
This is optional and is primarily used for allowing custom team avatars.
</info-popover>
</span>
<input class="gf-form-input max-width-22" type="email" ng-model="ctrl.team.email" placeholder="email@test.com">
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Update</button>
@ -26,42 +26,80 @@
<div class="gf-form-group">
<h3 class="page-heading">Team Members</h3>
<form name="ctrl.addMemberForm" class="gf-form-group">
<form name="ctrl.addMemberForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Add member</span>
<!--
Old picker
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
-->
<select-user-picker class="width-7" handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
<!--
Old picker
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
-->
<select-user-picker class="width-7" handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
</div>
</form>
<table class="filter-table" ng-show="ctrl.teamMembers.length > 0">
<thead>
<tr>
<th></th>
<th>Username</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tr ng-repeat="member in ctrl.teamMembers">
<td class="width-4 text-center link-td">
<img class="filter-table__avatar" ng-src="{{member.avatarUrl}}"></img>
</td>
<td>{{member.login}}</td>
<td>{{member.email}}</td>
<td style="width: 1%">
<a ng-click="ctrl.removeTeamMember(member)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
<div>
<em class="muted" ng-hide="ctrl.teamMembers.length > 0">
This team has no members yet.
</em>
</div>
<thead>
<tr>
<th></th>
<th>Username</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tr ng-repeat="member in ctrl.teamMembers">
<td class="width-4 text-center link-td">
<img class="filter-table__avatar" ng-src="{{member.avatarUrl}}"></img>
</td>
<td>{{member.login}}</td>
<td>{{member.email}}</td>
<td style="width: 1%">
<a ng-click="ctrl.removeTeamMember(member)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
<div>
<em class="muted" ng-hide="ctrl.teamMembers.length > 0">
This team has no members yet.
</em>
</div>
</div>
<div class="gf-form-group" ng-if="ctrl.enterprise">
<h3 class="page-heading">Team Group Mapping</h3>
<form name="ctrl.addGroupForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Add group</span>
<input class="gf-form-input max-width-22" type="text" ng-model="ctrl.newGroupId">
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.addGroup()">Add</button>
</div>
</form>
<table class="filter-table" ng-show="ctrl.teamGroups.length > 0">
<thead>
<tr>
<th>Group</th>
<th></th>
</tr>
</thead>
<tr ng-repeat="group in ctrl.teamGroups">
<td>{{group.groupId}}</td>
<td style="width: 1%">
<a ng-click="ctrl.removeGroup(group)" class="btn btn-danger btn-mini">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
<div>
<em class="muted" ng-hide="ctrl.teamGroups.length > 0">
This team has no associated groups yet.
</em>
</div>
</div>

View File

@ -1,15 +1,21 @@
import coreModule from 'app/core/core_module';
import config from 'app/core/config';
export default class TeamDetailsCtrl {
team: Team;
teamMembers: User[] = [];
navModel: any;
teamGroups: TeamGroup[] = [];
newGroupId: string;
enterprise: boolean;
/** @ngInject **/
constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) {
this.navModel = navModelSrv.getNav('cfg', 'teams', 0);
this.userPicked = this.userPicked.bind(this);
this.get = this.get.bind(this);
this.newGroupId = '';
this.enterprise = config.enterprise;
this.get();
}
@ -18,9 +24,16 @@ export default class TeamDetailsCtrl {
this.backendSrv.get(`/api/teams/${this.$routeParams.id}`).then(result => {
this.team = result;
});
this.backendSrv.get(`/api/teams/${this.$routeParams.id}/members`).then(result => {
this.teamMembers = result;
});
if (config.enterprise) {
this.backendSrv.get(`/api/teams/${this.$routeParams.id}/groups`).then(result => {
this.teamGroups = result;
});
}
}
}
@ -57,6 +70,20 @@ export default class TeamDetailsCtrl {
this.get();
});
}
addGroup() {
this.backendSrv.post(`/api/teams/${this.$routeParams.id}/groups`, { groupId: this.newGroupId }).then(() => {
this.get();
});
}
removeGroup(group: TeamGroup) {
this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/groups/${group.groupId}`).then(this.get);
}
}
export interface TeamGroup {
groupId: string;
}
export interface Team {

View File

@ -14,9 +14,9 @@ cd /go/src/github.com/grafana/grafana
echo "current dir: $(pwd)"
cd ..
git clone -b ee_build --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10
git clone -b master --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10
cd grafana-enterprise
git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f
#git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f
./build.sh
cd ../grafana