Skip to content

Commit

Permalink
Merge pull request #22 from silinternational/develop
Browse files Browse the repository at this point in the history
Release 3.0.0
  • Loading branch information
briskt authored Nov 14, 2019
2 parents 18a19e4 + 8b81a64 commit e104eb2
Show file tree
Hide file tree
Showing 13 changed files with 725 additions and 83 deletions.
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,24 @@ of the destination configuration required for Google Groups:
```

### Google Users
This destination can update User records in the Google Directory. Presently, only the user's name is available
for updating, but other fields may be added in the future. Following is an example configuration:
This destination can update User records in the Google Directory. The compare
attribute is `primaryEmail`. A limited subset of user properties are available
to be updated.

| property | Google property | Google sub-property | Google type |
|------------|-----------------|---------------------|--------------|
| id | externalIds | value | organization |
| area | locations | area | desk |
| building | locations | buildingId | desk |
| costCenter | organizations | costCenter | (not set) |
| department | organizations | department | (not set) |
| title | organizations | title | (not set) |
| phone | phones | value | work |
| manager | relations | value | manager |
| familyName | name | familyName | n/a |
| givenName | name | givenName | n/a |
Following is an example configuration listing all available fields:

```json
{
Expand Down Expand Up @@ -170,6 +186,46 @@ for updating, but other fields may be added in the future. Following is an examp
"Source": "first_name",
"Destination": "givenName",
"required": true
},
{
"Source": "id",
"Destination": "id",
"required": false
},
{
"Source": "phone",
"Destination": "phone",
"required": false
},
{
"Source": "area",
"Destination": "area",
"required": false
},
{
"Source": "building",
"Destination": "building",
"required": false
},
{
"Source": "cost_center",
"Destination": "costCenter",
"required": false
},
{
"Source": "department",
"Destination": "department",
"required": false
},
{
"Source": "title",
"Destination": "title",
"required": false
},
{
"Source": "manager",
"Destination": "manager",
"required": false
}
]
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/syncpeeps.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"github.com/silinternational/personnel-sync/googledest"

"github.com/silinternational/personnel-sync"
personnel_sync "github.com/silinternational/personnel-sync"
)

func main() {
Expand Down Expand Up @@ -73,7 +73,7 @@ func main() {
}

// Perform sync and get results
changeResults := personnel_sync.SyncPeople(source, destination, appConfig.AttributeMap, appConfig.Runtime.DryRunMode)
changeResults := personnel_sync.SyncPeople(source, destination, appConfig)

log.Printf("Sync results: %v users added, %v users updated, %v users removed, %v errors\n",
changeResults.Created, changeResults.Updated, changeResults.Deleted, len(changeResults.Errors))
Expand Down
4 changes: 2 additions & 2 deletions config.example.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"Runtime": {
"FailIfSinglePersonMissingCompareValue": false,
"DryRunMode": false
"DryRunMode": false,
"Verbosity": 1
},
"Source": {
"Type": "RestAPI",
Expand Down
51 changes: 32 additions & 19 deletions domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
"time"
)

const DefaultConfigFile = "./config.json"
const DestinationTypeGoogleGroups = "GoogleGroups"
const DestinationTypeGoogleUsers = "GoogleUsers"
const DestinationTypeWebHelpDesk = "WebHelpDesk"
const SourceTypeRestAPI = "RestAPI"
const (
DefaultConfigFile = "./config.json"
DefaultVerbosity = 5
DestinationTypeGoogleGroups = "GoogleGroups"
DestinationTypeGoogleUsers = "GoogleUsers"
DestinationTypeWebHelpDesk = "WebHelpDesk"
SourceTypeRestAPI = "RestAPI"
)

// LoadConfig looks for a config file if one is provided. Otherwise, it looks for
// a config file based on the CONFIG_PATH env var. If that is not set, it gets
Expand All @@ -36,7 +39,11 @@ func LoadConfig(configFile string) (AppConfig, error) {
return AppConfig{}, err
}

config := AppConfig{}
config := AppConfig{
Runtime: RuntimeConfig{
Verbosity: DefaultVerbosity,
},
}
err = json.Unmarshal(data, &config)
if err != nil {
log.Printf("unable to unmarshal application configuration file data, error: %s\n", err.Error())
Expand Down Expand Up @@ -114,17 +121,23 @@ func getPersonFromList(compareValue string, peopleList []Person) Person {
return Person{}
}

func personAttributesAreEqual(sp, dp Person, attributeMap []AttributeMap) bool {
caseSensitivityList := getCaseSensitivitySourceAttributeList(attributeMap)
func personAttributesAreEqual(sp, dp Person, config AppConfig) bool {
caseSensitivityList := getCaseSensitivitySourceAttributeList(config.AttributeMap)
equal := true
for key, val := range sp.Attributes {
if !stringsAreEqual(val, dp.Attributes[key], caseSensitivityList[key]) {
log.Printf("Attribute %s not equal for user %s. Case Sensitive: %v, Source: %s, Destination: %s \n",
key, sp.CompareValue, caseSensitivityList[key], val, dp.Attributes[key])
return false
if config.Runtime.Verbosity >= VerbosityMedium {
log.Printf(`User: "%s", "%s" not equal, CaseSensitive: "%t", Source: "%s", Dest: "%s"`+"\n",
sp.CompareValue, key, caseSensitivityList[key], val, dp.Attributes[key])
equal = false
} else {
log.Printf(`User: "%s" not equal`+"\n", key)
return false
}
}
}

return true
return equal
}

func stringsAreEqual(val1, val2 string, caseSensitive bool) bool {
Expand All @@ -149,7 +162,7 @@ func getCaseSensitivitySourceAttributeList(attributeMap []AttributeMap) map[stri
// (Create, Update and Delete) based on whether they are in the slice
// of destination Person instances.
// It skips all source Person instances that have DisableChanges set to true
func GenerateChangeSet(sourcePeople, destinationPeople []Person, attributeMap []AttributeMap, idField string) ChangeSet {
func GenerateChangeSet(sourcePeople, destinationPeople []Person, config AppConfig, idField string) ChangeSet {
var changeSet ChangeSet

// Find users who need to be created or updated
Expand All @@ -165,7 +178,7 @@ func GenerateChangeSet(sourcePeople, destinationPeople []Person, attributeMap []
continue
}

if !personAttributesAreEqual(sp, destinationPerson, attributeMap) {
if !personAttributesAreEqual(sp, destinationPerson, config) {
sp.ID = destinationPerson.Attributes["id"]
changeSet.Update = append(changeSet.Update, sp)
continue
Expand All @@ -189,8 +202,8 @@ func GenerateChangeSet(sourcePeople, destinationPeople []Person, attributeMap []
// - it gets the list of people from the destination
// - it generates the lists of people to change, update and delete
// - if dryRun is true, it prints those lists, but otherwise makes the associated changes
func SyncPeople(source Source, destination Destination, attributeMap []AttributeMap, dryRun bool) ChangeResults {
desiredAttrs := GetDesiredAttributes(attributeMap)
func SyncPeople(source Source, destination Destination, config AppConfig) ChangeResults {
desiredAttrs := GetDesiredAttributes(config.AttributeMap)
sourcePeople, err := source.ListUsers(desiredAttrs)
if err != nil {
return ChangeResults{
Expand All @@ -200,7 +213,7 @@ func SyncPeople(source Source, destination Destination, attributeMap []Attribute
log.Printf(" Found %v people in source", len(sourcePeople))

// remap source people to destination attributes for comparison
sourcePeople, err = RemapToDestinationAttributes(sourcePeople, attributeMap)
sourcePeople, err = RemapToDestinationAttributes(sourcePeople, config.AttributeMap)
if err != nil {
return ChangeResults{
Errors: []string{err.Error()},
Expand All @@ -215,10 +228,10 @@ func SyncPeople(source Source, destination Destination, attributeMap []Attribute
}
log.Printf(" Found %v people in destination", len(destinationPeople))

changeSet := GenerateChangeSet(sourcePeople, destinationPeople, attributeMap, destination.GetIDField())
changeSet := GenerateChangeSet(sourcePeople, destinationPeople, config, destination.GetIDField())

// If in DryRun mode only print out ChangeSet plans and return mocked change results based on plans
if dryRun {
if config.Runtime.DryRunMode {
printChangeSet(changeSet)
return ChangeResults{
Created: uint64(len(changeSet.Create)),
Expand Down
41 changes: 23 additions & 18 deletions domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,24 @@ func TestGenerateChangeSet(t *testing.T) {
},
}

attrMaps := []AttributeMap{
{
Source: "name",
Destination: "name",
CaseSensitive: true,
},
{
Source: "school",
Destination: "school",
CaseSensitive: false,
config := AppConfig{
AttributeMap: []AttributeMap{
{
Source: "name",
Destination: "name",
CaseSensitive: true,
},
{
Source: "school",
Destination: "school",
CaseSensitive: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GenerateChangeSet(tt.args.sourcePeople, tt.args.destinationPeople, attrMaps, ""); !reflect.DeepEqual(got, tt.want) {
got := GenerateChangeSet(tt.args.sourcePeople, tt.args.destinationPeople, config, "")
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GenerateChangeSet() = %v, want %v", got, tt.want)
}
})
Expand Down Expand Up @@ -197,16 +200,18 @@ func TestIDSetForUpdate(t *testing.T) {
},
}

attributeMap := []AttributeMap{
{
Source: "email",
Destination: "email",
Required: true,
CaseSensitive: false,
config := AppConfig{
AttributeMap: []AttributeMap{
{
Source: "email",
Destination: "email",
Required: true,
CaseSensitive: false,
},
},
}

changeSet := GenerateChangeSet(sourcePeople, destinationPeople, attributeMap, "id")
changeSet := GenerateChangeSet(sourcePeople, destinationPeople, config, "id")
if len(changeSet.Create) != 1 {
t.Error("Change set should include one person to be created.")
}
Expand Down
2 changes: 1 addition & 1 deletion googledest/google_groups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"reflect"
"testing"

"github.com/silinternational/personnel-sync"
personnel_sync "github.com/silinternational/personnel-sync"
admin "google.golang.org/api/admin/directory/v1"
)

Expand Down
Loading

0 comments on commit e104eb2

Please sign in to comment.