Story #7127

Updated by daviddavis about 3 years ago

## Proposal 

 As a developer, it would be nice to be able to label pulp objects such as repositories. To accomplish this, I propose adding Kubernetes style labels to pulp objects (more information on kubernetes labels here: 

 Labels are arbitrary key value pairs that can be applied to objects and used to query them. For example, I could attach a `label1=foo`, `label2=foo`, and `label3=foobar` to a repository. The repository could then be queried by any of the following methods 
 * `foo=bar`: select all objects that have the foo label set to bar 
 * `foo!=bar`: select all objects that have the foo label set to anything except bar 
 * `foo in (bar, foobar)`: selects all objects with the foo label that contain either the values for bar or foobar 
 * `foo notin (bar, foobar)`: selects all objects with the foo label that don't contain bar or foobar 
 * `foo`: selects all objects that contain the label foo (regardless of value) 
 * `!foo`: selects all objects that don't contain the label foo 

 ## Use cases 

 In galaxy_ng we need a way to filter repositories based on what they are used for. For example we are going to have the following repositories 
 * `published` - contains locally published collections. Should be searchable. Is the default repo if no repo is provided 
 * `staging` - content waiting for approval. Not searchable 
 * `rejected` - content that has been rejected. Not searchable 
 * `rh-certified` - content synced from automation hub. Contains red hat certified content. Searchable 
 * `community` - content synced from galaxy. Searchable 

 As well as an arbitrary number of repositories named inbound-<namespace_name> for uploading collections. Not searchable. 

 Right now the names for these repos are hard coded as a way for the API to identify which content should go where, however with label support we could add the following labels 

 * searchable= true | false 
 * default=true | false 
 * content-readiness= production | inbound | staging 
 * certification= community | rh-certified | local 

 With these labels we can: 
 * limit search results to repos that match `searchable=true` 
 * separate inbound repos with `content-readiness!=inbound` 
 * identify which repositories contain certified content with `certification=rh-certified` 

 ## Design 

 ### API Design 

 #### Filtering 

 Labels can be filtered by passing a urlencoded string to a `label_selector` parameter. 

 Some examples based on [the kubernetes documentation]( 
 * `?label_selector=environment%3Dproduction,tier%3Dfrontend` 
     * Evaluates to `environment=production,tier=frontend` 
 * `?label_selector=environment+in+%28production%2Cqa%29%2Ctier+in+%28frontend%29` 
     * Evaluates to `environment in (production,qa),tier in (frontend)` 

 Note: Ansible Galaxy and RHUI have agreed that for a first pass, we could just support a subset of operators (ie `=` and `!=`). 

 ##### LabelSelectFilter 

 `LabelSelectFilter` would be a `django_filter.Filter` that parses the label_select parameter and then filters the queryset. It can be applied to a Queryset of any model with labels. 

 Note: for an example of a complex `Filter`, [see the `RepositoryVersionFilter`]( 

 #### LabelSerializer 

 Create a new `LabelSerializer` that can be nested into other model serializers as a field (much like the `CreatedResourceSerializer`). This serializer should be both readable and writable and should enable the following API calls. 

 ##### Reading 

 # GET /pulp/api/v3/repositories/file/file/ 

   "labels": {"foo": "bar", "foo2": "baz"}, 

 ##### Setting/Updating 

 # POST /pulp/api/v3/repositories/file/file/ name=test labels:='{"foo": "bar"}' labels='[{"foo": "bar"}]' 

   "labels": {"foo": "bar"}, 

 # PATCH PUT /pulp/api/v3/repositories/file/file/<uuid>/ labels:='{"something": "else"}' labels='[{"foo": "baz"}]' 

   "labels": {"something": "else"}, 

 # PATCH /pulp/api/v3/repositories/file/file/<uuid>/ labels:='{}' 

   "labels": {}, {"foo": "baz"}, 

 ### Database Design 

 ##### Label (extends GenericRelationModel) 
 * **resource** - generic foreign key 
 * **key** (CharField) - the key of the label  
 * **value** (TextField) - the value for a label 

 * `Label` `resource` and `key` are unique together 
 * We should also limit `key` to alphanumerics  
 * The `Label` gets deleted when the resource gets deleted (which `GenericRelationModel` does) 
 * `key` and `value` should be indexed (`db_index=True`)