3rd-party-integrations / github-team-sync Goto Github PK
View Code? Open in Web Editor NEWSync GitHub teams to groups in Active Directory, LDAP, Okta, OneLogin or AzureAD when using any authentication method for GitHub.
License: MIT License
Sync GitHub teams to groups in Active Directory, LDAP, Okta, OneLogin or AzureAD when using any authentication method for GitHub.
License: MIT License
In some places we log exceptions and continue forward, here for example https://github.com/github/github-team-sync/blob/5d50752e9f1b812a9ddfe9f97ee666d3c3f349ac/githubapp/ldap.py#L87
Some of the errors printed currently are not that useful "mail" for instance when the actual exception is a missing attribute on the object,
We should clean this up.
Hi Team,
We recently migrated our GitHub Enterprise Server authentication from LDAP to SAML(Azure AD) . We are now using github-team-sync to sync AD group members with GitHub Teams. When we run the team sync it runs first time without any issues and after the initial run during idle time we are getting socket sending error[Errno 32] Broken pipe and the next runs are not running due to the error. Any help would be great helpful. Thanks in advance.
Error from logs:
Processing Team: GITHUB TEAM NAME
socket sending error[Errno 32] Broken pipe
If some users don't have a mail attribute (usually bot users) then it will fail to sync them with an error when it tries to access the empty attribute
When running apk add pipenv
it is failing with the following error:
/ # apk add pipenv
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
ERROR: unsatisfiable constraints:
pipenv (missing):
required by: world[pipenv]
As a fix, we can remove pipenv
from the apk add
and replace it with RUN pip install --no-cach-dir pipenv
, which works properly
Hi Team,
We recently migrated our GitHub Enterprise Server authentication from LDAP to SAML(Azure AD) . We are now using github-team-sync to sync AD group members with GitHub Teams. When we run the team sync it runs first time without any issues and after the initial run during idle time we are getting socket sending error[Errno 32] Broken pipe and the next runs are not running due to the error. Any help would be great helpful. Thanks in advance.
Error from logs:
Processing Team: GITHUB TEAM NAME
socket sending error[Errno 32] Broken pipe
LDAP_SERVER_PORT is currently not used and the port and protocol has to be included in the LDAP_SERVER_HOST
the PORT env var has to be set though otherwise an error is thrown
This variable will add users to the org if they are in the IDP team if it is true, defaults to false https://github.com/github/github-team-sync/blob/main/githubapp/__init__.py#L41
In the example syncmap.yml.example, it is referring to the LDAP group by a key of ldap:
but in the load_custom_map method it is looking for a key of directory:
. Which is the proper key and I'll submit a PR for fixing the code or documentation?
Once I updated my syncmap.yml to look like this, the syncs started working:
---
mapping:
- github: team1
directory: ldapgroup1
- github: team2
directory: ldapgroup2
The hardcoded value github_username
is used instead of the custom attribute USERNAME_ATTRIBUTE
. This line should be "username": getattr(user.profile, self.USERNAME_ATTRIBUTE),
At present, this script requires each team and group to be sync'ed separately. This means creating a wrapper to run python SAMLTeamSyncAD.py -g <ad_group> -t <gh_team> -o <gh_org> -s
repeatedly to sync multiple teams. You can accomplish this with a wrapper script, but it would be ideal if this script could handle the mapping of multiples at once, fed via YAML
When running against large installations with thousands of groups, it takes a significant amount of time to iterate over groups and may timeout after 40 minutes. In order to alleviate this problem, multiprocessing
should be implemented for parallel tasks
While synchronizing teams is a great help to organizations that need to centralize access, it doesn't address the issue where a person leaves the company and retains an account (particularly prevalent with GHES, GHAE and GHEC-EMU). In order to ensure that users who have been disabled in the IdP are also suspended in GitHub, we can utilize the enterprise-admin API to disable the user.
Hi,
in many cases, our GitHub and LDAP users differ in case sensitivity.
It would be great to have an "--ignore-case" option, which replaces the lines
add_users = list(set(ad_members) - set(ghe_members))
and
remove_users = list(set(ghe_members) - set(ad_members))
by a more complex implementation.
Can you imagine to have this feature added to the script?
I implemented a quick and dirty function, which does the job (couldn't come up with a better function name):
def get_substract_list(list1, list2):
result=[]
lower_list2 = list(set(i.lower() for i in list2))
for l in set(list1):
lower_l = l.lower()
if lower_l not in lower_list2:
result.append(l)
return result
First, this should be configurable via an option, and '-i' (like 'ignore case' in the grep command) is already used with different semantics.
Second, I would rather have methods like get_all_group_members()
and get_new_group_members()
(replacing get_group_members()
) in class ADSync
, instead of my utility method. This would require a bigger refactoring, which I don't have the time to.
What do you think? I could open a pull request with my 'quick and dirty' solution. Which option name do you propose?
With version 2.0 working as a GitHub app, we'll need to be notified when it fails or when the threshold for change is exceeded (#23).
This will help with:
I've set up my .env file to LDAP config and the problem I'm having is that users are being added to all teams in organization, not the one I described in syncmap.yml.
syncmap.yml:
---
mapping:
- github: test
directory: github_users
test is a team I made in my organization, github_users is a LDAP group I want to sync there.
Hi,
I played around with this sync tool and got this error:
Traceback (most recent call last): File "SAMLTeamSyncAD.py", line 198, in <module> main() File "SAMLTeamSyncAD.py", line 108, in main group_members = adsync.get_group_members(args.ad_group) File "SAMLTeamSyncAD.py", line 54, in get_group_members member_list.append(self.get_attr_by_dn(member)) File "SAMLTeamSyncAD.py", line 66, in get_attr_by_dn attributes=['sAMAccountName']) File "/home/s221676/.local/lib/python3.6/site-packages/ldap3/core/connection.py", line 786, in search check_names=self.check_names) File "/home/s221676/.local/lib/python3.6/site-packages/ldap3/operation/search.py", line 372, in search_operation request['filter'] = compile_filter(parse_filter(search_filter, schema, auto_escape, auto_encode, validator, check_names).elements[0]) # parse the searchFilter string and compile it starting from the root node File "/home/s221676/.local/lib/python3.6/site-packages/ldap3/operation/search.py", line 215, in parse_filter raise LDAPInvalidFilterError('malformed filter') ldap3.core.exceptions.LDAPInvalidFilterError: malformed filter
I am wondering, how this search query ever worked:
https://github.com/github/saml-ldap-team-sync/blob/master/SAMLTeamSyncAD.py#L65
I was using
user_filter2: (&(objectClass=USER)(dn={userdn}))
where {userdn} is replaced by e. g. 'CN=John Smith,OU=,,OU=,OU=__,DC=example,DC=com'
I have ldap3 2.6.1 installed
According to this issue in ldap3 (cannatag/ldap3#187), searching by 'dn' only works, if 'dn' is used as the search base, not as a search filter.
The python script works fine, after I changed it according to the suggestion in that issue.
Can anyone explain, if there is a way to use the script correctly in its current implementation or would you like me to open a pull request with my fix?
Best regards
Stefan
We experienced an issue within our AD environment where the sync script hit a heavily loaded AD server. The result was a query return of zero users for a group that had multiple users in it. The script then proceeded to remove all of the users from the GitHub team.
On the next run the script hit a lightly loaded AD server and repopulated the team but the damage was done.
The request here is to add a check for a null list. If the list is empty, report an error and proceed to the next sync instead of stripping the users from the GitHub team.
Our org has some nested AD groups. Here's an example group structure:
Group A
* User 1
* Group B
Group B
* User 2
* User 3
If I were to sync Group A
to a GitHub team, I would like to see Users 1, 2, and 3 to be added to the team. Currently I'm seeing an exception when encountering the Group B
member because it doesn't have a sAMAccountName
.
In similar spirit to #6, having some safety precautions around adding/removing large groups of individuals would be very valuable for larger organizations.
For example, after initially syncing a team then setting up this tool to run every 15min in order to continuously sync incremental changes to memberships, I wouldn't expect more than 100 people to ever be added or removed in a single execution. If so, it's probably a mistake and I'd prefer an exception to be thrown so a human can investigate why such a large change in membership is occurring before it actually occurs.
A possible solution that comes to mind is a "circuit breaker" or some kind of configurable threshold that can be set to say "never add/remove any more than N members at a time".
In production environments, using certificates will be much more necessary. https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https has some ideas, and should be documented here in the Wiki
The README says that the following permissions are required for AAD:
Directory.Read.All
Group.Read.All
GroupMember.Read.All
Organization.Read.All
User.Read.All
Looking at the code, it seems like the only permissions actually needed by the API paths used are GroupMember.Read.All
and User.Read.All
.
Why are these other permissions also documented as being required?
Traceback (most recent call last):
File "app.py", line 11, in
from githubapp import GitHubApp, LDAPClient, TEST_MODE, CRON_INTERVAL
File "/Users/sravula/Downloads/saml-ldap-team-sync-2.0/githubapp/init.py", line 27, in
TEST_MODE = strtobool(os.environ.get('TEST_MODE', False))
File "/usr/local/Cellar/python/3.7.4_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/distutils/util.py", line 301, in strtobool
val = val.lower()
AttributeError: 'bool' object has no attribute 'lower'
OSXSRAVUMBP15:saml-ldap-team-sync-2.0 sravula$
When installing this app to multiple orgs the app fails to authenticate beyond the first org and produces the following error: 403 Resource not accessible by integration
The readme mentions Python 3.4+ but the Dockerfile specifies python:2
Users are reporting that LDAP and AD users with "(" or ")" in their name are failing to sync.
No relevant logs or exceptions were attached to the report.
Reading the YAML file fails with a utf-8
error:
Traceback (most recent call last):
File "SAMLTeamSyncAD.py", line 195, in <module>
main()
File "SAMLTeamSyncAD.py", line 100, in main
adsync = ADSync(settings_file)
File "SAMLTeamSyncAD.py", line 12, in __init__
settings = yaml.load(stream, Loader=yaml.FullLoader)
File "/usr/local/lib/python3.6/dist-packages/yaml/__init__.py", line 112, in load
loader = Loader(stream)
File "/usr/local/lib/python3.6/dist-packages/yaml/loader.py", line 24, in __init__
Reader.__init__(self, stream)
File "/usr/local/lib/python3.6/dist-packages/yaml/reader.py", line 85, in __init__
self.determine_encoding()
File "/usr/local/lib/python3.6/dist-packages/yaml/reader.py", line 124, in determine_encoding
self.update_raw()
File "/usr/local/lib/python3.6/dist-packages/yaml/reader.py", line 178, in update_raw
data = self.stream.read(size)
File "/usr/lib/python3.6/codecs.py", line 321, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
To fix this, change with open(settings_file, 'r') as stream:
to with open(settings_file, 'rb') as stream:
so it reads as bytes
Currently team sync can only add users to a team if they are already a member of the organization.
Is it possible to have the team sync app also invite members if they are not a member yet?
There are a number of refactorings that are necessary. Release 2.0 intends to target the following:
settings.yml
into the .env
fileWe would like to run this application as a docker container in a kubernetes Cluster and right now we would have to build the image ourselves.
Since GitHub makes it really easy to publish docker images it would be great if this functionality could be added to the pipeline of a release (possibly main branch too).
If you want I can provide a pull request, configuration for making the artifact public would have to be done by the maintainers though...
The current version supports Python 2.x. In order to support Python 3.x we need to do the following:
byte
responses from Active Directoryfrom urlparse import urlparse
for Python 2 and from urllib.parse import urlparse
for Python 3[('CN=team-sync-demo,OU=Groups,DC=example,DC=com',
{'cn': [b'team-sync-demo'],
'dSCorePropagationData': [b'16010101000000.0Z'],
'distinguishedName': [b'CN=team-sync-demo,OU=Groups,DC=example,DC=com'],
'groupType': [b'-2147483646'],
'instanceType': [b'4'],
'member': [b'CN=Thomas Hughes,CN=Users,DC=example,DC=com',
b'CN=Jonathan Cardona,CN=Users,DC=example,DC=com'],
'name': [b'team-sync-demo'],
'objectCategory': [b'CN=Group,CN=Schema,CN=Configuration,DC=example,D'
b'C=com'],
'objectClass': [b'top', b'group'],
'objectGUID': [b'\xc0(\xf6\x06\x1dihE\xb4\xe2S\xaajH\xe6\x11'],
'objectSid': [b'\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xca-M\xe2'
b'\xef\xbb\n\x11qU\x08\xf4u\x04\x00\x00'],
'sAMAccountName': [b'team-sync-demo'],
'sAMAccountType': [b'268435456'],
'uSNChanged': [b'126446'],
'uSNCreated': [b'126441'],
'whenChanged': [b'20181112162105.0Z'],
'whenCreated': [b'20181112162053.0Z']})]
[('CN=team-sync-demo,OU=Groups,DC=example,DC=com',
{'cn': ['team-sync-demo'],
'dSCorePropagationData': ['16010101000000.0Z'],
'distinguishedName': ['CN=team-sync-demo,OU=Groups,DC=example,DC=com'],
'groupType': ['-2147483646'],
'instanceType': ['4'],
'member': ['CN=Thomas Hughes,CN=Users,DC=example,DC=com',
'CN=Jonathan Cardona,CN=Users,DC=example,DC=com'],
'name': ['team-sync-demo'],
'objectCategory': ['CN=Group,CN=Schema,CN=Configuration,DC=example,DC=com'],
'objectClass': ['top', 'group'],
'objectGUID': ['\xc0(\xf6\x06\x1dihE\xb4\xe2S\xaajH\xe6\x11'],
'objectSid': ['\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\xca-M\xe2\xef\xbb\n\x11qU\x08\xf4u\x04\x00\x00'],
'sAMAccountName': ['team-sync-demo'],
'sAMAccountType': ['268435456'],
'uSNChanged': ['126446'],
'uSNCreated': ['126441'],
'whenChanged': ['20181112162105.0Z'],
'whenCreated': ['20181112162053.0Z']})]
With the byte
responses in Python 3 the subsequent lookups fail
{'desc': 'Referral', 'info': "Referral:\nldap://example.com'/b'CN=Thomas%20Hughes,CN=Users,DC=example,DC=com'"}
{'desc': 'Referral', 'info': "Referral:\nldap://example.com'/b'CN=Jonathan%20Cardona,CN=Users,DC=example,DC=com'"}
github-demo
AD Group: team-sync-demo
---------------
GitHub Team: team-sync-demo
---------------
hollywood
But in Python 2, the lookups succeed
AD Group: team-sync-demo
---------------
iamhughes
hollywood
GitHub Team: team-sync-demo
---------------
iamhughes
hollywood
Hi,
since these lines (7e11e7c#diff-8cf96b3efbca5b77c8394b54670de4ddR93-R94) were added, I get an error, running the script:
Traceback (most recent call last):
File "./SAMLTeamSyncAD.py", line 197, in <module>
main()
File "./SAMLTeamSyncAD.py", line 94, in main
help="Skip empty groups in Active Directory, to avoid emptying the GitHub group")
File "/usr/lib/python3.7/argparse.py", line 1362, in add_argument
action = action_class(**kwargs)
File "/usr/lib/python3.7/argparse.py", line 869, in __init__
raise ValueError('nargs must be %r to supply const' % OPTIONAL)
ValueError: nargs must be '?' to supply const
I have these python modules installed:
Package Version
----------------- ----------
certifi 2019.11.28
cffi 1.14.0
chardet 3.0.4
cryptography 2.8
defusedxml 0.6.0
Deprecated 1.2.7
idna 2.9
jira 2.0.0
ldap3 2.7
oauthlib 3.1.0
pbr 5.4.4
pip 20.0.2
pyasn1 0.4.8
pycparser 2.20
PyGithub 1.47
PyJWT 1.7.1
PyYAML 5.3.1
requests 2.23.0
requests-oauthlib 1.3.0
requests-toolbelt 0.9.1
setuptools 46.0.0
six 1.14.0
urllib3 1.25.8
wrapt 1.12.1
The error occurs, no matter if I run the script with or without --skip-null option.
I didn't take the time yet, to dive deeper. Does anyone know, why this is happening?
When an account does not have an email, say a service account, it will throw an exception when trying to use the LDAP_USER_MAIL_ATTRIBUTE
, even if we are not comparing users by email account.
See https://github.com/github/github-team-sync/blob/main/githubapp/ldap.py#L80
One of the major SAML identity providers is PingOne. It would be useful for this app to have the ability to synchronize groups with GitHub
If I have security groups in Azure AD with names say
ABC-Devs
ABC-Devs-Java
ABC-Devs-Python
And corresponding teams in Github to be synced, there is a high chance that ABC-Devs
may return ABC-Devs-Java
or ABC-Devs-Python
because of the startswith
filter condition. It should instead be a eq
filter condition:
# f"{self.AZURE_API_ENDPOINT}/groups?$filter=startswith(displayName,'{group_name}')",
f"{self.AZURE_API_ENDPOINT}/groups?$filter=displayName eq '{group_name}'",
We would like to link email address from GitHub public profile to SMAL IDAP email attribute.
Currently the settings.yml
file is baked into the docker image and contains secrets (slack token, github token, AD bind password). Allowing these to be set from environment variables would allow us to pass secrets into the container at runtime, which makes the image more secure and portable.
When looking up groups in AD, if there are more than 1000 users that are returned it will err out with the following message:
Traceback (most recent call last):
File "ad_user.py", line 71, in <module>
print('|--------------------------------------------------------------------------------|')
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 852, in search_s
return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout)
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 846, in search_ext_s
return self.result(msgid,all=1,timeout=timeout)[1]
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 738, in result
resp_type, resp_data, resp_msgid = self.result2(msgid,all,timeout)
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 742, in result2
resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all,timeout)
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 749, in result3
resp_ctrl_classes=resp_ctrl_classes
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 756, in result4
ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 329, in _ldap_call
reraise(exc_type, exc_value, exc_traceback)
File "/usr/local/lib/python2.7/site-packages/ldap/ldapobject.py", line 313, in _ldap_call
result = func(*args,**kwargs)
ldap.SIZELIMIT_EXCEEDED: {'desc': u'Size limit exceeded'}
The size limit is configured on the server side, and in some cases cannot be changed. So we'll need to implement pagination to be able to gather all user accounts in this case
We should add a license to this open source project.
The --list
is helpful to see group & team members, but for large groups of hundreds of people it's difficult to know what changes are actually going to be made whenever I run --sync
since I can't easily eyeball the difference between the group/team members. Adding a dryrun option will allow me to know what the tool will do before it actually makes any changes.
Using the sample config produces the following error:
Traceback (most recent call last):
File "SAMLTeamSyncAD.py", line 195, in <module>
main()
File "SAMLTeamSyncAD.py", line 100, in main
adsync = ADSync(settings_file)
File "SAMLTeamSyncAD.py", line 25, in __init__
self.AD_PAGE_SIZE = settings['ldap']['page_size']
KeyError: 'page_size'
Add the following to the settings.yml
file:
ldap:
page_size: 1000
This app currently uses github3.py
for API interactions with GitHub. Since GitHub officially released ghapi
, it will be less maintenance and fewer missing features to use this official library in the future
Hi everyone,
This is more like a question than an issue. We are experiencing the following warning in our logs:
WARNING:apscheduler.scheduler:Execution of job "sync_all_teams (trigger: cron[month='*', day='*', day_of_week='*', hour='*', minute='0'], next run at: 2022-02-08 06:00:00 UTC)" skipped: maximum number of running instances reached (1)
WARNING:apscheduler.scheduler:Execution of job "sync_all_teams (trigger: cron[month='*', day='*', day_of_week='*', hour='*', minute='0'], next run at: 2022-02-08 06:00:00 UTC)" skipped: maximum number of running instances reached (1)
WARNING:apscheduler.scheduler:Execution of job "sync_all_teams (trigger: cron[month='*', day='*', day_of_week='*', hour='*', minute='0'], next run at: 2022-02-08 07:00:00 UTC)" skipped: maximum number of running instances reached (1)
WARNING:apscheduler.scheduler:Execution of job "sync_all_teams (trigger: cron[month='*', day='*', day_of_week='*', hour='*', minute='0'], next run at: 2022-02-08 07:00:00 UTC)" skipped: maximum number of running instances reached (1)
WARNING:apscheduler.scheduler:Execution of job "sync_all_teams (trigger: cron[month='*', day='*', day_of_week='*', hour='*', minute='0'], next run at: 2022-02-08 08:00:00 UTC)" skipped: maximum number of running instances reached (1)
Could you help us to resolve the warning? Maybe some configuration needs to be applied? Also there is another question. Is it possible this skipping of cron job task to result in not updating user if there is new change in the LDAP group?
Best Regards,
Iliyan
We should document a way to configure and run github-team-sync
via GitHub Actions. ๐
[root@eq1vjenbslave13 saml-ldap-team-sync]# python3 app.py
Traceback (most recent call last):
File "app.py", line 14, in
github_app = GitHubApp(app)
File "/opt/jenkins/virtualenv/saml-ldap-team-sync/githubapp/core.py", line 32, in init
self.init_app(app)
File "/opt/jenkins/virtualenv/saml-ldap-team-sync/githubapp/core.py", line 74, in init_app
self.load_env(app)
File "/opt/jenkins/virtualenv/saml-ldap-team-sync/githubapp/core.py", line 36, in load_env
app.config['GITHUBAPP_ID'] = int(os.environ['APP_ID'])
File "/usr/lib64/python3.6/os.py", line 669, in getitem
raise KeyError(key) from None
KeyError: 'APP_ID'
search_dn is being parsed by class ADSync but search_dn is not shown in the example settings.yml. What should be the value of search_dn or is this a typo?
Traceback (most recent call last):
File "SAMLTeamSyncAD.py", line 269, in <module>
main()
File "SAMLTeamSyncAD.py", line 170, in main
adsync = ADSync(settings_file)
File "SAMLTeamSyncAD.py", line 28, in __init__
self.AD_SEARCH_DN = settings['ldap']['search_dn']
KeyError: 'search_dn'
Hi,
This issue is related to the github3 python library. Not sure if this can be taken care of at the team sync app level but currently due to deprecated Teams API Endpoints the team sync is unable to sync the GitHub Teams. It's throwing the below error -
Unable to sync team: 404 You are receiving this error due to a service brownout. Please see https://github.blog/changelog/2022-02-22-sunset-notice-deprecated-teams-api-endpoints/ for more information.
FYI... the same issue is reported at sigmavirus24/github3.py#1080
The app has the ability to open issues when there are exceptions. We should allow for custom issue templates to be utlized
Larrge groups in Azure AD with more than 100 members and @odata.nextLink is not getting processed
Checking to see if SYNC_SCHEDULE can be set more frequently and how often would it accept
Current default 0 * * * *
. So something like */5 * * * *
would be acceptable or not? I suppose we should avoid getting it too frequent so love to hear any kind of threshold would imply here.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.