From 5ddc85403cac324f6101374c3d9b43f835ab1bbc Mon Sep 17 00:00:00 2001
From: Alex Clemmer <clemmer.alexander@gmail.com>
Date: Mon, 30 Oct 2017 01:14:02 -0700
Subject: [PATCH] Add schemas for `app.yaml` and `registry.yaml`

Every ksonnet application has an `app.yaml`, which is similar in
principle to node.js's `package.json`. It contains important metadata,
such as description, names, and information about dependencies. This is
similarly true of `registry.yaml`, which contains information about a
package registry.

This commit will introduce schemas for both these files.
---
 metadata/app/schema.go      | 106 ++++++++++++++++++++++++++++++++++++
 metadata/app/schema_test.go | 101 ++++++++++++++++++++++++++++++++++
 metadata/registry/schema.go |  34 ++++++++++++
 3 files changed, 241 insertions(+)
 create mode 100644 metadata/app/schema.go
 create mode 100644 metadata/app/schema_test.go
 create mode 100644 metadata/registry/schema.go

diff --git a/metadata/app/schema.go b/metadata/app/schema.go
new file mode 100644
index 00000000..4d1d3ac5
--- /dev/null
+++ b/metadata/app/schema.go
@@ -0,0 +1,106 @@
+// Copyright 2017 The kubecfg authors
+//
+//
+//    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 app
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+const (
+	DefaultAPIVersion = "0.1"
+	Kind              = "ksonnet.io/app"
+	DefaultVersion    = "0.0.1"
+)
+
+var ErrRegistryNameInvalid = fmt.Errorf("Registry name is invalid")
+var ErrRegistryExists = fmt.Errorf("Registry with name already exists, but has different URL or protocol")
+
+type Spec struct {
+	APIVersion   string           `json:"apiVersion,omitempty"`
+	Kind         string           `json:"kind,omitempty"`
+	Name         string           `json:"name,omitempty"`
+	Version      string           `json:"version,omitempty"`
+	Description  string           `json:"description,omitempty"`
+	Authors      []string         `json:"authors,omitempty"`
+	Contributors ContributorSpecs `json:"contributors,omitempty"`
+	Repository   *RepositorySpec  `json:"repository,omitempty"`
+	Bugs         string           `json:"bugs,omitempty"`
+	Keywords     []string         `json:"keywords,omitempty"`
+	Registries   RegistryRefSpecs `json:"registries,omitempty"`
+	License      string           `json:"license,omitempty"`
+}
+
+func (s *Spec) Marshal() ([]byte, error) {
+	return json.MarshalIndent(s, "", "  ")
+}
+
+func (s *Spec) GetRegistryRef(name string) (*RegistryRefSpec, bool) {
+	registryRefSpec, ok := s.Registries[name]
+	if ok {
+		// Populate name, which we do not include in the deserialization
+		// process.
+		registryRefSpec.Name = name
+		return registryRefSpec, true
+	}
+
+	return nil, false
+}
+
+func (s *Spec) AddRegistryRef(name, protocol, uri string) (*RegistryRefSpec, error) {
+	if name == "" {
+		return nil, ErrRegistryNameInvalid
+	}
+
+	registryRefSpec, registryRefExists := s.Registries[name]
+	if registryRefExists {
+		storedURI, uriExists := registryRefSpec.Spec["uri"]
+		if registryRefSpec.Protocol != protocol || !uriExists || storedURI != uri {
+			return nil, ErrRegistryExists
+		}
+	} else {
+		registryRefSpec = &RegistryRefSpec{
+			Protocol: protocol,
+			Spec: map[string]interface{}{
+				"uri": uri,
+			},
+			Name: name,
+		}
+	}
+
+	s.Registries[name] = registryRefSpec
+	return registryRefSpec, nil
+}
+
+type RepositorySpec struct {
+	Type string `json:"type"`
+	URI  string `json:"uri"`
+}
+
+type RegistryRefSpec struct {
+	Protocol string                 `json:"protocol"`
+	Spec     map[string]interface{} `json:"spec"`
+	Name     string
+}
+
+type RegistryRefSpecs map[string]*RegistryRefSpec
+
+type ContributorSpec struct {
+	Name  string `json:"name"`
+	Email string `json:"email"`
+}
+
+type ContributorSpecs []*ContributorSpec
diff --git a/metadata/app/schema_test.go b/metadata/app/schema_test.go
new file mode 100644
index 00000000..16e2c20e
--- /dev/null
+++ b/metadata/app/schema_test.go
@@ -0,0 +1,101 @@
+package app
+
+import (
+	"testing"
+)
+
+func TestGetRegistryRefSuccess(t *testing.T) {
+	example1 := Spec{
+		Registries: RegistryRefSpecs{
+			"simple1": &RegistryRefSpec{
+				Spec: map[string]interface{}{
+					"uri": "example.com",
+				},
+				Protocol: "github",
+			},
+		},
+	}
+
+	r1, ok := example1.GetRegistryRef("simple1")
+	if r1 == nil || !ok {
+		t.Error("Expected registry to contain 'simple1'")
+	}
+
+	uri, ok := r1.Spec["uri"]
+	if !ok || uri.(string) != "example.com" || r1.Name != "simple1" || r1.Protocol != "github" {
+		t.Errorf("Registry did not add correct values:\n%s", r1)
+	}
+}
+
+func TestGetRegistryRefFailure(t *testing.T) {
+	example1 := Spec{
+		Registries: RegistryRefSpecs{
+			"simple1": &RegistryRefSpec{
+				Spec: map[string]interface{}{
+					"uri": "example.com",
+				},
+				Protocol: "github",
+			},
+		},
+	}
+
+	r1, ok := example1.GetRegistryRef("simple2")
+	if r1 != nil || ok {
+		t.Error("Expected registry to not contain 'simple2'")
+	}
+}
+
+func TestAddRegistryRefSuccess(t *testing.T) {
+	var example1 = Spec{
+		Registries: RegistryRefSpecs{},
+	}
+
+	r1, err := example1.AddRegistryRef("simple1", "github", "example.com")
+	if r1 == nil || err != nil {
+		t.Errorf("Expected registry add to succeed:\n%s", err)
+	}
+
+	uri1, ok1 := r1.Spec["uri"]
+	if !ok1 || uri1.(string) != "example.com" || r1.Name != "simple1" || r1.Protocol != "github" {
+		t.Errorf("Registry did not add correct values:\n%s", r1)
+	}
+
+	// Test that the `Name` field is added properly if it already exists.
+	r2, err := example1.AddRegistryRef("simple1", "github", "example.com")
+	if r2 == nil || err != nil {
+		t.Errorf("Expected registry add to succeed:\n%s", err)
+	}
+
+	uri2, ok2 := r2.Spec["uri"]
+	if !ok2 || uri2.(string) != "example.com" || r1.Name != "simple1" || r1.Protocol != "github" {
+		t.Errorf("Registry did not add correct values:\n%s", r1)
+	}
+}
+
+func TestAddRegistryRefFailure(t *testing.T) {
+	example1 := Spec{
+		Registries: RegistryRefSpecs{
+			"simple1": &RegistryRefSpec{
+				Spec: map[string]interface{}{
+					"uri": "example.com",
+				},
+				Protocol: "github",
+			},
+		},
+	}
+
+	r1, err := example1.AddRegistryRef("", "github", "example.com")
+	if r1 != nil || err != ErrRegistryNameInvalid {
+		t.Error("Expected registry to fail to add registry with invalid name")
+	}
+
+	r2, err := example1.AddRegistryRef("simple1", "fakeProtocol", "example.com")
+	if r2 != nil || err != ErrRegistryExists {
+		t.Error("Expected registry to fail to add registry with duplicate name and different protocol")
+	}
+
+	r3, err := example1.AddRegistryRef("simple1", "github", "fakeUrl")
+	if r3 != nil || err != ErrRegistryExists {
+		t.Error("Expected registry to fail to add registry with duplicate name and different uri")
+	}
+}
diff --git a/metadata/registry/schema.go b/metadata/registry/schema.go
new file mode 100644
index 00000000..e63d203a
--- /dev/null
+++ b/metadata/registry/schema.go
@@ -0,0 +1,34 @@
+// Copyright 2017 The kubecfg authors
+//
+//
+//    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 registry
+
+const (
+	DefaultApiVersion = "0.1"
+	DefaultKind       = "ksonnet.io/registry"
+)
+
+type Spec struct {
+	APIVersion string            `json:"apiVersion"`
+	Kind       string            `json:"kind"`
+	Prototypes PrototypeRefSpecs `json:"prototypes"`
+	Libraries  LibraryRefSpecs   `json:"libraries"`
+}
+
+type Specs []*Spec
+
+type LibraryRefSpecs map[string]string
+
+type PrototypeRefSpecs map[string]string
-- 
GitLab