Documentation
¶
Index ¶
- Variables
- func CreateApisGenerator(apis *APIs, filename string) generator.Generator
- func CreateInstallGenerator(apigroup *APIGroup, filename string) generator.Generator
- func CreateUnversionedGenerator(apigroup *APIGroup, filename string) generator.Generator
- func CreateVersionedGenerator(apiversion *APIVersion, apigroup *APIGroup, filename string) generator.Generator
- func GetGroup(t *types.Type) string
- func GetGroupPackage(t *types.Type) string
- func GetKind(t *types.Type, group string) string
- func GetVersion(t *types.Type, group string) string
- func HasSubresource(t *types.Type) bool
- func IsAPIResource(t *types.Type) bool
- func IsAPISubresource(t *types.Type) bool
- func IsApisDir(dir string) bool
- func IsNonNamespaced(t *types.Type) bool
- func IsUnversioned(t *types.Type, group string) bool
- func IsVersioned(t *types.Type, group string) bool
- func LoadGoBoilerplate() ([]byte, error)
- type APIGroup
- type APIResource
- type APISubresource
- type APIVersion
- type APIs
- type APIsBuilder
- func (b *APIsBuilder) GenClient(c *types.Type) bool
- func (b *APIsBuilder) GenDeepCopy(c *types.Type) bool
- func (b *APIsBuilder) GetControllerTag(c *types.Type) string
- func (b *APIsBuilder) GetNameAndImport(tags SubresourceTags) (string, string)
- func (b *APIsBuilder) GetResourceTag(c *types.Type) string
- func (b *APIsBuilder) GetSubresourceTags(c *types.Type) []string
- func (b *APIsBuilder) GetSubresources(c *APIResource) map[string]*APISubresource
- func (b *APIsBuilder) IsInPackage(tags SubresourceTags) bool
- func (b *APIsBuilder) ParseAPIs()
- func (b *APIsBuilder) ParseDomain()
- func (b *APIsBuilder) ParseGroupNames()
- func (b *APIsBuilder) ParseIndex()
- func (b *APIsBuilder) ParsePackages()
- func (b *APIsBuilder) ParseStructsAndAliases(apigroup *APIGroup)
- type Alias
- type Comments
- type Field
- type Gen
- func (g *Gen) DefaultNameSystem() string
- func (g *Gen) Execute(outputFileName string, patterns ...string) error
- func (g *Gen) NameSystems() namer.NameSystems
- func (g *Gen) Packages(context *generator.Context, outputFileName string) []generator.Target
- func (g *Gen) ParsePackages(context *generator.Context) (sets.String, sets.String, string, string)
- type GenUnversionedType
- type ResourceTags
- type Struct
- type SubresourceTags
Constants ¶
This section is empty.
Variables ¶
var APIsTemplate = `` /* 1155-byte string literal not displayed */
var InstallAPITemplate = `` /* 649-byte string literal not displayed */
var UnversionedAPIImports = []string{
"fmt",
"context",
"k8s.io/apimachinery/pkg/apis/meta/internalversion",
"k8s.io/apimachinery/pkg/runtime",
"k8s.io/apimachinery/pkg/runtime/schema",
"k8s.io/apiserver/pkg/registry/generic",
"k8s.io/apiserver/pkg/registry/rest",
"github.com/devsy-org/apiserver/pkg/builders",
"github.com/devsy-org/apiserver/pkg/managerfactory",
}
var UnversionedAPITemplate = `
type NewRESTFunc func(factory managerfactory.SharedManagerFactory) rest.Storage
var (
{{ range $api := .UnversionedResources -}}
{{ if $api.REST -}}
{{$api.Group|public}}{{$api.Kind}}Storage = builders.NewApiResourceWithStorage( // Resource status endpoint
Internal{{ $api.Kind }},
func() runtime.Object { return &{{ $api.Kind }}{} }, // Register versioned resource
func() runtime.Object { return &{{ $api.Kind }}List{} }, // Register versioned resource list
New{{ $api.REST }},
)
New{{ $api.REST }} = func(getter generic.RESTOptionsGetter) rest.Storage {
return New{{ $api.REST }}Func(Factory)
}
New{{ $api.REST }}Func NewRESTFunc
{{ if $api.StatusREST -}}
New{{ $api.StatusREST }} = func(getter generic.RESTOptionsGetter) rest.Storage {
return New{{ $api.StatusREST }}Func(Factory)
}
New{{ $api.StatusREST }}Func NewRESTFunc
{{ end -}}
{{ else -}}
{{$api.Group|public}}{{$api.Kind}}Storage = builders.NewApiResource( // Resource status endpoint
Internal{{ $api.Kind }},
func() runtime.Object { return &{{ $api.Kind }}{} }, // Register versioned resource
func() runtime.Object { return &{{ $api.Kind }}List{} }, // Register versioned resource list
&{{ $api.Strategy }}{builders.StorageStrategySingleton},
)
{{ end -}}
{{ end -}}
{{ range $api := .UnversionedResources -}}
{{- if $api.ShortName -}}
Internal{{ $api.Kind }} = builders.NewInternalResourceWithShortcuts(
{{ else -}}
Internal{{ $api.Kind }} = builders.NewInternalResource(
{{ end -}}
"{{ $api.Resource }}",
"{{ $api.Kind }}",
func() runtime.Object { return &{{ $api.Kind }}{} },
func() runtime.Object { return &{{ $api.Kind }}List{} },
{{ if $api.ShortName -}}
[]string{"{{ $api.ShortName }}"},
[]string{"aggregation"}, // TBD
{{ end -}}
)
Internal{{ $api.Kind }}Status = builders.NewInternalResourceStatus(
"{{ $api.Resource }}",
"{{ $api.Kind }}Status",
func() runtime.Object { return &{{ $api.Kind }}{} },
func() runtime.Object { return &{{ $api.Kind }}List{} },
)
{{ range $subresource := .Subresources -}}
Internal{{$subresource.Kind}}REST = builders.NewInternalSubresource(
"{{$subresource.Resource}}", "{{$subresource.Request}}", "{{$subresource.Path}}",
func() runtime.Object { return &{{$subresource.Request}}{} },
)
New{{$subresource.Kind}}REST = func(getter generic.RESTOptionsGetter) rest.Storage {
return New{{$subresource.Kind}}RESTFunc(Factory)
}
New{{$subresource.Kind}}RESTFunc NewRESTFunc
{{ end -}}
{{ end -}}
// Registered resources and subresources
ApiVersion = builders.NewApiGroup("{{.Group}}.{{.Domain}}").WithKinds(
{{ range $api := .UnversionedResources -}}
Internal{{$api.Kind}},
Internal{{$api.Kind}}Status,
{{ range $subresource := $api.Subresources -}}
Internal{{$subresource.Kind}}REST,
{{ end -}}
{{ end -}}
)
// Required by code generated by go2idl
AddToScheme = (&runtime.SchemeBuilder{
ApiVersion.SchemeBuilder.AddToScheme,
RegisterDefaults,
}).AddToScheme
SchemeBuilder = ApiVersion.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
SchemeGroupVersion = ApiVersion.GroupVersion
)
// Required by code generated by go2idl
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Required by code generated by go2idl
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
{{ range $a := .Aliases -}}
type {{ $a.Name }} {{ $a.UnderlyingTypeName }}
{{ end -}}
{{ range $s := .Structs -}}
{{ if $s.GenUnversioned -}}
{{ if $s.GenClient }}// +genclient{{end}}
{{ if and $s.GenClient $s.NonNamespaced }}// +genclient:nonNamespaced{{end}}
{{ if $s.GenDeepCopy }}// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object{{end}}
type {{ $s.Name }} struct {
{{ range $f := $s.Fields -}}
{{ $f.Name }} {{ $f.UnversionedType }} {{ $f.Annotation }}
{{ end -}}
}
{{ end -}}
{{ end -}}
{{ range $api := .UnversionedResources -}}
//
// {{.Kind}} Functions and Structs
//
// +k8s:deepcopy-gen=false
type {{.Strategy}} struct {
builders.DefaultStorageStrategy
}
// +k8s:deepcopy-gen=false
type {{.StatusStrategy}} struct {
builders.DefaultStatusStorageStrategy
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$api.Kind}}List struct {
metav1.TypeMeta ` + "`json:\",inline\"`" + `
metav1.ListMeta ` + "`json:\"metadata,omitempty\"`" + `
Items []{{$api.Kind}} ` + "`json:\"items\"`" + `
}
{{ range $subresource := $api.Subresources -}}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$subresource.Request}}List struct {
metav1.TypeMeta ` + "`json:\",inline\"`" + `
metav1.ListMeta ` + "`json:\"metadata,omitempty\"`" + `
Items []{{$subresource.Request}} ` + "`json:\"items\"`" + `
}
{{ end -}}
func ({{$api.Kind}}) NewStatus() interface{} {
return {{$api.Kind}}Status{}
}
func (pc *{{$api.Kind}}) GetStatus() interface{} {
return pc.Status
}
func (pc *{{$api.Kind}}) SetStatus(s interface{}) {
pc.Status = s.({{$api.Kind}}Status)
}
func (pc *{{$api.Kind}}) GetSpec() interface{} {
return pc.Spec
}
func (pc *{{$api.Kind}}) SetSpec(s interface{}) {
pc.Spec = s.({{$api.Kind}}Spec)
}
func (pc *{{$api.Kind}}) GetObjectMeta() *metav1.ObjectMeta {
return &pc.ObjectMeta
}
func (pc *{{$api.Kind}}) SetGeneration(generation int64) {
pc.ObjectMeta.Generation = generation
}
func (pc {{$api.Kind}}) GetGeneration() int64 {
return pc.ObjectMeta.Generation
}
// Registry is an interface for things that know how to store {{.Kind}}.
// +k8s:deepcopy-gen=false
type {{.Kind}}Registry interface {
List{{.Kind}}s(ctx context.Context, options *internalversion.ListOptions) (*{{.Kind}}List, error)
Get{{.Kind}}(ctx context.Context, id string, options *metav1.GetOptions) (*{{.Kind}}, error)
Create{{.Kind}}(ctx context.Context, id *{{.Kind}}) (*{{.Kind}}, error)
Update{{.Kind}}(ctx context.Context, id *{{.Kind}}) (*{{.Kind}}, error)
Delete{{.Kind}}(ctx context.Context, id string) (bool, error)
}
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched types will panic.
func New{{.Kind}}Registry(sp builders.StandardStorageProvider) {{.Kind}}Registry {
return &storage{{.Kind}}{sp}
}
// Implement Registry
// storage puts strong typing around storage calls
// +k8s:deepcopy-gen=false
type storage{{.Kind}} struct {
builders.StandardStorageProvider
}
func (s *storage{{.Kind}}) List{{.Kind}}s(ctx context.Context, options *internalversion.ListOptions) (*{{.Kind}}List, error) {
if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() {
return nil, fmt.Errorf("field selector not supported yet")
}
st := s.GetStandardStorage()
obj, err := st.List(ctx, options)
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}List), err
}
func (s *storage{{.Kind}}) Get{{.Kind}}(ctx context.Context, id string, options *metav1.GetOptions) (*{{.Kind}}, error) {
st := s.GetStandardStorage()
obj, err := st.Get(ctx, id, options)
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}), nil
}
func (s *storage{{.Kind}}) Create{{.Kind}}(ctx context.Context, object *{{.Kind}}) (*{{.Kind}}, error) {
st := s.GetStandardStorage()
obj, err := st.Create(ctx, object, nil, &metav1.CreateOptions{})
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}), nil
}
func (s *storage{{.Kind}}) Update{{.Kind}}(ctx context.Context, object *{{.Kind}}) (*{{.Kind}}, error) {
st := s.GetStandardStorage()
obj, _, err := st.Update(ctx, object.Name, rest.DefaultUpdatedObjectInfo(object), nil, nil, false, &metav1.UpdateOptions{})
if err != nil {
return nil, err
}
return obj.(*{{.Kind}}), nil
}
func (s *storage{{.Kind}}) Delete{{.Kind}}(ctx context.Context, id string) (bool, error) {
st := s.GetStandardStorage()
_, sync, err := st.Delete(ctx, id, nil, &metav1.DeleteOptions{})
return sync, err
}
{{ end -}}
`
var VersionedAPITemplate = `
func addKnownTypes(scheme *runtime.Scheme) error {
// TODO this will get cleaned up with the scheme types are fixed
scheme.AddKnownTypes(SchemeGroupVersion,
{{ range $api := .Resources -}}
&{{ $api.Kind }}{},
&{{ $api.Kind }}List{},
{{ range $subresource := $api.Subresources -}}
&{{ $subresource.Kind }}{},
{{ end -}}
{{ end -}}
)
return nil
}
var (
ApiVersion = builders.NewApiVersion("{{.Group}}.{{.Domain}}", "{{.Version}}").WithResources(
{{ range $api := .Resources -}}
{{$api.Group}}.{{$api.Group|public}}{{$api.Kind}}Storage,
{{ if $api.REST }}{{ else -}}
builders.NewApiResource( // Resource status endpoint
{{ $api.Group }}.Internal{{ $api.Kind }}Status,
func() runtime.Object { return &{{ $api.Kind }}{} }, // Register versioned resource
func() runtime.Object { return &{{ $api.Kind }}List{} }, // Register versioned resource list
&{{ $api.Group }}.{{ $api.StatusStrategy }}{DefaultStatusStorageStrategy: builders.StatusStorageStrategySingleton},
),{{ end -}}
{{ if $api.StatusREST -}}
builders.NewApiResourceWithStorage(
{{ $api.Group }}.Internal{{ $api.Kind }}Status,
func() runtime.Object { return &{{ $api.Kind }}{} }, // Register versioned resource
func() runtime.Object { return &{{ $api.Kind }}List{} }, // Register versioned resource list
{{ $api.Group }}.New{{ $api.Kind }}StatusREST),
{{ end -}}
{{ range $subresource := $api.Subresources -}}
builders.NewApiResourceWithStorage(
{{ $api.Group }}.Internal{{ $subresource.Kind }}REST,
func() runtime.Object { return &{{ $subresource.Request }}{} }, // Register versioned resource
nil,
{{ $api.Group }}.New{{ $subresource.REST }},
),
{{ end -}}
{{ end -}}
)
// Required by code generated by go2idl
AddToScheme = (&runtime.SchemeBuilder{
ApiVersion.SchemeBuilder.AddToScheme,
RegisterDefaults,
RegisterConversions,
addKnownTypes,
func(scheme *runtime.Scheme) error {
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
},
}).AddToScheme
SchemeBuilder = ApiVersion.SchemeBuilder
localSchemeBuilder = SchemeBuilder
SchemeGroupVersion = ApiVersion.GroupVersion
)
// Required by code generated by go2idl
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Required by code generated by go2idl
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
{{ range $api := .Resources -}}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$api.Kind}}List struct {
metav1.TypeMeta ` + "`json:\",inline\"`" + `
metav1.ListMeta ` + "`json:\"metadata,omitempty\"`" + `
Items []{{$api.Kind}} ` + "`json:\"items\"`" + `
}
{{ range $subresource := $api.Subresources -}}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type {{$subresource.Request}}List struct {
metav1.TypeMeta ` + "`json:\",inline\"`" + `
metav1.ListMeta ` + "`json:\"metadata,omitempty\"`" + `
Items []{{$subresource.Request}} ` + "`json:\"items\"`" + `
}
{{ end }}{{ end -}}
`
Functions ¶
func CreateApisGenerator ¶
func CreateInstallGenerator ¶
func CreateVersionedGenerator ¶
func CreateVersionedGenerator( apiversion *APIVersion, apigroup *APIGroup, filename string, ) generator.Generator
func GetGroupPackage ¶
func HasSubresource ¶
HasSubresource returns true if t is an APIResource with one or more Subresources.
func IsAPIResource ¶
IsAPIResource returns true if t has a +resource comment tag.
func IsAPISubresource ¶
IsAPISubresource returns true if t has a +subresource-request comment tag.
func IsNonNamespaced ¶
IsNonNamespaced returns true if t has a +nonNamespaced comment tag.
func LoadGoBoilerplate ¶
LoadGoBoilerplate loads the boilerplate file passed to --go-header-file.
Types ¶
type APIGroup ¶
type APIGroup struct {
// Package is the name of the go package the api group is under - e.g. github.com/pwittrock/apiserver-helloworld/apis
Package string
// Domain is the domain portion of the group - e.g. k8s.io
Domain string
// Group is the short name of the group - e.g. mushroomkingdom
Group string
GroupTitle string
// Versions is the list of all versions for this group keyed by name
Versions map[string]*APIVersion
UnversionedResources map[string]*APIResource
// Structs is a list of unversioned definitions that must be generated
Structs []*Struct
Aliases map[string]*Alias
Pkg *types.Package
PkgPath string
// contains filtered or unexported fields
}
type APIResource ¶
type APIResource struct {
// Domain is the group domain - e.g. k8s.io
Domain string
// Group is the group name - e.g. mushroomkingdom
Group string
// Version is the api version - e.g. v1beta1
Version string
// Kind is the resource name - e.g. PeachesCastle
Kind string
// Resource is the resource name - e.g. peachescastles
Resource string
// ShortName is the resource short name - e.g. pc
ShortName string
// REST is the rest.Storage implementation used to handle requests
// This field is optional. The standard REST implementation will be used
// by default.
REST string
// StatusREST is the status rest.Storage implementation used to handle
// status requests.
StatusREST string
// Subresources is a map of subresources keyed by name
Subresources map[string]*APISubresource
// Type is the Type object from code-gen
Type *types.Type
// Strategy is name of the struct to use for the strategy
Strategy string
// Strategy is name of the struct to use for the strategy
StatusStrategy string
// NonNamespaced indicates that the resource kind is non namespaced
NonNamespaced bool
}
type APISubresource ¶
type APISubresource struct {
// Domain is the group domain - e.g. k8s.io
Domain string
// Group is the group name - e.g. mushroomkingdom
Group string
// Version is the api version - e.g. v1beta1
Version string
// Kind is the resource name - e.g. PeachesCastle
Kind string
// Resource is the resource name - e.g. peachescastles
Resource string
// Request is the subresource request type - e.g. ScaleCastle
Request string
// REST is the rest.Storage implementation used to handle requests
REST string
// Path is the subresource path - e.g. scale
Path string
// ImportPackage is the import statement that must appear for the Request
ImportPackage string
// RequestType is the type of the request
RequestType *types.Type
// RESTType is the type of the request handler
RESTType *types.Type
}
type APIVersion ¶
type APIVersion struct {
// Domain is the group domain - e.g. k8s.io
Domain string
// Group is the group name - e.g. mushroomkingdom
Group string
// Version is the api version - e.g. v1beta1
Version string
// Resources is a list of resources appearing in the API version keyed by name
Resources map[string]*APIResource
// Pkg is the Package object from code-gen
Pkg *types.Package
}
type APIs ¶
type APIs struct {
// Domain is the domain portion of the group - e.g. k8s.io
Domain string
// Package is the name of the go package the api group is under - e.g. github.com/pwittrock/apiserver-helloworld/apis
Package string
Pkg *types.Package
// Groups is a list of API groups
Groups map[string]*APIGroup
}
type APIsBuilder ¶
type APIsBuilder struct {
Domain string
VersionedPkgs sets.String
UnversionedPkgs sets.String
APIsPkg string
APIsPkgRaw *types.Package
GroupNames sets.String
APIs *APIs
ByGroupKindVersion map[string]map[string]map[string]*APIResource
ByGroupVersionKind map[string]map[string]map[string]*APIResource
SubByGroupVersionKind map[string]map[string]map[string]*types.Type
Groups map[string]types.Package
// contains filtered or unexported fields
}
func NewAPIsBuilder ¶
func NewAPIsBuilder(context *generator.Context) *APIsBuilder
func (*APIsBuilder) GenDeepCopy ¶
func (b *APIsBuilder) GenDeepCopy(c *types.Type) bool
func (*APIsBuilder) GetControllerTag ¶
func (b *APIsBuilder) GetControllerTag(c *types.Type) string
func (*APIsBuilder) GetNameAndImport ¶
func (b *APIsBuilder) GetNameAndImport(tags SubresourceTags) (string, string)
GetNameAndImport converts.
func (*APIsBuilder) GetResourceTag ¶
func (b *APIsBuilder) GetResourceTag(c *types.Type) string
GetResourceTag returns the value of the "+resource=" comment tag.
func (*APIsBuilder) GetSubresourceTags ¶
func (b *APIsBuilder) GetSubresourceTags(c *types.Type) []string
func (*APIsBuilder) GetSubresources ¶
func (b *APIsBuilder) GetSubresources(c *APIResource) map[string]*APISubresource
func (*APIsBuilder) IsInPackage ¶
func (b *APIsBuilder) IsInPackage(tags SubresourceTags) bool
Returns true if the subresource Request type is in the same package as the resource type.
func (*APIsBuilder) ParseAPIs ¶
func (b *APIsBuilder) ParseAPIs()
func (*APIsBuilder) ParseDomain ¶
func (b *APIsBuilder) ParseDomain()
ParseDomain parses the domain from the apis/doc.go file comment "// +domain=YOUR_DOMAIN".
func (*APIsBuilder) ParseGroupNames ¶
func (b *APIsBuilder) ParseGroupNames()
ParseGroupNames initializes b.GroupNames with the set of all groups.
func (*APIsBuilder) ParseIndex ¶
func (b *APIsBuilder) ParseIndex()
ParseIndex indexes all types with the comment "// +resource=RESOURCE" by GroupVersionKind and GroupKindVersion.
func (*APIsBuilder) ParsePackages ¶
func (b *APIsBuilder) ParsePackages()
ParsePackages parses out the sets of Versioned, Unversioned packages and identifies the root Apis package.
func (*APIsBuilder) ParseStructsAndAliases ¶
func (b *APIsBuilder) ParseStructsAndAliases(apigroup *APIGroup)
type Comments ¶
type Comments []string
Comments is a structure for using comment tags on go structs and fields.
func (Comments) GetTag ¶
GetTags returns the value for the first comment with a prefix matching "+name=" e.g. "+name=foo\n+name=bar" would return "foo".
type Gen ¶
type Gen struct {
GroupConverter func(apigroup *APIGroup)
// contains filtered or unexported fields
}
func (*Gen) DefaultNameSystem ¶
DefaultNameSystem returns the default name system for ordering the types to be processed by the generators in this package.
func (*Gen) NameSystems ¶
func (g *Gen) NameSystems() namer.NameSystems
NameSystems returns the name system used by the generators in this package.
type GenUnversionedType ¶
type GenUnversionedType struct {
Type *types.Type
Resource *APIResource
}
type ResourceTags ¶
type ResourceTags struct {
Resource string
REST string
StatusREST string
Strategy string
ShortName string
}
ResourceTags contains the tags present in a "+resource=" comment.
func ParseResourceTag ¶
func ParseResourceTag(tag string) ResourceTags
ParseResourceTag parses the tags in a "+resource=" comment into a ResourceTags struct.
type SubresourceTags ¶
SubresourceTags contains the tags present in a "+subresource=" comment.
func ParseSubresourceTag ¶
func ParseSubresourceTag(c *APIResource, tag string) SubresourceTags
ParseSubresourceTag parses the tags in a "+subresource=" comment into a SubresourceTags struct.