{ "info": { "author": "Brad Searle", "author_email": "bradleysearle@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "# Grifter\nPython library to build large scale Vagrant topologies for the networking \nspace. Can also be used the build small scale labs for networking/compute \ndevices.\n\n[![Build Status](https://travis-ci.org/bobthebutcher/grifter.svg?branch=master)](https://travis-ci.org/bobthebutcher/grifter.svg?branch=master)\n[![Coverage Status](https://coveralls.io/repos/github/bobthebutcher/grifter/badge.svg?branch=master)](https://coveralls.io/github/bobthebutcher/grifter?branch=master)\n\nNOTE: Python 3.6+ is required to make use of this library.\n\n```\n*****************************************************************\nThis project is currently in beta and stability is not currently \nguaranteed. Breaking API changes can be expected.\n*****************************************************************\n\n```\n## Vagrant\nWhat is Vagrant? From the Vagrant [website](https://www.vagrantup.com/docs/index.html)\n```\nA command line utility for managing the lifecycle of virtual machines\n```\n\n## Vagrant Libvirt\nWhat is Vagrant Libvirt? From the `vagrant-libvirt` github [page](https://github.com/vagrant-libvirt/vagrant-libvirt) \n```\nA Vagrant plugin that adds a Libvirt provider to Vagrant, allowing Vagrant to control and provision machines via Libvirt toolkit.\n```\n\n## Why\nWhen simulating large topologies Vagrantfiles can become thousands \nof lines long. Getting all the configuration correct is often a \nfrustrating, error riddled process especially for those not familiar \nwith Vagrant. Grifter aims to help simplify that process.\n\n##### Additional project goals\n- Generate topology.dot files for use with PTM :heavy_check_mark:\n- Generate Inventory files for tools such as Ansible, Nornir\n\nNOTE: Only a `vagrant-libvirt` compatible `Vagrantfile` for \nVagrant version `>= 2.1.0` will be generated. \n\nSupport for Virtualbox or any other provider type is not supported or\non the road map.\n\n## Dependencies\nGrifter requires the help of the following awesome projects from the Python \ncommunity.\n- [Cerberus](http://docs.python-cerberus.org/en/stable/) - Schema validation\n- [Click](https://click.palletsprojects.com/) - CLI utility\n- [Jinja2](http://jinja.pocoo.org/docs) - Template engine\n- [PyYAML](https://pyyaml.org/) - YAML all the things\n\n## Installation\nThere is currently no PyPI release for this project. Grifter can be \ninstalled directly from source using PIP. \n\nCreate and activate virtualenv.\n```\nmkdir ~/test && cd ~/test\npython3 -m venv .venv\nsource .venv/bin/activate\n```\n\nInstall `grifter` with `pip`\n```\n# Install the master branch.\npip install https://github.com/bobthebutcher/grifter/archive/master.zip\n```\n\nReleases are distributed via Github Releases.\n```\n# Install the latest release.\npip install https://github.com/bobthebutcher/grifter/archive/v0.2.11.zip\n```\n\n## Quick Start\nCreate a `guests.yml` file.\n``` \ntee guests.yml > /dev/null << \"EOF\"\nsrv01:\n vagrant_box: \n name: \"centos/7\"\nEOF\n```\n\nGenerate a Vagrantfile\n``` \ngrifter create guests.yml\n```\n\nLet Vagrant do its magic\n``` \nvagrant up\n```\n\n\n\n## Config File\nA file named `config.yml` is required to define the base settings of \neach box managed within the grifter environment. The default `config.yml` \nfile can be found [here](grifter/config.yml)\n\n### Box Naming\nGrifter expects Vagrant boxes to be named according to the following list.\n\n##### Custom Boxes\n- arista/veos\n- cisco/csr1000v\n- cisco/iosv\n- cisco/xrv\n- juniper/vmx-vcp\n- juniper/vmx-vfp\n- juniper/vqfx-pfe\n- juniper/vqfx-re\n- juniper/vsrx\n- juniper/vsrx-packetmode\n\n##### Vagrant Cloud Boxes\n- CumulusCommunity/cumulus-vx\n- centos/7\n- generic/ubuntu1804\n- opensuse/openSUSE-15.0-x86_64\n\n#### guest_config\nThe `guest_config` section defines characteristics about the Vagrant boxes \nused with grifter.\n#### Required Parameters.\n- data_interface_base\n- data_interface_offset\n- max_data_interfaces\n- management_interface\n\nNote: `data_interface_base` cannot be an empty string. If the box does not \nhave any data interfaces the suggested value is \"NA\". This field will be \nignored so it can be anything as long as it is not empty.\n\n```yaml\nguest_config:\n example/box:\n data_interface_base: \"eth\" # String pattern for data interfaces.\n data_interface_offset: 0 # Number of first data interface ie: 0, 1, 2, etc..\n internal_interfaces: 0 # Used for inter-box connections for multi-vm boxes.\n max_data_interfaces: 8 \n management_interface: \"ma1\"\n reserved_interfaces: 0 # Interfaces that are required but cannot be used.\n\n arista/veos:\n data_interface_base: \"eth\"\n data_interface_offset: 1\n internal_interfaces: 0\n max_data_interfaces: 24\n management_interface: \"ma1\"\n reserved_interfaces: 0\n\n juniper/vsrx-packetmode:\n data_interface_base: \"ge-0/0/\"\n data_interface_offset: 0\n internal_interfaces: 0\n max_data_interfaces: 16\n management_interface: \"fxp0.0\"\n reserved_interfaces: 0\n```\n\n#### guest_pairs\nThe `guest_pairs` section is used the define boxes that need two VMs to \nbe fully functional. Some examples are the Juniper vMX and vQFX where \none box is used for the control-plane and another for the forwarding-plane.\n\nNOTE: This functionality will be added in a future release.\n\n#### Custom config files\nA default config file ships with the grifter python package.\nThis file can be customized with your required parameters by creating a \n`config.yml` file in the following locations.\n - `/opt/grifter/`\n - `~/.grifter/`\n - `./` \n\n Parameters in a users `config.yml` file will be merged with the default \n `config.yml` file with the user-defined parameters taking preference.\n\n## Usage\n\n#### CLI Utility\nGrifter ships with a CLI utility. Execute `grifter -h` to \ndiscover all the CLI options. \n\n```\ngrifter -h\nUsage: grifter [OPTIONS] COMMAND [ARGS]...\n\n Create a Vagrantfile from a YAML data input file.\n\nOptions:\n --version Show the version and exit.\n -h, --help Show this message and exit.\n\nCommands:\n create Create a Vagrantfile.\n example Print example file declaration.\n```\n\n#### Create Vagrantfile\n```\ngrifter create guests.yml\n```\n\n### Guests Datafile\nGuest VMs characteristics and interface connections are defined in a YAML file. \nThis file can be named anything, but the recommended naming convention is \n`guests.yml`.\n\n#### Guest Schema\nJinja2 is used a the templating engine to generate the Vagrantfiles.\nGuests definition within a guests file must use the following \nschema as it is required to ensure templates render correctly and \nwithout errors. The guest data will be validated against the schema \nusing the Cerberus project.\n\n```yaml\nsome-guest: # guest name\n vagrant_box: # vagrant_box parameters\n name: # string - required\n version: # string - optional | default: \"\"\n url: # string - optional | default: \"\"\n provider: # string - optional | default: \"libvirt\"\n guest_type: # string - optional | default: \"\"\n boot_timeout: # integer - optional | default: 0\n throttle_cpu: # integer - optional | default: 0\n\n ssh: # dict - optional\n username: # string - optional | default: \"\"\n password: # string - optional | default: \"\"\n insert_key: # boolean - optional | default: False\n\n synced_folder: # dict - optional\n enabled: # boolean - default: False\n id: # string - default: \"vagrant-root\"\n src: # string - default: \".\"\n dst: # string - default: \"/vagrant\"\n\n provider_config: # dict - optional\n random_hostname: # boolean - optional | default: False\n nic_adapter_count: # integer - optional | default: 0\n disk_bus: # string - optional | default: \"\"\n cpus: # integer - optional | default: 1\n memory: # integer - optional | default: 512\n huge_pages: # boolean - optional | default: False\n storage_pool: # string - optional | default: \"\"\n additional_storage_volumes: # list - optional\n # For each list element the following is required.\n - location: # string\n type: # string\n bus: # string\n device: # string\n nic_model_type: # string - optional | default: \"\"\n management_network_mac: # string - optional | default: \"\"\n\n internal_interfaces: # list - optional\n # For each list element the following is required.\n - local_port: # integer\n remote_guest: # string\n remote_port: # integer\n\n data_interfaces: # list - optional\n # For each list element the following is required.\n - local_port: # integer\n remote_guest: # string\n remote_port: # integer\n```\n\n#### Example Datafile\nThe following example datafile defines two `arista/veos` switches connected \ntogether on ports 1 and 2.\n```yaml\nsw01:\n vagrant_box:\n name: \"arista/veos\"\n version: \"4.20.1F\"\n guest_type: \"tinycore\"\n provider: \"libvirt\"\n ssh:\n insert_key: False\n synced_folder:\n enabled: False\n provider_config:\n nic_adapter_count: 2\n disk_bus: \"ide\"\n cpus: 2\n memory: 2048\n data_interfaces:\n - local_port: 1\n remote_guest: \"sw02\"\n remote_port: 1\n - local_port: 2\n remote_guest: \"sw02\"\n remote_port: 2\n\nsw02:\n vagrant_box:\n name: \"arista/veos\"\n version: \"4.20.1F\"\n guest_type: \"tinycore\"\n provider: \"libvirt\"\n ssh:\n insert_key: False\n synced_folder:\n enabled: False\n provider_config:\n nic_adapter_count: 2\n disk_bus: \"ide\"\n cpus: 2\n memory: 2048\n data_interfaces:\n - local_port: 1\n remote_guest: \"sw01\"\n remote_port: 1\n - local_port: 2\n remote_guest: \"sw01\"\n remote_port: 2\n```\n#### Generated Vagrantfile\n```ruby\n# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\ndef get_mac(oui=\"28:b7:ad\")\n \"Generate a MAC address\"\n nic = (1..3).map{\"%0.2x\"%rand(256)}.join(\":\")\n return \"#{oui}:#{nic}\"\nend\n\ncwd = Dir.pwd.split(\"/\").last\nusername = ENV['USER']\ndomain_prefix = \"#{username}_#{cwd}\"\ndomain_uuid = \"1f22b55d-2d7e-5a24-b4fa-3a8878df5cc5\"\n\nVagrant.require_version \">= 2.1.0\"\nVagrant.configure(\"2\") do |config|\n\n config.vm.define \"sw01\" do |node|\n guest_name = \"sw01\"\n node.vm.box = \"arista/veos\"\n node.vm.box_version = \"4.20.1F\"\n node.vm.guest = :tinycore\n node.vm.synced_folder \".\", \"/vagrant\", id: \"vagrant-root\", disabled: true\n\n node.ssh.insert_key = false\n\n node.vm.provider :libvirt do |domain|\n domain.default_prefix = \"#{domain_prefix}\"\n domain.cpus = 2\n domain.memory = 2048\n domain.disk_bus = \"ide\"\n domain.nic_adapter_count = 2\n end\n\n node.vm.network :private_network,\n # sw01-eth1 <--> sw02-eth1\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.146.53.1\",\n :libvirt__tunnel_local_port => 10001,\n :libvirt__tunnel_ip => \"127.146.53.2\",\n :libvirt__tunnel_port => 10001,\n :libvirt__iface_name => \"sw01-eth1-#{domain_uuid}\",\n auto_config: false\n\n node.vm.network :private_network,\n # sw01-eth2 <--> sw02-eth2\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.146.53.1\",\n :libvirt__tunnel_local_port => 10002,\n :libvirt__tunnel_ip => \"127.146.53.2\",\n :libvirt__tunnel_port => 10002,\n :libvirt__iface_name => \"sw01-eth2-#{domain_uuid}\",\n auto_config: false\n\n end\n config.vm.define \"sw02\" do |node|\n guest_name = \"sw02\"\n node.vm.box = \"arista/veos\"\n node.vm.box_version = \"4.20.1F\"\n node.vm.guest = :tinycore\n node.vm.synced_folder \".\", \"/vagrant\", id: \"vagrant-root\", disabled: true\n\n node.ssh.insert_key = false\n\n node.vm.provider :libvirt do |domain|\n domain.default_prefix = \"#{domain_prefix}\"\n domain.cpus = 2\n domain.memory = 2048\n domain.storage_pool_name = \"disk1\"\n domain.disk_bus = \"ide\"\n domain.nic_adapter_count = 2\n end\n\n node.vm.network :private_network,\n # sw02-eth1 <--> sw01-eth1\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.146.53.2\",\n :libvirt__tunnel_local_port => 10001,\n :libvirt__tunnel_ip => \"127.146.53.1\",\n :libvirt__tunnel_port => 10001,\n :libvirt__iface_name => \"sw02-eth1-#{domain_uuid}\",\n auto_config: false\n\n node.vm.network :private_network,\n # sw02-eth2 <--> sw01-eth2\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.146.53.2\",\n :libvirt__tunnel_local_port => 10002,\n :libvirt__tunnel_ip => \"127.146.53.1\",\n :libvirt__tunnel_port => 10002,\n :libvirt__iface_name => \"sw02-eth2-#{domain_uuid}\",\n auto_config: false\n\n end\n\nend\n```\n\n### Defaults Per-Guest Type\nIt is possible to define default values per guest group type. Grifter will \nlook for a file named `guest-defaults.yml` in the following locations from \nthe least to most preferred:\n\n - `/opt/grifter/`\n - `~/.grifter/`\n - `./` \n\n```yaml\narista/veos:\n vagrant_box:\n version: \"4.20.1F\"\n guest_type: \"tinycore\"\n ssh:\n insert_key: False\n synced_folder:\n enabled: False\n provider_config:\n nic_adapter_count: 24\n cpus: 2\n memory: 2048\n disk_bus: \"ide\"\n\njuniper/vsrx-packetmode:\n vagrant_box:\n version: \"18.3R1-S1.4\"\n provider: \"libvirt\"\n guest_type: \"tinycore\"\n ssh:\n insert_key: False\n synced_folder:\n enabled: False\n provider_config:\n nic_adapter_count: 2\n disk_bus: \"ide\"\n cpus: 2\n memory: 4096\n```\n\nGroup variables can be over-written by variables at the guest variable level. \nThe values of the group and guest variables will be merged prior to building \na `Vagrantfile` with the guest variables taking precedence over the group \nvariables.\n\nThis means you can have a much more succinct guests file by reducing \na lot of duplication. Here is an example of a simplified guest file. The \nvalues from the `arista/veos` guest type in the `guest-defaults.yml` file \nwill be used to fill in the parameters for the guests.\n\n```yaml\nsw01:\n vagrant_box:\n name: \"arista/veos\"\n provider_config:\n nic_adapter_count: 2\n data_interfaces:\n - local_port: 1\n remote_guest: \"sw02\"\n remote_port: 1\n - local_port: 2\n remote_guest: \"sw02\"\n remote_port: 2\n\nsw02:\n vagrant_box:\n name: \"arista/veos\"\n provider_config:\n nic_adapter_count: 2\n data_interfaces:\n - local_port: 1\n remote_guest: \"sw01\"\n remote_port: 1\n - local_port: 2\n remote_guest: \"sw01\"\n remote_port: 2\n```\n\nThe generated `Vagrantfile` below is the same as the one above, but with a \nmuch cleaner guest definition file.\n\n```ruby\n# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\ndef get_mac(oui=\"28:b7:ad\")\n \"Generate a MAC address\"\n nic = (1..3).map{\"%0.2x\"%rand(256)}.join(\":\")\n return \"#{oui}:#{nic}\"\nend\n\ncwd = Dir.pwd.split(\"/\").last\nusername = ENV['USER']\ndomain_prefix = \"#{username}_#{cwd}\"\ndomain_uuid = \"d35fb1b6-ecdc-5412-be22-185446af92d6\"\n\nVagrant.require_version \">= 2.1.0\"\nVagrant.configure(\"2\") do |config|\n\n config.vm.define \"sw01\" do |node|\n guest_name = \"sw01\"\n node.vm.box = \"arista/veos\"\n node.vm.box_version = \"4.20.1F\"\n node.vm.guest = :tinycore\n node.vm.synced_folder \".\", \"/vagrant\", id: \"vagrant-root\", disabled: true\n\n node.ssh.insert_key = false\n\n node.vm.provider :libvirt do |domain|\n domain.default_prefix = \"#{domain_prefix}\"\n domain.cpus = 2\n domain.memory = 2048\n domain.disk_bus = \"ide\"\n domain.nic_adapter_count = 2\n end\n\n node.vm.network :private_network,\n # sw01-eth1 <--> sw02-eth1\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.127.145.1\",\n :libvirt__tunnel_local_port => 10001,\n :libvirt__tunnel_ip => \"127.127.145.2\",\n :libvirt__tunnel_port => 10001,\n :libvirt__iface_name => \"sw01-eth1-#{domain_uuid}\",\n auto_config: false\n\n node.vm.network :private_network,\n # sw01-eth2 <--> sw02-eth2\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.127.145.1\",\n :libvirt__tunnel_local_port => 10002,\n :libvirt__tunnel_ip => \"127.127.145.2\",\n :libvirt__tunnel_port => 10002,\n :libvirt__iface_name => \"sw01-eth2-#{domain_uuid}\",\n auto_config: false\n\n end\n config.vm.define \"sw02\" do |node|\n guest_name = \"sw02\"\n node.vm.box = \"arista/veos\"\n node.vm.box_version = \"4.20.1F\"\n node.vm.guest = :tinycore\n node.vm.synced_folder \".\", \"/vagrant\", id: \"vagrant-root\", disabled: true\n\n node.ssh.insert_key = false\n\n node.vm.provider :libvirt do |domain|\n domain.default_prefix = \"#{domain_prefix}\"\n domain.cpus = 2\n domain.memory = 2048\n domain.storage_pool_name = \"disk1\"\n domain.disk_bus = \"ide\"\n domain.nic_adapter_count = 2\n end\n\n node.vm.network :private_network,\n # sw02-eth1 <--> sw01-eth1\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.127.145.2\",\n :libvirt__tunnel_local_port => 10001,\n :libvirt__tunnel_ip => \"127.127.145.1\",\n :libvirt__tunnel_port => 10001,\n :libvirt__iface_name => \"sw02-eth1-#{domain_uuid}\",\n auto_config: false\n\n node.vm.network :private_network,\n # sw02-eth2 <--> sw01-eth2\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.127.145.2\",\n :libvirt__tunnel_local_port => 10002,\n :libvirt__tunnel_ip => \"127.127.145.1\",\n :libvirt__tunnel_port => 10002,\n :libvirt__iface_name => \"sw02-eth2-#{domain_uuid}\",\n auto_config: false\n\n end\n\nend\n```\n\n## Example Files\nExamples of the `config.yml`, `guests-defaults.yml` and `guests.yml` files \ncan be found [here](grifter/examples)\n\n\n## Interfaces\nThere are 3 types of interfaces that can be defined.\n\n- internal_interfaces\n- data_interfaces\n- reserved_interfaces\n\n#### Internal Interfaces\nConfig location: `guests.yml` \nUsed for an inter-vm communication channel for multi-vm boxes. \nKnown examples are the vMX and vQFX.\n\n#### data_interfaces\nConfig location: `guests.yml` \nRevenue ports that are used to pass data traffic.\n\n#### reserved_interfaces\nConfig location: `config.yml` \nInterfaces that need to be defined because 'reasons' but cannot be \nused. The only known example is the `juniper/vqfx-re`. The number of \nreserved_interfaces is defined per-box type in the `config.yml` file. \nGrifter builds out the interface definitions automatically as a \nblackhole interfaces.\n\n#### Blackhole Interfaces\nInterfaces defined in the Vagratfile relate to interfaces \non the guest vm on a first to last basis. This can be undesirable when \ntrying to accurately simulate a production environment when devices \ncan have 48+ ports. \n\nGrifter will automatically create `blackhole interfaces` to fill out \nundefined `data_interfaces` ports up to the box types \n`max_data_interfaces` parameter in the `config.yml` file. \n\n#### Vagrantfile Interface Order\nInterfaces are added to the Vagrantfile in the following order.\n- internal_interfaces\n- reserved_interfaces\n- data_interfaces\n\nInterfaces are configured using the udp tunneling type. This \nwill create a 'pseudo' layer 1 connection between VM ports.\n\n##### Example interface definition\n```yaml\n data_interfaces:\n - local_port: 1\n remote_guest: \"sw02\"\n remote_port: 1\n```\n##### Rendered Vagrantfile interface\n```ruby\n node.vm.network :private_network,\n # sw01-eth1 <--> sw02-eth1\n :mac => \"#{get_mac()}\",\n :libvirt__tunnel_type => \"udp\",\n :libvirt__tunnel_local_ip => \"127.255.255.1\",\n :libvirt__tunnel_local_port => 10001,\n :libvirt__tunnel_ip => \"127.255.255.2\",\n :libvirt__tunnel_port => 10001,\n :libvirt__iface_name => \"sw01-eth1-#{domain_uuid}\",\n auto_config: false\n```\n\n#### NIC Adapter Count\nConfig location: `guests.yml` \nDefines the total number of `data_interfaces` to create on the VM. \nAny undefined `data_interfaces` will be added as a blackhole interface.\n\nThe total is calculated against the sum of the `internal_interfaces`, `\nreserved_interfaces` and `data_interfaces` parameters after blackhole \ninterfaces have been added automatically by the template system.\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "", "keywords": "", "license": "GNU GENERAL PUBLIC LICENSE Version 3", "maintainer": "", "maintainer_email": "", "name": "grifter", "package_url": "https://pypi.org/project/grifter/", "platform": "", "project_url": "https://pypi.org/project/grifter/", "project_urls": null, "release_url": "https://pypi.org/project/grifter/0.2.11/", "requires_dist": [ "jinja2", "pyyaml", "click", "cerberus" ], "requires_python": "", "summary": "A package to generate Vagrantfiles for use with Vagrant.", "version": "0.2.11" }, "last_serial": 4679356, "releases": { "0.2.11": [ { "comment_text": "", "digests": { "md5": "d48596b22a161e3501827abd20b53830", "sha256": "e4ee4cc61fc2513894345407cd51d4c25cb4770a47e1f1791e650b5d488cf66e" }, "downloads": -1, "filename": "grifter-0.2.11-py3-none-any.whl", "has_sig": false, "md5_digest": "d48596b22a161e3501827abd20b53830", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 48655, "upload_time": "2019-01-10T03:07:10", "url": "https://files.pythonhosted.org/packages/d4/ba/6c08ebf66a7846c9156852dfd29c4126524a6e75cdadc85bc568400fc02b/grifter-0.2.11-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f83f372a48c4d1c55bb9d47e25ce2f28", "sha256": "42caa05681a2804b6f4aa8c2e4adef13d014c83df64d6baf5699d882698433ba" }, "downloads": -1, "filename": "grifter-0.2.11.tar.gz", "has_sig": false, "md5_digest": "f83f372a48c4d1c55bb9d47e25ce2f28", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29380, "upload_time": "2019-01-10T03:07:13", "url": "https://files.pythonhosted.org/packages/8d/b9/0eafd12c6b2b514de5bffc7414891b375b0a47e7b96b38bb98f3f5cc4e7a/grifter-0.2.11.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "d48596b22a161e3501827abd20b53830", "sha256": "e4ee4cc61fc2513894345407cd51d4c25cb4770a47e1f1791e650b5d488cf66e" }, "downloads": -1, "filename": "grifter-0.2.11-py3-none-any.whl", "has_sig": false, "md5_digest": "d48596b22a161e3501827abd20b53830", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 48655, "upload_time": "2019-01-10T03:07:10", "url": "https://files.pythonhosted.org/packages/d4/ba/6c08ebf66a7846c9156852dfd29c4126524a6e75cdadc85bc568400fc02b/grifter-0.2.11-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f83f372a48c4d1c55bb9d47e25ce2f28", "sha256": "42caa05681a2804b6f4aa8c2e4adef13d014c83df64d6baf5699d882698433ba" }, "downloads": -1, "filename": "grifter-0.2.11.tar.gz", "has_sig": false, "md5_digest": "f83f372a48c4d1c55bb9d47e25ce2f28", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29380, "upload_time": "2019-01-10T03:07:13", "url": "https://files.pythonhosted.org/packages/8d/b9/0eafd12c6b2b514de5bffc7414891b375b0a47e7b96b38bb98f3f5cc4e7a/grifter-0.2.11.tar.gz" } ] }