bonclay7 / aws-amicleaner Goto Github PK
View Code? Open in Web Editor NEWCleanup your old unused ami and related snapshots
License: MIT License
Cleanup your old unused ami and related snapshots
License: MIT License
I have thousands of AMIs to clean on my AWS account. But because AWS API has some rate limits, I get
botocore.exceptions.ClientError: An error occurred (RequestLimitExceeded) when calling the DeregisterImage operation (reached max retries: 4): Request limit exceeded.
I made a pull request to increase max_attempts for boto3 (default is 4). It uses exponential backoff between API calls when it gets RequestLimitExceeded
this way I can remove thousands of AMIs without app exception.
Invalid type for parameter SnapshotId, value: None, type: <type 'NoneType'>, valid types: <type 'basestring'>
in function remove_amis
Ami Snapshots:
{'volume_size': 80, 'snapshot_id': 'snap-xxxxx', 'encrypted': True, 'volume_type': 'gp2', 'device_name': '/dev/sda1'}
snap-xxxx deleted
{'volume_size': 80, 'snapshot_id': None, 'encrypted': True, 'volume_type': 'gp2', 'device_name': 'xvdb'}
Invalid type for parameter SnapshotId, value: None, type: <type 'NoneType'>, valid types: <type 'basestring'>
Hello, I found this bug. I'm guessing it's because there are too many (?)
Retrieving AMIs to clean ...
Traceback (most recent call last):
File "/home/alex/.local/bin/amicleaner", line 8, in
sys.exit(main())
File "/home/alex/.local/lib/python2.7/site-packages/amicleaner/cli.py", line 195, in main
app.run_cli()
File "/home/alex/.local/lib/python2.7/site-packages/amicleaner/cli.py", line 165, in run_cli
candidates = self.prepare_candidates()
File "/home/alex/.local/lib/python2.7/site-packages/amicleaner/cli.py", line 66, in prepare_candidates
candidates_amis = candidates_amis or self.fetch_candidates()
File "/home/alex/.local/lib/python2.7/site-packages/amicleaner/cli.py", line 53, in fetch_candidates
excluded_amis += f.fetch_zeroed_asg()
File "/home/alex/.local/lib/python2.7/site-packages/amicleaner/fetch.py", line 71, in fetch_zeroed_asg
LaunchConfigurationNames=zeroed_lcs
File "/home/alex/.local/lib/python2.7/site-packages/botocore/client.py", line 316, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/home/alex/.local/lib/python2.7/site-packages/botocore/client.py", line 599, in _make_api_call
api_params, operation_model, context=request_context)
File "/home/alex/.local/lib/python2.7/site-packages/botocore/client.py", line 647, in _convert_to_request_dict
api_params, operation_model)
File "/home/alex/.local/lib/python2.7/site-packages/botocore/validate.py", line 297, in serialize_to_request
raise ParamValidationError(report=report.generate_report())
botocore.exceptions.ParamValidationError: Parameter validation failed:
Invalid length for parameter LaunchConfigurationNames[11], value: 0, valid range: 1-inf
Invalid length for parameter LaunchConfigurationNames[12], value: 0, valid range: 1-inf
Invalid length for parameter LaunchConfigurationNames[13], value: 0, valid range: 1-inf
Thanks,
Alex.
If the user uses the -f option, this should forcefully clean the snapshots as well.
When AMIs are removed via the UI, it would be great to have a way to remove snapshot images no longer associated with valid AMIs.
Allow the user to pass a region from the CLI instead of only relying on the credentials provider.
I would like to allow certain tags such as branch
with value of master
to never be cleaned up.
~/P/o/scripts ❯❯❯ amicleaner --full-report master
Traceback (most recent call last):
File "/usr/bin/amicleaner", line 11, in <module>
load_entry_point('aws-amicleaner==0.1.2', 'console_scripts', 'amicleaner')()
File "/usr/lib/python3.6/site-packages/pkg_resources/__init__.py", line 560, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/usr/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2648, in load_entry_point
return ep.load()
File "/usr/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2302, in load
return self.resolve()
File "/usr/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2308, in resolve
module = __import__(self.module_name, fromlist=['__name__'], level=0)
File "/usr/lib/python3.6/site-packages/amicleaner/cli.py", line 69
print TERM.bold("\nCleaning from {} AMI id(s) ...".format(
^
SyntaxError: invalid syntax
I've been using this tool for the past week without an issue, but yesterday I started getting an error message when trying to perform a clean:
Invalid length for parameter LaunchConfigurationNames[0], value: 0, valid range: 1-inf
This is the command I'm running
amicleaner --mapping-key name --mapping-values my-ami-name --keep-previous 3
when having images with
tag:
- foo: bar
- name: test
- published: false
tag:
- foo: bar
- name: test_test
- published: false
i want to be able to select only the ami's with name:test
and published:false
as i cannot be sure of if there are amis with the tag name:example
i cannot mis/abuse the --excluded-mapping-values test_test
for example.
is there an ability to just say i want only to remove the one containing 2 tags and there corosponding values.
like --include-mapping-values name=test, published:false
or why not be able to select which group you want to delete.
as you already group them anyways
a cross check over multiple accounts would be great, maybe by allowing to specify multiple profile-names via cli-arg and then looping through those.
the region can be static as those ami's would be copies anyways?
It would be nice for people trying to figure out the various mappings for their own system if it was possible to see all AMIs, even those that are going to be preserved.
After installation via pip, it required builtins module to be available which is part of future.
I believe this should be included as part of the documentation
Traceback (most recent call last):
File "/usr/local/bin/amicleaner", line 7, in <module>
from amicleaner.cli import main
File "/usr/local/lib/python2.7/dist-packages/amicleaner/cli.py", line 6, in <module>
from builtins import input
ImportError: No module named builtins
Solution:
pip install future
Hello,
First thank you for this useful tool :-)
I would like to delete all AMIs with a tag "usage" equals to "test".
Currently, as I think I understand the tool, it is not possible to do this. It is only possible to delete AMIs with specific tags (with --mapping-key tags --mapping-values <tag_names>
) and to exclude some AMI that have a specific value for a tag (with -excluded-mapping-values <tag_values>
.
It would be useful to add an option like "-included-mapping-values" to be able to delete AMIs that specifically matches some tag values.
For example, with the current syntax, it could be like :
# Delete all AMIs with a tag "usage" equals to "test"
amicleaner --mapping-key tags --mapping-values usage -included-mapping-values test
# Delete all AMIs with a tag "usage" equals to "test" and a tag "env" equals to "stage"
amicleaner --mapping-key tags --mapping-values usage env -included-mapping-values test stage
# Delete all AMIs with a tag "usage" equals to "test" and a tag "env" equals to "stage" except those with a tag "keep" equals to "true"
amicleaner --mapping-key tags --mapping-values usage env -included-mapping-values test stage -excluded-mapping-values
But the syntax can quickly become unconvenient for advanced request so I think that it will maybe be easier to pass to a key=value list for include or exclude (based on AWS filters syntax) :
amicleaner --include-filters name=aminame,tag:key1=value1,tag:key2=value2,... --exclude-filters tag:key3=value3,tag:key4=value4,...
Do you think it may be a good and feasible idea ?
Thank you
Currently if I specify --keep-previous 5
, the report outputs that all but the previous 1 will be cleaned regardless of the 5
.
Also, if query the AMIs to be cleaned by --mapping-key tags --mapping-values somevalue
, then it will correctly list the AMIs to be cleaned, but then claim during removal they are excluded due to not having tags, even though they were listed correctly in the first place due to their tags.
This is with Python3 and latest Boto3 and awsamicleaner.
When I try to pass multiple values for --excluded-mapping-values parameter I get duplicate AMIs marked for deletion on one hand and also multiple AMIs which should be excluded are wrongly added in AMIs to clean on the other hand.
amicleaner --mapping-key tags --mapping-values Scope --excluded-mapping-values dev_ready test_ready --full-report --keep-previous 0
Assuming that I have 14 AMIs with 'testing' Scope tag and 14 AMis with 'dev_ready', when I try to cleanup all 'testing' AMIs I get 28(all 14 AMIs ID are duplicated) AMIs to be removed in testing group but also few AMIs in dev_ready group which is abnormal since I excluded 'dev_ready' Scope tags.
This tool looks great, and is almost exactly what we need. One constraint we need to enforce is that we do not want to delete anything that's less than six months old.
We have a central account that creates + shares AMIs with other accounts, and want to guarantee an AMI exists for at least that amount of time.
If this can be added, that would be awesome.
Running on Mac High Sierra, python 2.7.10.
After installing with pip (successfully) I attempt to run amicleaner --help. I get the following:
Traceback (most recent call last):
File "/usr/local/bin/amicleaner", line 6, in
from pkg_resources import load_entry_point
File "/usr/local/lib/python2.7/site-packages/pkg_resources/init.py", line 3019, in
@_call_aside
File "/usr/local/lib/python2.7/site-packages/pkg_resources/init.py", line 3003, in _call_aside
f(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/pkg_resources/init.py", line 3032, in _initialize_master_working_set
working_set = WorkingSet._build_master()
File "/usr/local/lib/python2.7/site-packages/pkg_resources/init.py", line 657, in _build_master
return cls._build_from_requirements(requires)
File "/usr/local/lib/python2.7/site-packages/pkg_resources/init.py", line 670, in _build_from_requirements
dists = ws.resolve(reqs, Environment())
File "/usr/local/lib/python2.7/site-packages/pkg_resources/init.py", line 854, in resolve
raise VersionConflict(dist, req).with_context(dependent_req)
pkg_resources.ContextualVersionConflict: (botocore 1.8.49 (/usr/local/lib/python2.7/site-packages), Requirement.parse('botocore==1.7.41'), set(['awscli']))
Hello,
Do you think it's possible to delete AMI ID older than 10 days or with commit sha?
I am getting the following error:
[ec2-user@operator ~]$ sudo amicleaner --full-report
Traceback (most recent call last):
File "/usr/local/bin/amicleaner", line 11, in <module>
load_entry_point('aws-amicleaner==0.2.2', 'console_scripts', 'amicleaner')()
File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 564, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2662, in load_entry_point
return ep.load()
File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2316, in load
return self.resolve()
File "/usr/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2322, in resolve
module = __import__(self.module_name, fromlist=['__name__'], level=0)
File "/usr/local/lib/python2.7/site-packages/amicleaner/cli.py", line 6, in <module>
from builtins import input
ImportError: No module named builtins
Python 2.7
Latest AWSCLI
Calling "amicleaner -f --from-ids ami-01ba7496a691b0108" for below standing ami
{
"Architecture": "x86_64",
"CreationDate": "2022-08-08T08:15:13.000Z",
"ImageId": "ami-01ba7496a691b0108",
"ImageLocation": "643424300734/DataLake-knime 4.6.1 2022-08-08T07_35_44Z",
"ImageType": "machine",
"Public": false,
"OwnerId": "643424300734",
"PlatformDetails": "Linux/UNIX",
"UsageOperation": "RunInstances",
"State": "pending",
"BlockDeviceMappings": [{
"DeviceName": "/dev/xvda",
"Ebs": {
"DeleteOnTermination": true,
"VolumeSize": 50,
"VolumeType": "gp2",
"Encrypted": true
}
}],
"EnaSupport": true,
"Hypervisor": "xen",
"Name": "DataLake-knime 4.6.1 2022-08-08T07_35_44Z",
"RootDeviceName": "/dev/xvda",
"RootDeviceType": "ebs",
"SriovNetSupport": "simple",
"VirtualizationType": "hvm"
}
resulted in:
[2022-08-08T08:18:54.020Z] Traceback (most recent call last):
[2022-08-08T08:18:54.020Z] File "/usr/local/bin/amicleaner", line 10, in
[2022-08-08T08:18:54.020Z] sys.exit(main())
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/amicleaner/cli.py", line 195, in main
[2022-08-08T08:18:54.020Z] app.run_cli()
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/amicleaner/cli.py", line 159, in run_cli
[2022-08-08T08:18:54.020Z] self.prepare_delete_amis(self.from_ids, from_ids=True)
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/amicleaner/cli.py", line 109, in prepare_delete_amis
[2022-08-08T08:18:54.020Z] failed = AMICleaner().remove_amis_from_ids(candidates)
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/amicleaner/core.py", line 147, in remove_amis_from_ids
[2022-08-08T08:18:54.020Z] return self.remove_amis(amis)
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/amicleaner/core.py", line 120, in remove_amis
[2022-08-08T08:18:54.020Z] SnapshotId=block_device.snapshot_id
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/botocore/client.py", line 508, in _api_call
[2022-08-08T08:18:54.020Z] return self._make_api_call(operation_name, kwargs)
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/botocore/client.py", line 879, in _make_api_call
[2022-08-08T08:18:54.020Z] api_params, operation_model, context=request_context
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/botocore/client.py", line 940, in _convert_to_request_dict
[2022-08-08T08:18:54.020Z] api_params, operation_model
[2022-08-08T08:18:54.020Z] File "/usr/local/lib/python3.7/site-packages/botocore/validate.py", line 381, in serialize_to_request
[2022-08-08T08:18:54.020Z] raise ParamValidationError(report=report.generate_report())
[2022-08-08T08:18:54.020Z] botocore.exceptions.ParamValidationError: Parameter validation failed:
[2022-08-08T08:18:54.020Z] Invalid type for parameter SnapshotId, value: None, type: <class 'NoneType'>, valid types: <class 'str'>
amicleaner should realize there is no snapshot on its own and not try to delete it.
Hi.
We tag our builds with a tag called BuildTag
using one of these methods:
production-master
and staging-master
.bang-brown-sun
, loll-lowly-twig
, etc.In addition, we also tag them with BuiltFor
which will be production
or staging
.
The BuildTag
is determined from the names of the branches of code being deployed in that image. We have 5 code bases that a developer can pick and choose for deploying to staging for test or demonstration purposes, prior to the code being merged to that projects master branch. Each combination of the branches will generate a different BuildTag
value.
What we would like to be able to do is to be able to have our AMI counts as follow:
For a single BuildTag
, no more than 5 AMIs.
For a single BuiltFor
, no more than 20 AMIs.
So, as build_tag of production_master is built_for production, there will only be 5 AMIs available.
But for our staging builds (which have the different build_tags), each tag would only have 5 AMIs available, but overall, no more than 20.
Is this possible with the amicleaner?
amicleaner -f --full-report --keep-previous 2 --mapping-key name --excluded-mapping-values app
usage: amicleaner [-h] [-v] [--from-ids FROM_IDS [FROM_IDS ...]]
[--full-report] [--mapping-key MAPPING_KEY]
[--mapping-values MAPPING_VALUES [MAPPING_VALUES ...]]
[--keep-previous KEEP_PREVIOUS] [-f] [--check-orphans]
amicleaner: error: unrecognized arguments: --excluded-mapping-values app
$ pip install aws-amicleaner --upgrade
Collecting aws-amicleaner
Collecting blessings (from aws-amicleaner)
Collecting boto3 (from aws-amicleaner)
Using cached boto3-1.4.4-py2.py3-none-any.whl
Collecting awscli (from aws-amicleaner)
Using cached awscli-1.11.118-py2.py3-none-any.whl
Collecting prettytable (from aws-amicleaner)
Collecting boto (from aws-amicleaner)
Using cached boto-2.48.0-py2.py3-none-any.whl
Collecting argparse (from aws-amicleaner)
Using cached argparse-1.4.0-py2.py3-none-any.whl
Collecting jmespath<1.0.0,>=0.7.1 (from boto3->aws-amicleaner)
Using cached jmespath-0.9.3-py2.py3-none-any.whl
Collecting s3transfer<0.2.0,>=0.1.10 (from boto3->aws-amicleaner)
Using cached s3transfer-0.1.10-py2.py3-none-any.whl
Collecting botocore<1.6.0,>=1.5.0 (from boto3->aws-amicleaner)
Using cached botocore-1.5.81-py2.py3-none-any.whl
Collecting docutils>=0.10 (from awscli->aws-amicleaner)
Using cached docutils-0.13.1-py2-none-any.whl
Collecting colorama<=0.3.7,>=0.2.5 (from awscli->aws-amicleaner)
Using cached colorama-0.3.7-py2.py3-none-any.whl
Collecting rsa<=3.5.0,>=3.1.2 (from awscli->aws-amicleaner)
Using cached rsa-3.4.2-py2.py3-none-any.whl
Collecting PyYAML<=3.12,>=3.10 (from awscli->aws-amicleaner)
Collecting futures<4.0.0,>=2.2.0; python_version == "2.6" or python_version == "2.7" (from s3transfer<0.2.0,>=0.1.10->boto3->aws-amicleaner)
Using cached futures-3.1.1-py2-none-any.whl
Collecting python-dateutil<3.0.0,>=2.1 (from botocore<1.6.0,>=1.5.0->boto3->aws-amicleaner)
Using cached python_dateutil-2.6.1-py2.py3-none-any.whl
Collecting pyasn1>=0.1.3 (from rsa<=3.5.0,>=3.1.2->awscli->aws-amicleaner)
Using cached pyasn1-0.2.3-py2.py3-none-any.whl
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1->botocore<1.6.0,>=1.5.0->boto3->aws-amicleaner)
Using cached six-1.10.0-py2.py3-none-any.whl
Installing collected packages: blessings, jmespath, futures, docutils, six, python-dateutil, botocore, s3transfer, boto3, colorama, pyasn1, rsa, PyYAML, awscli, prettytable, boto, argparse, aws-amicleaner
Successfully installed PyYAML-3.12 argparse-1.4.0 aws-amicleaner-0.1.2 awscli-1.11.118 blessings-1.6 boto-2.48.0 boto3-1.4.4 botocore-1.5.81 colorama-0.3.7 docutils-0.13.1 futures-3.1.1 jmespath-0.9.3 prettytable-0.7.2 pyasn1-0.2.3 python-dateutil-2.6.1 rsa-3.4.2 s3transfer-0.1.10 six-1.10.0
$ amicleaner --version
-bash: amicleaner: command not found
Allow a user to pass a profile on the CLI instead of solely through env vars or credentials file.
Hi,
It's no stated anywhere that amicleaner does not run on win10. But when I try to run it first tried source 'ncurses' which can be easily installed through pip but now it tries to use 'fcntl' somehwere although I didn't spot it in your code it's probably some linux dependency and thus amicleaner fails and there's no replacement for 'fcntl'. There is 'portalocker' which can expose the same but it needs a code change.
Hey!
First, thanks for your library, it saves me some times :)
For the background, we have integrated this library in a Lambda function triggered periodically. To do it, we use: https://gist.github.com/egeloen/6fb87773a6e0b548b02a0394f4d4851a with a CloudWatch event.
Everything is working fine when using Python 2.7 but when trying to upgrade to Python 3.6, we faced an issue. Basically, on the Python 2.7 runtime, AWS have compiled Python with curses but for Python 3.6 they didn't. So, we can't upgrade as this library relies on blessings which relies on curses.
Since I'm not a Python guy, I'm not sure what is the best option here. What would you advice?
Retrieving AMIs to clean ...
Traceback (most recent call last):
File "/usr/local/bin/amicleaner", line 11, in
sys.exit(main())
File "/usr/local/lib/python2.7/site-packages/amicleaner/cli.py", line 195, in main
app.run_cli()
File "/usr/local/lib/python2.7/site-packages/amicleaner/cli.py", line 165, in run_cli
candidates = self.prepare_candidates()
File "/usr/local/lib/python2.7/site-packages/amicleaner/cli.py", line 66, in prepare_candidates
candidates_amis = candidates_amis or self.fetch_candidates()
File "/usr/local/lib/python2.7/site-packages/amicleaner/cli.py", line 53, in fetch_candidates
excluded_amis += f.fetch_zeroed_asg()
File "/usr/local/lib/python2.7/site-packages/amicleaner/fetch.py", line 71, in fetch_zeroed_asg
LaunchConfigurationNames=zeroed_lcs
File "/usr/local/lib/python2.7/site-packages/botocore/client.py", line 320, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/usr/local/lib/python2.7/site-packages/botocore/client.py", line 596, in _make_api_call
api_params, operation_model, context=request_context)
File "/usr/local/lib/python2.7/site-packages/botocore/client.py", line 632, in _convert_to_request_dict
api_params, operation_model)
File "/usr/local/lib/python2.7/site-packages/botocore/validate.py", line 291, in serialize_to_request
raise ParamValidationError(report=report.generate_report())
botocore.exceptions.ParamValidationError: Parameter validation failed:
Invalid length for parameter LaunchConfigurationNames[0], value: 0, valid range: 1-inf
[Container] 2019/12/20 08:15:07 Command did not exit successfully amicleaner -f --mapping-key name --mapping-values ${AMI_TAG_NAME} --keep-previous ${AMI_RETENTION} exit status 1
[Container] 2019/12/20 08:15:07 Phase complete: POST_BUILD State: FAILED
[Container] 2019/12/20 08:15:07 Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: amicleaner -f --mapping-key name --mapping-values ${AMI_TAG_NAME} --keep-previous ${AMI_RETENTION}. Reason: exit status 1
Is there a way to exclude them?
It shouldn't report unused AMIs if it is attached to a launch configuration. That could lead to deletion of AMIs that are launched on demand with spot instances
When running this command-
amicleaner -f --full-report --mapping-key tags --mapping-values Name Branch --keep-previous 2
on CircleCI the result is-
Traceback (most recent call last):
File "/usr/local/bin/amicleaner", line 7, in <module>
from amicleaner.cli import main
File "/usr/local/lib/python3.6/site-packages/amicleaner/cli.py", line 13, in <module>
from .resources.config import MAPPING_KEY, MAPPING_VALUES
File "/usr/local/lib/python3.6/site-packages/amicleaner/resources/config.py", line 9, in <module>
TERM = Terminal()
File "/usr/local/lib/python3.6/site-packages/blessings/__init__.py", line 105, in __init__
self._init_descriptor)
_curses.error: setupterm: could not find terminal
Tests are running using actual credentials file.
Mocks aren't used or used incorrectly.
I would love to be able to deploy this, but am currently blocked on the issue fixed by #40
Hello,
Is there any way currently to see what will be deleted without actually deleting it?
Would like to see what will be wiped out, currently I don't see any option, but not sure if I missed that?
Has this project been abandoned by the owner?
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.