diff --git a/cmd/registry/controller/controller.go b/cmd/registry/controller/controller.go index 4e55d51f8..cd35e03a5 100644 --- a/cmd/registry/controller/controller.go +++ b/cmd/registry/controller/controller.go @@ -144,7 +144,7 @@ func generateActions( ctx context.Context, client connection.Client, resourcePattern string, - resourceList []Resource, + resourceList []ResourceInstance, dependencyMaps []map[string]time.Time, generatedResource *rpc.GeneratedResource) ([]*Action, error) { diff --git a/cmd/registry/controller/list-apis.go b/cmd/registry/controller/list-apis.go deleted file mode 100644 index 0de7c6da5..000000000 --- a/cmd/registry/controller/list-apis.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controller - -import ( - "github.com/apigee/registry/rpc" -) - -func GenerateApiHandler(result *[]Resource) func(*rpc.Api) { - return func(api *rpc.Api) { - resource := ApiResource{Api: api} - (*result) = append((*result), resource) - } -} diff --git a/cmd/registry/controller/list-artifacts.go b/cmd/registry/controller/list-artifacts.go deleted file mode 100644 index 0ddd889bd..000000000 --- a/cmd/registry/controller/list-artifacts.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controller - -import ( - "github.com/apigee/registry/rpc" -) - -func GenerateArtifactHandler(result *[]Resource) func(*rpc.Artifact) { - return func(artifact *rpc.Artifact) { - resource := ArtifactResource{Artifact: artifact} - (*result) = append((*result), resource) - } -} diff --git a/cmd/registry/controller/list-specs.go b/cmd/registry/controller/list-specs.go deleted file mode 100644 index d9b6229c1..000000000 --- a/cmd/registry/controller/list-specs.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controller - -import ( - "github.com/apigee/registry/rpc" -) - -func GenerateSpecHandler(result *[]Resource) func(*rpc.ApiSpec) { - return func(spec *rpc.ApiSpec) { - resource := SpecResource{Spec: spec} - (*result) = append((*result), resource) - } -} diff --git a/cmd/registry/controller/list.go b/cmd/registry/controller/list.go index b243fe4fc..d6a6529de 100644 --- a/cmd/registry/controller/list.go +++ b/cmd/registry/controller/list.go @@ -19,29 +19,34 @@ import ( "github.com/apigee/registry/cmd/registry/core" "github.com/apigee/registry/connection" + "github.com/apigee/registry/rpc" "github.com/apigee/registry/server/registry/names" ) -func ListResources(ctx context.Context, client connection.Client, pattern, filter string) ([]Resource, error) { - var result []Resource +func ListResources(ctx context.Context, client connection.Client, pattern, filter string) ([]ResourceInstance, error) { + var result []ResourceInstance var err2 error // First try to match collection names. if api, err := names.ParseApiCollection(pattern); err == nil { - err2 = core.ListAPIs(ctx, client, api, filter, GenerateApiHandler(&result)) + err2 = core.ListAPIs(ctx, client, api, filter, generateApiHandler(&result)) + } else if version, err := names.ParseVersionCollection(pattern); err == nil { + err2 = core.ListVersions(ctx, client, version, filter, generateVersionHandler(&result)) } else if spec, err := names.ParseSpecCollection(pattern); err == nil { - err2 = core.ListSpecs(ctx, client, spec, filter, GenerateSpecHandler(&result)) + err2 = core.ListSpecs(ctx, client, spec, filter, generateSpecHandler(&result)) } else if artifact, err := names.ParseArtifactCollection(pattern); err == nil { - err2 = core.ListArtifacts(ctx, client, artifact, filter, false, GenerateArtifactHandler(&result)) + err2 = core.ListArtifacts(ctx, client, artifact, filter, false, generateArtifactHandler(&result)) } // Then try to match resource names. if api, err := names.ParseApi(pattern); err == nil { - err2 = core.ListAPIs(ctx, client, api, filter, GenerateApiHandler(&result)) + err2 = core.ListAPIs(ctx, client, api, filter, generateApiHandler(&result)) + } else if version, err := names.ParseVersion(pattern); err == nil { + err2 = core.ListVersions(ctx, client, version, filter, generateVersionHandler(&result)) } else if spec, err := names.ParseSpec(pattern); err == nil { - err2 = core.ListSpecs(ctx, client, spec, filter, GenerateSpecHandler(&result)) + err2 = core.ListSpecs(ctx, client, spec, filter, generateSpecHandler(&result)) } else if artifact, err := names.ParseArtifact(pattern); err == nil { - err2 = core.ListArtifacts(ctx, client, artifact, filter, false, GenerateArtifactHandler(&result)) + err2 = core.ListArtifacts(ctx, client, artifact, filter, false, generateArtifactHandler(&result)) } if err2 != nil { @@ -50,3 +55,59 @@ func ListResources(ctx context.Context, client connection.Client, pattern, filte return result, nil } + +func generateApiHandler(result *[]ResourceInstance) func(*rpc.Api) { + return func(api *rpc.Api) { + apiName, err := names.ParseApi(api.GetName()) + if err != nil { + panic(err) + } + resource := ApiResource{ + ApiName: ApiName{Api: apiName}, + UpdateTimestamp: api.UpdateTime.AsTime(), + } + (*result) = append((*result), resource) + } +} + +func generateVersionHandler(result *[]ResourceInstance) func(*rpc.ApiVersion) { + return func(version *rpc.ApiVersion) { + versionName, err := names.ParseVersion(version.GetName()) + if err != nil { + panic(err) + } + resource := VersionResource{ + VersionName: VersionName{Version: versionName}, + UpdateTimestamp: version.UpdateTime.AsTime(), + } + (*result) = append((*result), resource) + } +} + +func generateSpecHandler(result *[]ResourceInstance) func(*rpc.ApiSpec) { + return func(spec *rpc.ApiSpec) { + specName, err := names.ParseSpec(spec.GetName()) + if err != nil { + panic(err) + } + resource := SpecResource{ + SpecName: SpecName{Spec: specName}, + UpdateTimestamp: spec.RevisionUpdateTime.AsTime(), + } + (*result) = append((*result), resource) + } +} + +func generateArtifactHandler(result *[]ResourceInstance) func(*rpc.Artifact) { + return func(artifact *rpc.Artifact) { + artifactName, err := names.ParseArtifact(artifact.GetName()) + if err != nil { + panic(err) + } + resource := ArtifactResource{ + ArtifactName: ArtifactName{Artifact: artifactName}, + UpdateTimestamp: artifact.UpdateTime.AsTime(), + } + (*result) = append((*result), resource) + } +} diff --git a/cmd/registry/controller/parser.go b/cmd/registry/controller/parser.go index 33a8455d7..791593bda 100644 --- a/cmd/registry/controller/parser.go +++ b/cmd/registry/controller/parser.go @@ -25,6 +25,51 @@ import ( const resourceKW = "$resource" +func parseResourceCollection(resourcePattern string) (ResourceName, error) { + if api, err := names.ParseApiCollection(resourcePattern); err == nil { + return ApiName{Api: api}, nil + } else if version, err := names.ParseVersionCollection(resourcePattern); err == nil { + return VersionName{Version: version}, nil + } else if spec, err := names.ParseSpecCollection(resourcePattern); err == nil { + return SpecName{Spec: spec}, nil + } else if artifact, err := names.ParseArtifactCollection(resourcePattern); err == nil { + return ArtifactName{Artifact: artifact}, nil + } + + return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern) +} + +func parseResource(resourcePattern string) (ResourceName, error) { + if api, err := names.ParseApi(resourcePattern); err == nil { + return ApiName{Api: api}, nil + } else if version, err := names.ParseVersion(resourcePattern); err == nil { + return VersionName{Version: version}, nil + } else if spec, err := names.ParseSpec(resourcePattern); err == nil { + return SpecName{Spec: spec}, nil + } else if artifact, err := names.ParseArtifact(resourcePattern); err == nil { + return ArtifactName{Artifact: artifact}, nil + } + + return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern) +} + +func parseResourcePattern(resourcePattern string) (ResourceName, error) { + + // First try to match resource collections. + resource, err := parseResourceCollection(resourcePattern) + if err == nil { + return resource, nil + } + + // Then try to match resource names. + resource, err = parseResource(resourcePattern) + if err == nil { + return resource, nil + } + + return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern) +} + func extendDependencyPattern( resourcePattern string, dependencyPattern string, @@ -39,33 +84,38 @@ func extendDependencyPattern( // dependencyPattern: "$resource.api/versions/-" // Returns "projects/demo/locations/global/apis/-/versions/-" + // If there is no $resource prefix, prepend project name and return if !strings.HasPrefix(dependencyPattern, resourceKW) { return fmt.Sprintf("projects/%s/locations/global/%s", projectID, dependencyPattern), nil } - entityRegex := regexp.MustCompile(fmt.Sprintf(`(\%s\.(api|version|spec|artifact))(/|$)`, resourceKW)) - matches := entityRegex.FindStringSubmatch(dependencyPattern) + // Extract the $resource reference + // Example result for the following regex // dependencyPattern: "$resource.api/artifacts/score" // matches: ["$resource.api/", "$resource.api", "api"] + entityRegex := regexp.MustCompile(fmt.Sprintf(`(\%s\.(api|version|spec|artifact))(/|$)`, resourceKW)) + matches := entityRegex.FindStringSubmatch(dependencyPattern) if len(matches) <= 2 { return "", fmt.Errorf("invalid dependency pattern: %s", dependencyPattern) } + // Convert resourcePattern to resourceName to extract entity values (api, spec, version,artifact) + resourceName, err := parseResourcePattern(resourcePattern) + if err != nil { + return "", err + } + entity, entityType := matches[1], matches[2] entityVal := "" switch entityType { case "api": - re := regexp.MustCompile(`.*/apis/[^/]*`) - entityVal = re.FindString(resourcePattern) + entityVal = resourceName.GetApi() case "version": - re := regexp.MustCompile(`.*/versions/[^/]*`) - entityVal = re.FindString(resourcePattern) + entityVal = resourceName.GetVersion() case "spec": - re := regexp.MustCompile(`.*/specs/[^/]*`) - entityVal = re.FindString(resourcePattern) + entityVal = resourceName.GetSpec() case "artifact": - re := regexp.MustCompile(`.*/artifacts/[^/]*`) - entityVal = re.FindString(resourcePattern) + entityVal = resourceName.GetArtifact() default: return "", fmt.Errorf("invalid combination resourcePattern: %q dependencyPattern: %q", resourcePattern, dependencyPattern) } @@ -78,39 +128,51 @@ func extendDependencyPattern( } +// This function is used in case of receipt artifacts where the name of the target resource to create is not known func resourceNameFromGroupKey( resourcePattern string, groupKey string) (string, error) { - // Derives the resource name from the provided resourcePattern and dependencyName. + // Derives the resource name from the provided resourcePattern and groupKey. // Example: // 1) resourcePattern: projects/demo/locations/global/apis/-/versions/-/specs/- - // dependencyName: projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/complexity + // groupKey: projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/complexity // returns projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml // 2) resourcePattern: projects/demo/locations/global/apis/petstore/versions/-/specs/-/artifacts/custom-artifact - // dependencyName: projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/complexity + // groupKey: projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/complexity // returns projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/custom-artifact - // Replace apis/- pattern with the corresponding name and so on. - // dependency.GetApi() returns the full api name projects/demo/locations/global/apis/petstore - // We use stringsSplit()[-1] to extract only the API name - // apiPattern := regexp.MustCompile(`/apis/-`) - apiName := strings.Split(extractEntityName(groupKey, "api"), "/") - resourceName := strings.ReplaceAll(resourcePattern, "/apis/-", - fmt.Sprintf("/apis/%s", apiName[len(apiName)-1])) - - versionName := strings.Split(extractEntityName(groupKey, "version"), "/") - resourceName = strings.ReplaceAll(resourceName, "/versions/-", - fmt.Sprintf("/versions/%s", versionName[len(versionName)-1])) + groupName, err := parseResource(groupKey) + resourceName := resourcePattern + + if err == nil { + // Replace `apis/-` pattern with the corresponding name. + // groupName.GetApi() returns the full api name projects/demo/locations/global/apis/petstore + // We use stringsSplit()[-1] to extract only the API name + apiName := strings.Split(groupName.GetApi(), "/") + if len(apiName) > 0 { + resourceName = strings.ReplaceAll(resourceName, "/apis/-", + fmt.Sprintf("/apis/%s", apiName[len(apiName)-1])) + } - specName := strings.Split(extractEntityName(groupKey, "spec"), "/") - resourceName = strings.ReplaceAll(resourceName, "/specs/-", - fmt.Sprintf("/specs/%s", specName[len(specName)-1])) + versionName := strings.Split(groupName.GetVersion(), "/") + if len(versionName) > 0 { + resourceName = strings.ReplaceAll(resourceName, "/versions/-", + fmt.Sprintf("/versions/%s", versionName[len(versionName)-1])) + } - artifactName := strings.Split(extractEntityName(groupKey, "artifact"), "/") - resourceName = strings.ReplaceAll(resourceName, "/artifacts/-", - fmt.Sprintf("/artifacts/%s", artifactName[len(artifactName)-1])) + specName := strings.Split(groupName.GetSpec(), "/") + if len(specName) > 0 { + resourceName = strings.ReplaceAll(resourceName, "/specs/-", + fmt.Sprintf("/specs/%s", specName[len(specName)-1])) + } - //Validate resourceName + artifactName := strings.Split(groupName.GetArtifact(), "/") + if len(artifactName) > 0 { + resourceName = strings.ReplaceAll(resourceName, "/artifacts/-", + fmt.Sprintf("/artifacts/%s", artifactName[len(artifactName)-1])) + } + } + //Validate generated resourceName if _, err := names.ParseProject(resourceName); err == nil { return resourceName, nil } else if _, err := names.ParseApi(resourceName); err == nil { @@ -146,7 +208,7 @@ func ValidateResourceEntry(resource *rpc.GeneratedResource) error { // Validate that the action contains reference to valid entities. // Same as the group entity - entity, entityType, err := getCommandEntitity(resource.Action) + entity, entityType, err := getCommandEntity(resource.Action) if err != nil { return err } @@ -185,8 +247,8 @@ func getGroupEntity(pattern string) (string, error) { return matches[1], nil } -func getGroupKey(pattern string, resource Resource) (string, error) { - // Reads the sourcePattern, finds out group by entity type and returns the group value +func getGroupKey(pattern string, resource ResourceInstance) (string, error) { + // Reads the pattern and returns the group value for the resource // Example: // pattern: $resource.api/versions/-/specs/- // resource: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml" @@ -214,7 +276,7 @@ func getGroupKey(pattern string, resource Resource) (string, error) { } -func getCommandEntitity(action string) (string, string, error) { +func getCommandEntity(action string) (string, string, error) { // Check if there is a reference to $n in the action isMatch, err := regexp.MatchString(fmt.Sprintf(`\%s`, resourceKW), action) if err != nil { @@ -250,7 +312,7 @@ func getCommandEntitity(action string) (string, string, error) { func generateCommand(action string, resourceName string) (string, error) { - entity, entityType, err := getCommandEntitity(action) + entity, entityType, err := getCommandEntity(action) if err != nil { return "", err } @@ -260,7 +322,24 @@ func generateCommand(action string, resourceName string) (string, error) { return action, nil } - entityVal := extractEntityName(resourceName, entityType) + resource, err := parseResource(resourceName) + if err != nil { + return "", fmt.Errorf("error generating command, invalid resourceName: %s", resourceName) + } + + entityVal := "" + switch entityType { + case "api": + entityVal = resource.GetApi() + case "version": + entityVal = resource.GetVersion() + case "spec": + entityVal = resource.GetSpec() + case "artifact": + entityVal = resource.GetArtifact() + default: + entityVal = "" + } if len(entityVal) == 0 { return "", fmt.Errorf("error generating command, cannot derive args for action. Invalid action: %s", action) @@ -269,9 +348,3 @@ func generateCommand(action string, resourceName string) (string, error) { return action, nil } - -func extractEntityName(name string, group_name string) string { - re := regexp.MustCompile(fmt.Sprintf(".*\\/%ss\\/[^\\/]*", group_name)) - group := re.FindString(name) - return group -} diff --git a/cmd/registry/controller/parser_test.go b/cmd/registry/controller/parser_test.go index 70855c219..a39f2caeb 100644 --- a/cmd/registry/controller/parser_test.go +++ b/cmd/registry/controller/parser_test.go @@ -18,9 +18,28 @@ import ( "fmt" "testing" - "github.com/apigee/registry/rpc" + // "github.com/apigee/registry/rpc" + "github.com/apigee/registry/server/registry/names" ) +func generateSpec(t *testing.T, specName string) names.Spec { + t.Helper() + spec, err := names.ParseSpec(specName) + if err != nil { + t.Fatalf("Failed generateSpec(%s): %s", specName, err.Error()) + } + return spec +} + +func generateArtifact(t *testing.T, artifactName string) names.Artifact { + t.Helper() + artifact, err := names.ParseArtifact(artifactName) + if err != nil { + t.Fatalf("Failed generateSpec(%s): %s", artifactName, err.Error()) + } + return artifact +} + func TestExtendDependencyPattern(t *testing.T) { tests := []struct { desc string @@ -188,15 +207,14 @@ func TestGetGroupKey(t *testing.T) { tests := []struct { desc string pattern string - resource Resource + resource ResourceInstance want string }{ { desc: "api group", pattern: "$resource.api/versions/-/specs/-", resource: SpecResource{ - Spec: &rpc.ApiSpec{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"}, + SpecName: SpecName{Spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")}, }, want: "projects/demo/locations/global/apis/petstore", }, @@ -204,8 +222,7 @@ func TestGetGroupKey(t *testing.T) { desc: "version group", pattern: "$resource.version/specs/-", resource: SpecResource{ - Spec: &rpc.ApiSpec{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"}, + SpecName: SpecName{Spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")}, }, want: "projects/demo/locations/global/apis/petstore/versions/1.0.0", }, @@ -213,8 +230,7 @@ func TestGetGroupKey(t *testing.T) { desc: "spec group", pattern: "$resource.spec", resource: SpecResource{ - Spec: &rpc.ApiSpec{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"}, + SpecName: SpecName{Spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")}, }, want: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml", }, @@ -222,8 +238,7 @@ func TestGetGroupKey(t *testing.T) { desc: "artifact group", pattern: "$resource.artifact", resource: ArtifactResource{ - Artifact: &rpc.Artifact{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic"}, + ArtifactName: ArtifactName{Artifact: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic")}, }, want: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic", }, @@ -231,8 +246,7 @@ func TestGetGroupKey(t *testing.T) { desc: "no group", pattern: "apis/-/versions/-/specs/-", resource: ArtifactResource{ - Artifact: &rpc.Artifact{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic"}, + ArtifactName: ArtifactName{Artifact: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic")}, }, want: "default", }, @@ -255,30 +269,27 @@ func TestGetGroupKeyError(t *testing.T) { tests := []struct { desc string pattern string - resource Resource + resource ResourceInstance }{ { desc: "typo", pattern: "$resource.apis/versions/-/specs/-", resource: SpecResource{ - Spec: &rpc.ApiSpec{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"}, + SpecName: SpecName{Spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")}, }, }, { desc: "incorrect reference", pattern: "$resource.name/versions/-/specs/-", resource: SpecResource{ - Spec: &rpc.ApiSpec{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"}, + SpecName: SpecName{Spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")}, }, }, { desc: "incorrect resourceKW", pattern: "$resources.api/versions/-/specs/-", resource: SpecResource{ - Spec: &rpc.ApiSpec{ - Name: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"}, + SpecName: SpecName{Spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")}, }, }, } diff --git a/cmd/registry/controller/resource-name.go b/cmd/registry/controller/resource-name.go new file mode 100644 index 000000000..dfe9e1b57 --- /dev/null +++ b/cmd/registry/controller/resource-name.go @@ -0,0 +1,164 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +import ( + "github.com/apigee/registry/server/registry/names" +) + +// This interface is used to describe resource patterns +// Example: +// projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml +// projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/- +type ResourceName interface { + GetArtifact() string + GetSpec() string + GetVersion() string + GetApi() string + GetName() string +} + +type SpecName struct { + Spec names.Spec +} + +func (s SpecName) GetArtifact() string { + return "" +} + +func (s SpecName) GetSpec() string { + return s.Spec.String() +} + +func (s SpecName) GetVersion() string { + return s.Spec.Version().String() +} + +func (s SpecName) GetApi() string { + return s.Spec.Api().String() +} + +func (s SpecName) GetName() string { + return s.Spec.String() +} + +type VersionName struct { + Version names.Version +} + +func (v VersionName) GetArtifact() string { + return "" +} + +func (v VersionName) GetSpec() string { + return "" +} + +func (v VersionName) GetVersion() string { + return v.Version.String() +} + +func (v VersionName) GetApi() string { + return v.Version.Api().String() +} + +func (v VersionName) GetName() string { + return v.Version.String() +} + +type ApiName struct { + Api names.Api +} + +func (a ApiName) GetArtifact() string { + return "" +} + +func (a ApiName) GetSpec() string { + return "" +} + +func (a ApiName) GetVersion() string { + return "" +} + +func (a ApiName) GetApi() string { + return a.Api.String() +} + +func (a ApiName) GetName() string { + return a.Api.String() +} + +type ArtifactName struct { + Artifact names.Artifact +} + +func (ar ArtifactName) GetArtifact() string { + return ar.Artifact.String() +} + +func (ar ArtifactName) GetSpec() string { + specPattern := names.Spec{ + ProjectID: ar.Artifact.ProjectID(), + ApiID: ar.Artifact.ApiID(), + VersionID: ar.Artifact.VersionID(), + SpecID: ar.Artifact.SpecID(), + } + + // Validate the generated name + if spec, err := names.ParseSpec(specPattern.String()); err == nil { + return spec.String() + } else if _, err := names.ParseSpecCollection(specPattern.String()); err == nil { + return spec.String() + } + + return "" +} + +func (ar ArtifactName) GetVersion() string { + versionPattern := names.Version{ + ProjectID: ar.Artifact.ProjectID(), + ApiID: ar.Artifact.ApiID(), + VersionID: ar.Artifact.VersionID(), + } + // Validate the generated name + if version, err := names.ParseVersion(versionPattern.String()); err == nil { + return version.String() + } else if _, err := names.ParseVersionCollection(versionPattern.String()); err == nil { + return version.String() + } + + return "" +} + +func (ar ArtifactName) GetApi() string { + apiPattern := names.Api{ + ProjectID: ar.Artifact.ProjectID(), + ApiID: ar.Artifact.ApiID(), + } + // Validate the generated name + if _, err := names.ParseApi(apiPattern.String()); err == nil { + return apiPattern.String() + } else if _, err := names.ParseApiCollection(apiPattern.String()); err == nil { + return apiPattern.String() + } + + return "" +} + +func (ar ArtifactName) GetName() string { + return ar.Artifact.String() +} diff --git a/cmd/registry/controller/resource.go b/cmd/registry/controller/resource.go new file mode 100644 index 000000000..3f00e1121 --- /dev/null +++ b/cmd/registry/controller/resource.go @@ -0,0 +1,62 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +import ( + "time" +) + +// This interface is used to describe resource instances +// ResourceName is embeded, the only additional field is the UpdateTimestamp +type ResourceInstance interface { + ResourceName + GetUpdateTimestamp() time.Time +} + +type SpecResource struct { + SpecName + UpdateTimestamp time.Time +} + +func (s SpecResource) GetUpdateTimestamp() time.Time { + return s.UpdateTimestamp +} + +type VersionResource struct { + VersionName + UpdateTimestamp time.Time +} + +func (v VersionResource) GetUpdateTimestamp() time.Time { + return v.UpdateTimestamp +} + +type ApiResource struct { + ApiName + UpdateTimestamp time.Time +} + +func (a ApiResource) GetUpdateTimestamp() time.Time { + return a.UpdateTimestamp +} + +type ArtifactResource struct { + ArtifactName + UpdateTimestamp time.Time +} + +func (ar ArtifactResource) GetUpdateTimestamp() time.Time { + return ar.UpdateTimestamp +} diff --git a/cmd/registry/controller/resources.go b/cmd/registry/controller/resources.go deleted file mode 100644 index 3c0774ad9..000000000 --- a/cmd/registry/controller/resources.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controller - -import ( - "time" - - "github.com/apigee/registry/rpc" -) - -type Resource interface { - GetArtifact() string - GetSpec() string - GetVersion() string - GetApi() string - GetName() string - GetUpdateTimestamp() time.Time - ExtractResourceGroup(string) string -} - -type SpecResource struct { - Spec *rpc.ApiSpec -} - -func (s SpecResource) GetArtifact() string { - return "" -} - -func (s SpecResource) GetSpec() string { - return s.Spec.Name -} - -func (s SpecResource) GetVersion() string { - version := extractEntityName(s.Spec.Name, "version") - return version -} - -func (s SpecResource) GetApi() string { - api := extractEntityName(s.Spec.Name, "api") - return api -} - -func (s SpecResource) GetName() string { - return s.Spec.Name -} - -func (s SpecResource) GetUpdateTimestamp() time.Time { - return s.Spec.RevisionUpdateTime.AsTime() -} - -func (s SpecResource) ExtractResourceGroup(group_id string) string { - group_v := extractEntityName(s.Spec.Name, group_id) - return group_v -} - -type ApiResource struct { - Api *rpc.Api -} - -func (a ApiResource) GetArtifact() string { - return "" -} - -func (a ApiResource) GetSpec() string { - return "" -} - -func (a ApiResource) GetVersion() string { - return "" -} - -func (a ApiResource) GetApi() string { - return a.Api.Name -} - -func (a ApiResource) GetName() string { - return a.Api.Name -} - -func (a ApiResource) GetUpdateTimestamp() time.Time { - return a.Api.UpdateTime.AsTime() -} - -func (a ApiResource) ExtractResourceGroup(group_id string) string { - group_v := extractEntityName(a.Api.Name, group_id) - return group_v -} - -type ArtifactResource struct { - Artifact *rpc.Artifact -} - -func (ar ArtifactResource) GetArtifact() string { - return ar.Artifact.Name -} - -func (ar ArtifactResource) GetSpec() string { - spec := extractEntityName(ar.Artifact.Name, "spec") - return spec -} - -func (ar ArtifactResource) GetVersion() string { - version := extractEntityName(ar.Artifact.Name, "version") - return version -} - -func (ar ArtifactResource) GetApi() string { - api := extractEntityName(ar.Artifact.Name, "api") - return api -} - -func (ar ArtifactResource) GetName() string { - return ar.Artifact.Name -} - -func (ar ArtifactResource) GetUpdateTimestamp() time.Time { - return ar.Artifact.UpdateTime.AsTime() -} - -func (ar ArtifactResource) ExtractResourceGroup(group_id string) string { - group_v := extractEntityName(ar.Artifact.Name, group_id) - return group_v -} diff --git a/server/registry/names/spec.go b/server/registry/names/spec.go index b1b4f49d8..01cf9d44c 100644 --- a/server/registry/names/spec.go +++ b/server/registry/names/spec.go @@ -142,11 +142,12 @@ func ParseSpec(name string) (Spec, error) { // ParseSpecCollection parses the name of a spec collection. func ParseSpecCollection(name string) (Spec, error) { - if !specCollectionRegexp().MatchString(name) { - return Spec{}, fmt.Errorf("invalid spec collection name %q: must match %q", name, simpleSpecRegexp) + r := specCollectionRegexp() + if !r.MatchString(name) { + return Spec{}, fmt.Errorf("invalid spec collection name %q: must match %q", name, r) } - m := specCollectionRegexp().FindStringSubmatch(name) + m := r.FindStringSubmatch(name) spec := Spec{ ProjectID: m[1], ApiID: m[2],