diff --git a/README.md b/README.md
deleted file mode 100644
index 6f8a176c..00000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# pulp_template
\ No newline at end of file
diff --git a/README.rst b/README.rst
new file mode 100644
index 00000000..a4e94f13
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,120 @@
+Tepmlate to create your own plugin
+==================================
+
+This is the ``plugin_template`` repository to help plugin writers
+get started and write their own plugin for `Pulp Project
+3.0+ `__.
+
+Clone this repository and run the provided ``rename.py`` script to create
+a skeleton for your plugin with the name of your choice. It will contain
+``setup.py``, expected plugin layout and stubs for necessary classes and methods.
+
+``$ git clone https://github.com/pulp/plugin_template.git``
+
+``$ cd plugin_template``
+
+``$ ./rename.py your_plugin_name``
+
+Check `Plugin Writer's Guide `__
+for more details and suggestions on plugin implementaion.
+
+Below are some ideas for how to document your plugin.
+
+
+All REST API examples below use `httpie `__ to
+perform the requests.
+
+Install ``pulpcore``
+--------------------
+
+Follow the `installation
+instructions `__
+provided with pulpcore.
+
+Install plugin
+--------------
+
+From source
+~~~~~~~~~~~
+
+Define installation steps here.
+
+Install from PyPI
+~~~~~~~~~~~~~~~~~
+
+Define installation steps here.
+
+
+Create a repository ``foo``
+---------------------------
+
+``$ http POST http://localhost:8000/api/v3/repositories/ name=foo``
+
+Add an Importer to repository ``foo``
+-------------------------------------
+
+Add important details about your Importer and provide examples.
+
+``$ http POST http://localhost:8000/api/v3/repositories/foo/importers/plugin-template/ some=params``
+
+.. code:: json
+
+ {
+ "_href": "http://localhost:8000/api/v3/repositories/foo/importers/plugin-template/bar/",
+ ...
+ }
+
+Add a Publisher to repository ``foo``
+-------------------------------------
+
+``$ http POST http://localhost:8000/api/v3/repositories/foo/publishers/plugin-template/ name=bar``
+
+.. code:: json
+
+ {
+ "_href": "http://localhost:8000/api/v3/repositories/foo/publishers/plugin-template/bar/",
+ ...
+ }
+
+Add a Distribution to Publisher ``bar``
+---------------------------------------
+
+``$ http POST http://localhost:8000/api/v3/repositories/foo/publishers/plugin-template/bar/distributions/ some=params``
+
+Sync repository ``foo`` using Importer ``bar``
+----------------------------------------------
+
+Use ``plugin-template`` Importer:
+
+``http POST http://localhost:8000/api/v3/repositories/foo/importers/plugin-template/bar/sync/``
+
+Add content to repository ``foo``
+---------------------------------
+
+``$ http POST http://localhost:8000/api/v3/repositorycontents/ repository='http://localhost:8000/api/v3/repositories/foo/' content='http://localhost:8000/api/v3/content/plugin-template/a9578a5f-c59f-4920-9497-8d1699c112ff/'``
+
+Create a Publication using Publisher ``bar``
+--------------------------------------------
+
+Dispatch the Publish task
+
+``$ http POST http://localhost:8000/api/v3/repositories/foo/publishers/plugin-template/bar/publish/``
+
+.. code:: json
+
+ [
+ {
+ "_href": "http://localhost:8000/api/v3/tasks/fd4cbecd-6c6a-4197-9cbe-4e45b0516309/",
+ "task_id": "fd4cbecd-6c6a-4197-9cbe-4e45b0516309"
+ }
+ ]
+
+Check status of a task
+----------------------
+
+``$ http GET http://localhost:8000/api/v3/tasks/82e64412-47f8-4dd4-aa55-9de89a6c549b/``
+
+Download ``foo.tar.gz`` from Pulp
+---------------------------------
+
+``$ http GET http://localhost:8000/content/foo/foo.tar.gz``
diff --git a/flake8.cfg b/flake8.cfg
new file mode 100644
index 00000000..d65b11a1
--- /dev/null
+++ b/flake8.cfg
@@ -0,0 +1,5 @@
+[flake8]
+exclude = */migrations/*
+# E401: multiple imports on one line
+ignore = E401
+max-line-length = 100
\ No newline at end of file
diff --git a/pulp_plugin_template/__init__.py b/pulp_plugin_template/__init__.py
new file mode 100644
index 00000000..20cc86a1
--- /dev/null
+++ b/pulp_plugin_template/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'pulp_plugin_template.app.PulpPluginTemplatePluginAppConfig'
diff --git a/pulp_plugin_template/app/__init__.py b/pulp_plugin_template/app/__init__.py
new file mode 100644
index 00000000..69500f6a
--- /dev/null
+++ b/pulp_plugin_template/app/__init__.py
@@ -0,0 +1,6 @@
+from pulpcore.plugin import PulpPluginAppConfig
+
+
+class PulpPluginTemplatePluginAppConfig(PulpPluginAppConfig):
+ name = 'pulp_plugin_template.app'
+ label = 'pulp_plugin_template'
diff --git a/pulp_plugin_template/app/models.py b/pulp_plugin_template/app/models.py
new file mode 100644
index 00000000..438b1d9e
--- /dev/null
+++ b/pulp_plugin_template/app/models.py
@@ -0,0 +1,84 @@
+"""
+Check `Plugin Writer's Guide`_ and `pulp_example`_ plugin
+implementation for more details.
+
+.. _Plugin Writer's Guide:
+ http://docs.pulpproject.org/en/3.0/nightly/plugins/plugin-writer/index.html
+
+.. _pulp_example:
+ https://github.com/pulp/pulp_example/
+"""
+
+from gettext import gettext as _
+from logging import getLogger
+
+from django.db import models
+
+from pulpcore.plugin.models import (Artifact, Content, ContentArtifact, RemoteArtifact, Importer,
+ ProgressBar, Publisher, RepositoryContent, PublishedArtifact,
+ PublishedMetadata)
+from pulpcore.plugin.tasking import Task
+
+
+log = getLogger(__name__)
+
+
+class PluginTemplateContent(Content):
+ """
+ The "plugin-template" content type.
+
+ Define fields you need for your new content type and
+ specify uniqueness constraint to identify unit of this type.
+
+ For example::
+
+ field1 = models.TextField()
+ field2 = models.IntegerField()
+ field3 = models.CharField()
+
+ class Meta:
+ unique_together = (field1, field2)
+ """
+ TYPE = 'plugin-template'
+
+ @classmethod
+ def natural_key_fields(cls):
+ for unique in cls._meta.unique_together:
+ for field in unique:
+ yield field
+
+
+class PluginTemplatePublisher(Publisher):
+ """
+ A Publisher for PluginTemplateContent.
+
+ Define any additional fields for your new publisher if needed.
+ A ``publish`` method should be defined.
+ It is responsible for publishing metadata and artifacts
+ which belongs to a specific repository.
+ """
+ TYPE = 'plugin-template'
+
+ def publish(self):
+ """
+ Publish the repository.
+ """
+ raise NotImplementedError
+
+
+class PluginTemplateImporter(Importer):
+ """
+ An Importer for PluginTemplateContent.
+
+ Define any additional fields for your new importer if needed.
+ A ``sync`` method should be defined.
+ It is responsible for parsing metadata of the content,
+ downloading of the content and saving it to Pulp.
+ """
+ TYPE = 'plugin-template'
+
+ def sync(self):
+ """
+ Synchronize the repository with the remote repository.
+ """
+ raise NotImplementedError
diff --git a/pulp_plugin_template/app/serializers.py b/pulp_plugin_template/app/serializers.py
new file mode 100644
index 00000000..c0a86b8f
--- /dev/null
+++ b/pulp_plugin_template/app/serializers.py
@@ -0,0 +1,61 @@
+"""
+Check `Plugin Writer's Guide`_ and `pulp_example`_ plugin
+implementation for more details.
+
+.. _Plugin Writer's Guide:
+ http://docs.pulpproject.org/en/3.0/nightly/plugins/plugin-writer/index.html
+
+.. _pulp_example:
+ https://github.com/pulp/pulp_example/
+"""
+
+from rest_framework import serializers
+from pulpcore.plugin import serializers as platform
+
+from . import models
+
+
+class PluginTemplateContentSerializer(platform.ContentSerializer):
+ """
+ A Serializer for PluginTemplateContent.
+
+ Add serializers for the new fields defined in PluginTemplateContent and
+ add those fields to the Meta class keeping fields from the parent class as well.
+
+ For example::
+
+ field1 = serializers.TextField()
+ field2 = serializers.IntegerField()
+ field3 = serializers.CharField()
+
+ class Meta:
+ fields = platform.ContentSerializer.Meta.fields + ('field1', 'field2', 'field3')
+ model = models.PluginTemplateContent
+ """
+ class Meta:
+ fields = platform.ContentSerializer.Meta.fields
+ model = models.PluginTemplateContent
+
+
+class PluginTemplateImporterSerializer(platform.ImporterSerializer):
+ """
+ A Serializer for PluginTemplateImporter.
+
+ Add any new fields if defined on PluginTemplateImporter.
+ Similar to the example above, in PluginTemplateContentSerializer.
+ """
+ class Meta:
+ fields = platform.ImporterSerializer.Meta.fields
+ model = models.PluginTemplateImporter
+
+
+class PluginTemplatePublisherSerializer(platform.PublisherSerializer):
+ """
+ A Serializer for PluginTemplatePublisher.
+
+ Add any new fields if defined on PluginTemplatePublisher.
+ Similar to the example above, in PluginTemplateContentSerializer.
+ """
+ class Meta:
+ fields = platform.PublisherSerializer.Meta.fields
+ model = models.PluginTemplatePublisher
diff --git a/pulp_plugin_template/app/viewsets.py b/pulp_plugin_template/app/viewsets.py
new file mode 100644
index 00000000..735eb2b7
--- /dev/null
+++ b/pulp_plugin_template/app/viewsets.py
@@ -0,0 +1,53 @@
+"""
+Check `Plugin Writer's Guide`_ and `pulp_example`_ plugin
+implementation for more details.
+
+.. _Plugin Writer's Guide:
+ http://docs.pulpproject.org/en/3.0/nightly/plugins/plugin-writer/index.html
+
+.. _pulp_example:
+ https://github.com/pulp/pulp_example/
+"""
+
+from pulpcore.plugin import viewsets as platform
+
+from . import models, serializers
+
+
+class PluginTemplateContentViewSet(platform.ContentViewSet):
+ """
+ A ViewSet for PluginTemplateContent.
+
+ Define endpoint name which will appear in the API endpoint for this content type.
+ For example::
+ http://pulp.example.com/api/v3/content/plugin-template/
+
+ Also specify queryset and serializer for PluginTemplateContent.
+ """
+ endpoint_name = 'plugin-template'
+ queryset = models.PluginTemplateContent.objects.all()
+ serializer_class = serializers.PluginTemplateContentSerializer
+
+
+class PluginTemplateImporterViewSet(platform.ImporterViewSet):
+ """
+ A ViewSet for PluginTemplateImporter.
+
+ Similar to the PluginTemplateContentViewSet above, define endpoint_name,
+ queryset and serializer, at a minimum.
+ """
+ endpoint_name = 'plugin-template'
+ queryset = models.PluginTemplateImporter.objects.all()
+ serializer_class = serializers.PluginTemplateImporterSerializer
+
+
+class PluginTemplatePublisherViewSet(platform.PublisherViewSet):
+ """
+ A ViewSet for PluginTemplatePublisher.
+
+ Similar to the PluginTemplateContentViewSet above, define endpoint_name,
+ queryset and serializer, at a minimum.
+ """
+ endpoint_name = 'plugin-template'
+ queryset = models.PluginTemplatePublisher.objects.all()
+ serializer_class = serializers.PluginTemplatePublisherSerializer
diff --git a/rename.py b/rename.py
new file mode 100755
index 00000000..1e4c3a30
--- /dev/null
+++ b/rename.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import re
+import shutil
+import sys
+import tempfile
+import textwrap
+
+TEMPLATE_SNAKE = 'pulp_plugin_template'
+TEMPLATE_CAMEL = 'PulpPluginTemplate'
+TEMPLATE_CAMEL_SHORT = 'PluginTemplate'
+TEMPLATE_DASH = 'pulp-plugin-template'
+TEMPLATE_DASH_SHORT = 'plugin-template'
+IGNORE_FILES = ('LICENSE', 'rename.py', 'flake8.cfg')
+IGNORE_COPYTREE = ('.git*', '*.pyc', '*.egg-info', 'rename.py', '__pycache__')
+
+
+def is_valid(name):
+ """
+ Check if specified name is compliant with requirements for it.
+
+ The max length of the name is 16 characters. It seems reasonable to have this limitation
+ because the plugin name is used for directory name on the file system and it is also used
+ as a name of some Python objects, like class names, so it is expected to be relatively short.
+ """
+ return bool(re.match(r'^[a-z][0-9a-z_]{2,15}$', name))
+
+
+def to_camel(name):
+ """
+ Convert plugin name from snake to camel case
+ """
+ return name.title().replace('_', '')
+
+
+def to_dash(name):
+ """
+ Convert plugin name from snake case to dash representation
+ """
+ return name.replace('_', '-')
+
+
+def main():
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
+ description='rename template data to a specified plugin name')
+ parser.add_argument('plugin_name', type=str,
+ help=textwrap.dedent('''\
+ set plugin name to this one
+
+ Requirements for plugin name:
+ - specified in the snake form: your_new_plugin_name
+ - consists of 3-16 characters
+ - possible characters are letters [a-z], numbers [0-9], underscore [_]
+ - first character should be a letter [a-z]
+ '''))
+ args = parser.parse_args()
+ plugin_name = args.plugin_name
+
+ if not is_valid(plugin_name):
+ parser.print_help()
+ return 2
+
+ pulp_plugin_name = 'pulp_' + plugin_name
+ replace_map = {TEMPLATE_SNAKE: pulp_plugin_name,
+ TEMPLATE_DASH_SHORT: to_dash(plugin_name),
+ TEMPLATE_DASH: to_dash(pulp_plugin_name),
+ TEMPLATE_CAMEL_SHORT: to_camel(plugin_name),
+ TEMPLATE_CAMEL: to_camel(pulp_plugin_name)}
+
+ # copy template directory
+ orig_root_dir = os.path.dirname(os.path.abspath(parser.prog))
+ dst_root_dir = os.path.join(os.path.dirname(orig_root_dir), pulp_plugin_name)
+ try:
+ shutil.copytree(orig_root_dir, dst_root_dir,
+ ignore=shutil.ignore_patterns(*IGNORE_COPYTREE))
+ except FileExistsError:
+ print(textwrap.dedent('''
+ It looks like plugin with such name already exists!
+ Please, choose another name.
+ '''))
+ return 1
+
+ # rename python package directory
+ listed_dir = os.listdir(dst_root_dir)
+ if TEMPLATE_SNAKE in listed_dir:
+ os.rename(os.path.join(dst_root_dir, TEMPLATE_SNAKE),
+ os.path.join(dst_root_dir, pulp_plugin_name))
+
+ # replace text
+ for dir_path, dirs, files in os.walk(dst_root_dir):
+ for file in files:
+ # skip files which don't need any text replacement
+ if file in IGNORE_FILES:
+ continue
+
+ file_path = os.path.join(dir_path, file)
+
+ # write substituted text to temporary file
+ with open(file_path) as fd_in, tempfile.NamedTemporaryFile(mode='w', dir=dir_path,
+ delete=False) as fd_out:
+ tempfile_path = fd_out.name
+ text = fd_in.read()
+ for old, new in replace_map.items():
+ text = text.replace(old, new)
+ fd_out.write(text)
+
+ # overwrite existing file by renaming the temporary one
+ os.rename(tempfile_path, file_path)
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..49c3baf9
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+from setuptools import setup
+
+requirements = [
+ 'pulpcore-plugin',
+]
+
+setup(
+ name='pulp-plugin-template',
+ version='0.0.1a1.dev1',
+ description='pulp-plugin-template plugin for the Pulp Project',
+ author='AUTHOR',
+ author_email='author@email.here',
+ url='http://example.com/',
+ install_requires=requirements,
+ include_package_data=True,
+ packages=['pulp_plugin_template', 'pulp_plugin_template.app'],
+ entry_points={
+ 'pulpcore.plugin': [
+ 'pulp_plugin_template = pulp_plugin_template:default_app_config',
+ ]
+ }
+)