1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00

Merge branch '254_dynamic_webhook_configurations' into feature_redesign

This commit is contained in:
shivkumar dudhani 2019-08-19 17:12:28 -07:00
commit 3abd422de4
1054 changed files with 73556 additions and 23600 deletions

View file

@ -1,7 +1,7 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG}"
title: "[BUG]"
labels: bug
assignees: ''

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ Gopkg.lock
kyverno
gh-pages/public
_output
coverage.txt

View file

@ -34,9 +34,10 @@ metadata:
spec:
rules:
- name: check-pod-resources
resource:
kinds:
- Pod
match:
resources:
kinds:
- Pod
validate:
message: "CPU and memory resource requests and limits are required"
pattern:
@ -67,9 +68,10 @@ metadata:
spec:
rules:
- name: set-image-pull-policy
resource:
kinds:
- Deployment
match:
resources:
kinds:
- Deployment
mutate:
overlay:
spec:
@ -94,12 +96,13 @@ metadata:
spec:
rules:
- name: "zk-kafka-address"
resource:
kinds:
- Namespace
selector:
matchExpressions:
- {key: kafka, operator: Exists}
match:
resources:
kinds:
- Namespace
selector:
matchExpressions:
- {key: kafka, operator: Exists}
generate:
kind: ConfigMap
name: zk-kafka-address

View file

@ -231,7 +231,7 @@ spec:
containers:
- name: kyverno
image: nirmata/kyverno:latest
args: ["--filterK8Resources","[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*]Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]"]
args: ["--filterK8Resources","[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]"]
ports:
- containerPort: 443
securityContext:

View file

@ -0,0 +1,246 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: policies.kyverno.io
spec:
group: kyverno.io
versions:
- name: v1alpha1
served: true
storage: true
scope: Cluster
names:
kind: Policy
plural: policies
singular: policy
subresources:
status: {}
validation:
openAPIV3Schema:
properties:
spec:
required:
- rules
properties:
# default values to be handled by user
validationFailureAction:
type: string
enum:
- enforce # blocks the resorce api-reques if a rule fails. Default behavior
- audit # allows resource creationg and reports the failed validation rules as violations
rules:
type: array
items:
type: object
required:
- name
- match
properties:
name:
type: string
match:
type: object
required:
- resources
properties:
resources:
type: object
required:
- kinds
properties:
kinds:
type: array
items:
type: string
name:
type: string
namespace:
type: string
selector:
properties:
matchLabels:
type: object
additionalProperties:
type: string
matchExpressions:
type: array
items:
type: object
required:
- key
- operator
properties:
key:
type: string
operator:
type: string
values:
type: array
items:
type: string
exclude:
type: object
required:
- resources
properties:
resources:
type: object
properties:
kinds:
type: array
items:
type: string
name:
type: string
namespace:
type: string
selector:
properties:
matchLabels:
type: object
additionalProperties:
type: string
matchExpressions:
type: array
items:
type: object
required:
- key
- operator
properties:
key:
type: string
operator:
type: string
values:
type: array
items:
type: string
mutate:
type: object
properties:
overlay:
AnyValue: {}
patches:
type: array
items:
type: object
required:
- path
- op
properties:
path:
type: string
op:
type: string
enum:
- add
- replace
- remove
value:
AnyValue: {}
validate:
type: object
required:
- pattern
properties:
message:
type: string
pattern:
AnyValue: {}
generate:
type: object
required:
- kind
- name
properties:
kind:
type: string
name:
type: string
clone:
type: object
required:
- namespace
- name
properties:
namespace:
type: string
name:
type: string
data:
AnyValue: {}
---
kind: Namespace
apiVersion: v1
metadata:
name: "kyverno"
---
apiVersion: v1
kind: Service
metadata:
namespace: kyverno
name: kyverno-svc
labels:
app: kyverno
spec:
ports:
- port: 443
targetPort: 443
selector:
app: kyverno
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kyverno-service-account
namespace: kyverno
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: kyverno-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kyverno-service-account
namespace: kyverno
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
namespace: kyverno
name: kyverno
labels:
app: kyverno
spec:
replicas: 1
template:
metadata:
labels:
app: kyverno
spec:
serviceAccountName: kyverno-service-account
containers:
- name: kyverno
image: nirmata/kyverno:latest
args:
- "--webhooktimeout=4"
# open one of the profiling flag here
- "--cpu=true"
- "--filterK8Resources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*]Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*]"
ports:
- containerPort: 443
securityContext:
privileged: true
volumeMounts:
- mountPath: /opt/nirmata
name: profiling-volume
volumes:
- name: profiling-volume
hostPath:
path: /opt/nirmata
type: Directory

View file

@ -1,10 +1,10 @@
apiVersion: kyverno.io/v1alpha1
kind: Policy
metadata:
name: "default-networkPolicy"
name: defaultgeneratenetworkpolicy
spec:
rules:
- name: "default-networkPolicy"
- name: "default-networkpolicy"
match:
resources:
kinds:
@ -12,7 +12,7 @@ spec:
name: "devtest"
generate:
kind: NetworkPolicy
name: default-networkPolicy
name: defaultnetworkpolicy
data:
spec:
# select all pods in the namespace

49
init.go
View file

@ -2,6 +2,10 @@ package main
import (
"fmt"
"math/rand"
"time"
"github.com/pkg/profile"
"github.com/golang/glog"
client "github.com/nirmata/kyverno/pkg/dclient"
@ -51,3 +55,48 @@ func initTLSPemPair(configuration *rest.Config, client *client.Client) (*tls.Tls
glog.Infoln("Using existing TLS key/certificate pair")
return tlsPair, nil
}
var prof interface {
Stop()
}
func enableProfiling(cpu, memory bool) interface {
Stop()
} {
file := "/opt/nirmata/kyverno/" + randomString(6)
if cpu {
glog.Infof("Enable cpu profiling ...")
prof = profile.Start(profile.CPUProfile, profile.ProfilePath(file))
} else if memory {
glog.Infof("Enable memory profiling ...")
prof = profile.Start(profile.MemProfile, profile.ProfilePath(file))
}
return prof
}
func disableProfiling(p interface{ Stop() }) {
if p != nil {
p.Stop()
}
}
// generate random string
const charset = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var seededRand *rand.Rand = rand.New(
rand.NewSource(time.Now().UnixNano()))
func stringWithCharset(length int, charset string) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
func randomString(length int) string {
return stringWithCharset(length, charset)
}

31
main.go
View file

@ -23,11 +23,15 @@ var (
kubeconfig string
serverIP string
filterK8Resources string
cpu bool
memory bool
webhookTimeout int
)
func main() {
defer glog.Flush()
printVersionInfo()
prof = enableProfiling(cpu, memory)
// CLIENT CONFIG
clientConfig, err := createClientConfig(kubeconfig)
@ -94,16 +98,23 @@ func main() {
if err != nil {
glog.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err)
}
server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations(), egen, filterK8Resources)
if err != nil {
glog.Fatalf("Unable to create webhook server: %v\n", err)
}
webhookRegistrationClient, err := webhooks.NewWebhookRegistrationClient(clientConfig, client, serverIP)
// WEBHOOK REGISTRATION
// -- validationwebhookconfiguration (Policy)
// -- mutatingwebhookconfiguration (All resources)
webhookRegistrationClient, err := webhooks.NewWebhookRegistrationClient(clientConfig, client, serverIP, int32(webhookTimeout))
if err != nil {
glog.Fatalf("Unable to register admission webhooks on cluster: %v\n", err)
}
if err = webhookRegistrationClient.Register(); err != nil {
glog.Fatalf("Failed registering Admission Webhooks: %v\n", err)
}
server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations(), egen, webhookRegistrationClient, filterK8Resources)
if err != nil {
glog.Fatalf("Unable to create webhook server: %v\n", err)
}
stopCh := signals.SetupSignalHandler()
if err = webhookRegistrationClient.Register(); err != nil {
@ -119,11 +130,21 @@ func main() {
//TODO add WG for the go routines?
server.RunAsync()
<-stopCh
disableProfiling(prof)
server.Stop()
}
func init() {
// profiling feature gate
// cpu and memory profiling cannot be enabled at same time
// if both cpu and memory are enabled
// by default is to profile cpu
flag.BoolVar(&cpu, "cpu", false, "cpu profilling feature gate, default to false || cpu and memory profiling cannot be enabled at the same time")
flag.BoolVar(&memory, "memory", false, "memory profilling feature gate, default to false || cpu and memory profiling cannot be enabled at the same time")
flag.IntVar(&webhookTimeout, "webhooktimeout", 2, "timeout for webhook configurations")
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
flag.StringVar(&serverIP, "serverIP", "", "IP address where Kyverno controller runs. Only required if out-of-cluster.")
flag.StringVar(&filterK8Resources, "filterK8Resources", "", "k8 resource in format [kind,namespace,name] where policy is not evaluated by the admission webhook. example --filterKind \"[Deployment, kyverno, kyverno]\" --filterKind \"[Deployment, kyverno, kyverno],[Events, *, *]\"")

View file

@ -0,0 +1,281 @@
package controller
import (
"fmt"
"reflect"
"time"
"github.com/nirmata/kyverno/pkg/annotations"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/golang/glog"
v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
lister "github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/sharedinformer"
violation "github.com/nirmata/kyverno/pkg/violation"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
//PolicyController to manage Policy CRD
type PolicyController struct {
client *client.Client
policyLister lister.PolicyLister
policySynced cache.InformerSynced
violationBuilder violation.Generator
eventController event.Generator
queue workqueue.RateLimitingInterface
filterK8Resources []utils.K8Resource
}
// NewPolicyController from cmd args
func NewPolicyController(client *client.Client,
policyInformer sharedinformer.PolicyInformer,
violationBuilder violation.Generator,
eventController event.Generator,
filterK8Resources string) *PolicyController {
controller := &PolicyController{
client: client,
policyLister: policyInformer.GetLister(),
policySynced: policyInformer.GetInfomer().HasSynced,
violationBuilder: violationBuilder,
eventController: eventController,
filterK8Resources: utils.ParseKinds(filterK8Resources),
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), policyWorkQueueName),
}
policyInformer.GetInfomer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.createPolicyHandler,
UpdateFunc: controller.updatePolicyHandler,
DeleteFunc: controller.deletePolicyHandler,
})
return controller
}
func (pc *PolicyController) createPolicyHandler(resource interface{}) {
pc.enqueuePolicy(resource)
}
func (pc *PolicyController) updatePolicyHandler(oldResource, newResource interface{}) {
newPolicy := newResource.(*v1alpha1.Policy)
oldPolicy := oldResource.(*v1alpha1.Policy)
newPolicy.Status = v1alpha1.Status{}
oldPolicy.Status = v1alpha1.Status{}
newPolicy.ResourceVersion = ""
oldPolicy.ResourceVersion = ""
if reflect.DeepEqual(newPolicy, oldPolicy) {
return
}
pc.enqueuePolicy(newResource)
}
func (pc *PolicyController) deletePolicyHandler(resource interface{}) {
var object metav1.Object
var ok bool
if object, ok = resource.(metav1.Object); !ok {
glog.Error("error decoding object, invalid type")
return
}
cleanAnnotations(pc.client, resource, pc.filterK8Resources)
glog.Infof("policy deleted: %s", object.GetName())
}
func (pc *PolicyController) enqueuePolicy(obj interface{}) {
var key string
var err error
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
glog.Error(err)
return
}
pc.queue.Add(key)
}
// Run is main controller thread
func (pc *PolicyController) Run(stopCh <-chan struct{}) error {
defer utilruntime.HandleCrash()
if ok := cache.WaitForCacheSync(stopCh, pc.policySynced); !ok {
return fmt.Errorf("failed to wait for caches to sync")
}
for i := 0; i < policyControllerWorkerCount; i++ {
go wait.Until(pc.runWorker, time.Second, stopCh)
}
glog.Info("started policy controller workers")
return nil
}
//Stop to perform actions when controller is stopped
func (pc *PolicyController) Stop() {
pc.queue.ShutDown()
glog.Info("shutting down policy controller workers")
}
func (pc *PolicyController) runWorker() {
for pc.processNextWorkItem() {
}
}
func (pc *PolicyController) processNextWorkItem() bool {
obj, shutdown := pc.queue.Get()
if shutdown {
return false
}
err := func(obj interface{}) error {
defer pc.queue.Done(obj)
err := pc.syncHandler(obj)
pc.handleErr(err, obj)
return nil
}(obj)
if err != nil {
glog.Error(err)
return true
}
return true
}
func (pc *PolicyController) handleErr(err error, key interface{}) {
if err == nil {
pc.queue.Forget(key)
return
}
// This controller retries if something goes wrong. After that, it stops trying.
if pc.queue.NumRequeues(key) < policyWorkQueueRetryLimit {
glog.Warningf("Error syncing events %v: %v", key, err)
// Re-enqueue the key rate limited. Based on the rate limiter on the
// queue and the re-enqueue history, the key will be processed later again.
pc.queue.AddRateLimited(key)
return
}
pc.queue.Forget(key)
glog.Error(err)
glog.Warningf("Dropping the key out of the queue: %v", err)
}
func (pc *PolicyController) syncHandler(obj interface{}) error {
var key string
var ok bool
if key, ok = obj.(string); !ok {
return fmt.Errorf("expected string in workqueue but got %#v", obj)
}
_, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
glog.Errorf("invalid policy key: %s", key)
return nil
}
// Get Policy
policy, err := pc.policyLister.Get(name)
if err != nil {
if errors.IsNotFound(err) {
glog.Errorf("policy '%s' in work queue no longer exists", key)
return nil
}
return err
}
glog.Infof("process policy %s on existing resources", policy.GetName())
// Process policy on existing resources
policyInfos := engine.ProcessExisting(pc.client, policy, pc.filterK8Resources)
events, violations := pc.createEventsAndViolations(policyInfos)
// Events, Violations
pc.eventController.Add(events...)
err = pc.violationBuilder.Add(violations...)
if err != nil {
glog.Error(err)
}
// Annotations
pc.createAnnotations(policyInfos)
return nil
}
func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) {
for _, pi := range policyInfos {
//get resource
obj, err := pc.client.GetResource(pi.RKind, pi.RNamespace, pi.RName)
if err != nil {
glog.Error(err)
continue
}
// add annotation for policy application
ann := obj.GetAnnotations()
// if annotations are nil then create a map and patch
// else
// add the exact patch
patch, err := annotations.PatchAnnotations(ann, pi, info.All)
if patch == nil {
/// nothing to patch
return
}
_, err = pc.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, patch)
if err != nil {
glog.Error(err)
continue
}
}
}
func (pc *PolicyController) createEventsAndViolations(policyInfos []*info.PolicyInfo) ([]*event.Info, []*violation.Info) {
events := []*event.Info{}
violations := []*violation.Info{}
// Create events from the policyInfo
for _, policyInfo := range policyInfos {
frules := []v1alpha1.FailedRule{}
sruleNames := []string{}
for _, rule := range policyInfo.Rules {
if !rule.IsSuccessful() {
e := &event.Info{}
frule := v1alpha1.FailedRule{Name: rule.Name}
switch rule.RuleType {
case info.Mutation, info.Validation, info.Generation:
// Events
e = event.NewEvent(policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation, event.FProcessRule, rule.Name, policyInfo.Name)
switch rule.RuleType {
case info.Mutation:
frule.Type = info.Mutation.String()
case info.Validation:
frule.Type = info.Validation.String()
case info.Generation:
frule.Type = info.Generation.String()
}
frule.Error = rule.GetErrorString()
default:
glog.Info("Unsupported Rule type")
}
frule.Error = rule.GetErrorString()
frules = append(frules, frule)
events = append(events, e)
} else {
sruleNames = append(sruleNames, rule.Name)
}
}
if !policyInfo.IsSuccessful() {
e := event.NewEvent("Policy", "", policyInfo.Name, event.PolicyViolation, event.FResourcePolcy, policyInfo.RNamespace+"/"+policyInfo.RName, concatFailedRules(frules))
events = append(events, e)
// Violation
v := violation.BuldNewViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation.String(), policyInfo.GetFailedRules())
violations = append(violations, v)
} else {
// clean up violations
pc.violationBuilder.RemoveInactiveViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, info.Mutation)
pc.violationBuilder.RemoveInactiveViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, info.Validation)
}
}
return events, violations
}

View file

@ -0,0 +1,147 @@
package controller
import (
"testing"
"github.com/golang/glog"
types "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
event "github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/sharedinformer"
violation "github.com/nirmata/kyverno/pkg/violation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/sample-controller/pkg/signals"
)
func TestCreatePolicy(t *testing.T) {
f := newFixture(t)
// new policy is added to policy lister and explictly passed to sync-handler
// to process the existing
policy := newPolicy("test-policy")
f.policyLister = append(f.policyLister, policy)
f.objects = append(f.objects, policy)
// run controller
f.runControler("test-policy")
}
func (f *fixture) runControler(policyName string) {
policyInformerFactory, err := sharedinformer.NewFakeSharedInformerFactory()
if err != nil {
f.t.Fatal(err)
}
eventController := event.NewEventController(f.Client, policyInformerFactory)
violationBuilder := violation.NewPolicyViolationBuilder(f.Client, policyInformerFactory, eventController)
// new controller
policyController := NewPolicyController(
f.Client,
policyInformerFactory,
violationBuilder,
eventController,
"")
stopCh := signals.SetupSignalHandler()
// start informer & controller
policyInformerFactory.Run(stopCh)
if err = policyController.Run(stopCh); err != nil {
glog.Fatalf("Error running PolicyController: %v\n", err)
}
// add policy to the informer
for _, p := range f.policyLister {
policyInformerFactory.GetInfomer().GetIndexer().Add(p)
}
// sync handler
// reads the policy from the policy lister and processes them
err = policyController.syncHandler(policyName)
if err != nil {
f.t.Fatal(err)
}
policyController.Stop()
}
type fixture struct {
t *testing.T
Client *client.Client
policyLister []*types.Policy
objects []runtime.Object
}
func newFixture(t *testing.T) *fixture {
// init groupversion
regResource := []schema.GroupVersionResource{
schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"},
schema.GroupVersionResource{Group: "group2", Version: "version", Resource: "thekinds"},
schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"},
schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
}
objects := []runtime.Object{newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
newUnstructured("group/version", "TheKind", "ns-foo", "name-baz"),
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-baz"),
newUnstructured("apps/v1", "Deployment", "kyverno", "kyverno"),
}
scheme := runtime.NewScheme()
// Create mock client
fclient, err := client.NewMockClient(scheme, objects...)
if err != nil {
t.Fatal(err)
}
// set discovery Client
fclient.SetDiscovery(client.NewFakeDiscoveryClient(regResource))
f := &fixture{
t: t,
Client: fclient,
}
return f
}
// create mock client with initial resouces
// set registered resources for gvr
func (f *fixture) setupFixture() {
scheme := runtime.NewScheme()
fclient, err := client.NewMockClient(scheme, f.objects...)
if err != nil {
f.t.Fatal(err)
}
regresource := []schema.GroupVersionResource{
schema.GroupVersionResource{Group: "kyverno.io",
Version: "v1alpha1",
Resource: "policys"}}
fclient.SetDiscovery(client.NewFakeDiscoveryClient(regresource))
}
func newPolicy(name string) *types.Policy {
return &types.Policy{
TypeMeta: metav1.TypeMeta{APIVersion: types.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
}
func newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": kind,
"metadata": map[string]interface{}{
"namespace": namespace,
"name": name,
},
},
}
}

View file

@ -3,12 +3,14 @@ package engine
import (
"encoding/json"
"errors"
"fmt"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
@ -22,7 +24,7 @@ func Generate(client *client.Client, policy kyverno.Policy, ns unstructured.Unst
}
glog.V(4).Infof("applying policy %s generate rule %s on resource %s/%s/%s", policy.Name, rule.Name, ns.GetKind(), ns.GetNamespace(), ns.GetName())
ri := info.NewRuleInfo(rule.Name, info.Generation)
err := applyRuleGenerator(client, ns, rule.Generation)
err := applyRuleGenerator(client, ns, rule.Generation, policy.GetCreationTimestamp())
if err != nil {
ri.Fail()
ri.Addf("Failed to apply rule generator, err %v.", rule.Name, err)
@ -36,11 +38,15 @@ func Generate(client *client.Client, policy kyverno.Policy, ns unstructured.Unst
return ris
}
func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen kyverno.Generation) error {
func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen kyverno.Generation, policyCreationTime metav1.Time) error {
var err error
resource := &unstructured.Unstructured{}
var rdata map[string]interface{}
// To manage existing resource , we compare the creation time for the default resource to be generate and policy creation time
processExisting := func() bool {
nsCreationTime := ns.GetCreationTimestamp()
return nsCreationTime.Before(&policyCreationTime)
}()
if gen.Data != nil {
glog.V(4).Info("generate rule: creates new resource")
// 1> Check if resource exists
@ -85,12 +91,15 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen
glog.V(4).Infof("generate rule: clone reference resource %s/%s/%s present", gen.Kind, gen.Kind, gen.Clone.Namespace, gen.Clone.Name)
rdata = resource.UnstructuredContent()
}
if processExisting {
// for existing resources we generate an error which indirectly generates a policy violation
return fmt.Errorf("resource %s not found in existing namespace %s", gen.Name, ns.GetName())
}
resource.SetUnstructuredContent(rdata)
resource.SetName(gen.Name)
resource.SetNamespace(ns.GetName())
// Reset resource version
resource.SetResourceVersion("")
_, err = client.CreateResource(gen.Kind, ns.GetName(), resource, false)
if err != nil {
glog.V(4).Infof("generate rule: unable to create resource %s/%s/%s: %v", gen.Kind, resource.GetNamespace(), resource.GetName(), err)

View file

@ -10,11 +10,21 @@ import (
)
// Mutate performs mutation. Overlay first and then mutation patches
//TODO: check if gvk needs to be passed or can be set in resource
func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) ([][]byte, []info.RuleInfo) {
//TODO: convert rawResource to unstructured to avoid unmarhalling all the time for get some resource information
var patches [][]byte
var ruleInfos []info.RuleInfo
func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) EngineResponse {
var allPatches, rulePatches [][]byte
var err error
var errs []error
ris := []info.RuleInfo{}
patchedDocument, err := resource.MarshalJSON()
if err != nil {
glog.Errorf("unable to marshal resource : %v\n", err)
}
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
return EngineResponse{PatchedResource: resource}
}
for _, rule := range policy.Spec.Rules {
if reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) {
@ -34,9 +44,9 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) ([][]byte
// Process Overlay
if rule.Mutation.Overlay != nil {
oPatches, err := processOverlay(resource, rule)
rulePatches, err = processOverlay(rule, patchedDocument)
if err == nil {
if len(oPatches) == 0 {
if len(rulePatches) == 0 {
// if array elements dont match then we skip(nil patch, no error)
// or if acnohor is defined and doenst match
// policy is not applicable
@ -44,14 +54,13 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) ([][]byte
continue
}
glog.V(4).Infof("overlay applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
ruleInfo.Add("Overlay succesfully applied")
ruleInfo.Addf("Rule %s: Overlay succesfully applied.", rule.Name)
// update rule information
// strip slashes from string
patch := JoinPatches(oPatches)
ruleInfo.Changes = string(patch)
patches = append(patches, oPatches...)
ruleInfo.Patches = rulePatches
allPatches = append(allPatches, rulePatches...)
glog.V(4).Infof("overlay applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
} else {
glog.V(4).Infof("failed to apply overlay: %v", err)
ruleInfo.Fail()
@ -61,7 +70,7 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) ([][]byte
// Process Patches
if len(rule.Mutation.Patches) != 0 {
jsonPatches, errs := processPatches(resource, rule)
rulePatches, errs = processPatches(rule, patchedDocument)
if len(errs) > 0 {
ruleInfo.Fail()
for _, err := range errs {
@ -71,10 +80,29 @@ func Mutate(policy kyverno.Policy, resource unstructured.Unstructured) ([][]byte
} else {
glog.V(4).Infof("patches applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
ruleInfo.Addf("Patches succesfully applied.")
patches = append(patches, jsonPatches...)
ruleInfo.Patches = rulePatches
allPatches = append(allPatches, rulePatches...)
}
}
ruleInfos = append(ruleInfos, ruleInfo)
patchedDocument, err = ApplyPatches(patchedDocument, rulePatches)
if err != nil {
glog.Errorf("Failed to apply patches on ruleName=%s, err%v\n:", rule.Name, err)
}
ris = append(ris, ruleInfo)
}
patchedResource, err := ConvertToUnstructured(patchedDocument)
if err != nil {
glog.Errorf("Failed to convert patched resource to unstructuredtype, err%v\n:", err)
return EngineResponse{PatchedResource: resource}
}
return EngineResponse{
Patches: allPatches,
PatchedResource: *patchedResource,
RuleInfos: ris,
}
return patches, ruleInfos
}

View file

@ -12,29 +12,27 @@ import (
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// ProcessOverlay handles validating admission request
// rawResource handles validating admission request
// Checks the target resources for rules defined in the policy
func processOverlay(resourceUnstr unstructured.Unstructured, rule kyverno.Rule) ([][]byte, error) {
//TODO check if there is better solution
resourceRaw, err := resourceUnstr.MarshalJSON()
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
return nil, err
}
// TODO: pass in the unstructured object in stead of raw byte?
func processOverlay(rule kyverno.Rule, rawResource []byte) ([][]byte, error) {
var resource interface{}
if err := json.Unmarshal(resourceRaw, &resource); err != nil {
if err := json.Unmarshal(rawResource, &resource); err != nil {
glog.V(4).Infof("unable to unmarshal resource : %v", err)
return nil, err
}
resourceInfo := ParseResourceInfoFromObject(rawResource)
patches, err := processOverlayPatches(resource, rule.Mutation.Overlay)
if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
glog.V(4).Infof("overlay pattern %s does not match resource %s/%s", rule.Mutation.Overlay, resourceUnstr.GetNamespace(), resourceUnstr.GetName())
return nil, nil
// glog.V(4).Infof("overlay pattern %s does not match resource %s/%s", rule.Mutation.Overlay, resourceUnstr.GetNamespace(), resourceUnstr.GetName())
glog.Infof("Resource does not meet conditions in overlay pattern, resource=%s, rule=%s\n", resourceInfo, rule.Name)
// patches, err := processOverlayPatches(resource, rule.Mutation.Overlay)
// if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
// glog.V(4).Infof("overlay pattern %s does not match resource %s/%s", rule.Mutation.Overlay, resourceUnstr.GetNamespace(), resourceUnstr.GetName())
// return nil, nil
}
return patches, err

View file

@ -3,11 +3,9 @@ package engine
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
@ -15,15 +13,8 @@ import (
// ProcessPatches Returns array from separate patches that can be applied to the document
// Returns error ONLY in case when creation of resource should be denied.
func processPatches(resourceUnstr unstructured.Unstructured, rule kyverno.Rule) (allPatches [][]byte, errs []error) {
//TODO check if there is better solution
resource, err := resourceUnstr.MarshalJSON()
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
errs = append(errs, fmt.Errorf("unable to marshal resource : %v", err))
return nil, errs
}
// TODO: pass in the unstructured object in stead of raw byte?
func processPatches(rule kyverno.Rule, resource []byte) (allPatches [][]byte, errs []error) {
if len(resource) == 0 {
errs = append(errs, errors.New("Source document for patching is empty"))
return nil, errs

View file

@ -5,7 +5,7 @@ import (
"gotest.tools/assert"
types "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
types "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
)
const endpointsDocument string = `{
@ -58,7 +58,7 @@ func makeRuleWithPatches(patches []types.Patch) types.Rule {
Patches: patches,
}
return types.Rule{
Mutation: &mutation,
Mutation: mutation,
}
}

View file

@ -10,6 +10,7 @@ import (
"github.com/minio/minio/pkg/wildcard"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -17,6 +18,142 @@ import (
"k8s.io/apimachinery/pkg/labels"
)
type EngineResponse struct {
Patches [][]byte
PatchedResource unstructured.Unstructured
RuleInfos []info.RuleInfo
}
// //ListResourcesThatApplyToPolicy returns list of resources that are filtered by policy rules
// func ListResourcesThatApplyToPolicy(client *client.Client, policy *kyverno.Policy, filterK8Resources []utils.K8Resource) map[string]resourceInfo {
// // key uid
// resourceMap := map[string]resourceInfo{}
// for _, rule := range policy.Spec.Rules {
// // Match
// for _, k := range rule.MatchResources.Kinds {
// namespaces := []string{}
// if k == "Namespace" {
// namespaces = []string{""}
// } else {
// if rule.MatchResources.Namespace != "" {
// // if namespace is specified then we add the namespace
// namespaces = append(namespaces, rule.MatchResources.Namespace)
// } else {
// // no namespace specified, refer to all namespaces
// namespaces = getAllNamespaces(client)
// }
// // Check if exclude namespace is not clashing
// namespaces = excludeNamespaces(namespaces, rule.ExcludeResources.Namespace)
// }
// // If kind is namespace then namespace is "", override
// // Get resources in the namespace
// for _, ns := range namespaces {
// rMap := getResourcesPerNamespace(k, client, ns, rule, filterK8Resources)
// mergeresources(resourceMap, rMap)
// }
// }
// }
// return resourceMap
// }
// func getResourcesPerNamespace(kind string, client *client.Client, namespace string, rule kyverno.Rule, filterK8Resources []utils.K8Resource) map[string]resourceInfo {
// resourceMap := map[string]resourceInfo{}
// // List resources
// list, err := client.ListResource(kind, namespace, rule.MatchResources.Selector)
// if err != nil {
// glog.Errorf("unable to list resource for %s with label selector %s", kind, rule.MatchResources.Selector.String())
// return nil
// }
// var selector labels.Selector
// // exclude label selector
// if rule.ExcludeResources.Selector != nil {
// selector, err = v1helper.LabelSelectorAsSelector(rule.ExcludeResources.Selector)
// if err != nil {
// glog.Error(err)
// }
// }
// for _, res := range list.Items {
// // exclude label selectors
// if selector != nil {
// set := labels.Set(res.GetLabels())
// if selector.Matches(set) {
// // if matches
// continue
// }
// }
// var name string
// // match
// // name
// // wild card matching
// name = rule.MatchResources.Name
// if name != "" {
// // if does not match then we skip
// if !wildcard.Match(name, res.GetName()) {
// continue
// }
// }
// // exclude
// // name
// // wild card matching
// name = rule.ExcludeResources.Name
// if name != "nil" {
// // if matches then we skip
// if wildcard.Match(name, res.GetName()) {
// continue
// }
// }
// gvk := res.GroupVersionKind()
// ri := resourceInfo{Resource: res, Gvk: &metav1.GroupVersionKind{Group: gvk.Group,
// Version: gvk.Version,
// Kind: gvk.Kind}}
// // Skip the filtered resources
// if utils.SkipFilteredResources(gvk.Kind, res.GetNamespace(), res.GetName(), filterK8Resources) {
// continue
// }
// resourceMap[string(res.GetUID())] = ri
// }
// return resourceMap
// }
// // merge b into a map
// func mergeresources(a, b map[string]resourceInfo) {
// for k, v := range b {
// a[k] = v
// }
// }
// func getAllNamespaces(client *client.Client) []string {
// namespaces := []string{}
// // get all namespaces
// nsList, err := client.ListResource("Namespace", "", nil)
// if err != nil {
// glog.Error(err)
// return namespaces
// }
// for _, ns := range nsList.Items {
// namespaces = append(namespaces, ns.GetName())
// }
// return namespaces
// }
// func excludeNamespaces(namespaces []string, excludeNs string) []string {
// if excludeNs == "" {
// return namespaces
// }
// filteredNamespaces := []string{}
// for _, n := range namespaces {
// if n == excludeNs {
// continue
// }
// filteredNamespaces = append(filteredNamespaces, n)
// }
// return filteredNamespaces
// }
//MatchesResourceDescription checks if the resource matches resource desription of the rule or not
func MatchesResourceDescription(resource unstructured.Unstructured, rule kyverno.Rule) bool {
matches := rule.MatchResources.ResourceDescription
@ -355,3 +492,18 @@ func convertToFloat(value interface{}) (float64, error) {
return 0, fmt.Errorf("Could not convert %T to float64", value)
}
}
type resourceInfo struct {
Resource unstructured.Unstructured
Gvk *metav1.GroupVersionKind
}
func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
resource := &unstructured.Unstructured{}
err := resource.UnmarshalJSON(data)
if err != nil {
glog.V(4).Infof("failed to unmarshall resource: %v", err)
return nil, err
}
return resource, nil
}

View file

@ -3,9 +3,340 @@ package engine
import (
"testing"
types "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"gotest.tools/assert"
)
func TestResourceMeetsDescription_Kind(t *testing.T) {
resourceName := "test-config-map"
resourceDescription := types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: nil,
},
}
excludeResourcesResourceDesc := types.ResourceDescription{}
groupVersionKind := metav1.GroupVersionKind{Kind: "ConfigMap"}
rawResource := []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
resourceDescription.Kinds[0] = "Deployment"
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
resourceDescription.Kinds[0] = "ConfigMap"
groupVersionKind.Kind = "Deployment"
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
}
func TestResourceMeetsDescription_Name(t *testing.T) {
resourceName := "test-config-map"
resourceDescription := types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: nil,
},
}
excludeResourcesResourceDesc := types.ResourceDescription{}
groupVersionKind := metav1.GroupVersionKind{Kind: "ConfigMap"}
rawResource := []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
resourceDescription.Name = "test-config-map-new"
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
rawResource = []byte(`{
"metadata":{
"name":"test-config-map-new",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
rawResource = []byte(`{
"metadata":{
"name":"",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
}
func TestResourceMeetsDescription_MatchExpressions(t *testing.T) {
resourceName := "test-config-map"
resourceDescription := types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: nil,
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "label2",
Operator: "NotIn",
Values: []string{
"sometest1",
},
},
metav1.LabelSelectorRequirement{
Key: "label1",
Operator: "In",
Values: []string{
"test1",
"test8",
"test201",
},
},
metav1.LabelSelectorRequirement{
Key: "label3",
Operator: "DoesNotExist",
Values: nil,
},
metav1.LabelSelectorRequirement{
Key: "label2",
Operator: "In",
Values: []string{
"test2",
},
},
},
},
}
excludeResourcesResourceDesc := types.ResourceDescription{}
groupVersionKind := metav1.GroupVersionKind{Kind: "ConfigMap"}
rawResource := []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
rawResource = []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1234567890",
"label2":"test2"
}
}
}`)
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
}
func TestResourceMeetsDescription_MatchLabels(t *testing.T) {
resourceName := "test-config-map"
resourceDescription := types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label1": "test1",
"label2": "test2",
},
MatchExpressions: nil,
},
}
groupVersionKind := metav1.GroupVersionKind{Kind: "ConfigMap"}
excludeResourcesResourceDesc := types.ResourceDescription{}
rawResource := []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
rawResource = []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label3":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
resourceDescription = types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label3": "test1",
"label2": "test2",
},
MatchExpressions: nil,
},
}
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
}
func TestResourceMeetsDescription_MatchLabelsAndMatchExpressions(t *testing.T) {
resourceName := "test-config-map"
resourceDescription := types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label1": "test1",
},
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "label2",
Operator: "In",
Values: []string{
"test2",
},
},
},
},
}
groupVersionKind := metav1.GroupVersionKind{Kind: "ConfigMap"}
excludeResourcesResourceDesc := types.ResourceDescription{}
rawResource := []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
resourceDescription = types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label1": "test1",
},
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "label2",
Operator: "NotIn",
Values: []string{
"sometest1",
},
},
},
},
}
rawResource = []byte(`{
"metadata":{
"name":"test-config-map",
"namespace":"default",
"creationTimestamp":null,
"labels":{
"label1":"test1",
"label2":"test2"
}
}
}`)
assert.Assert(t, ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
resourceDescription = types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label1": "test1",
},
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "label2",
Operator: "In",
Values: []string{
"sometest1",
},
},
},
},
}
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
resourceDescription = types.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: resourceName,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"label1": "test1",
"label3": "test3",
},
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "label2",
Operator: "In",
Values: []string{
"test2",
},
},
},
},
}
assert.Assert(t, false == ResourceMeetsDescription(rawResource, resourceDescription, excludeResourcesResourceDesc, groupVersionKind))
}
// func TestResourceMeetsDescription_Kind(t *testing.T) {
// resourceName := "test-config-map"
// resourceDescription := types.ResourceDescription{

View file

@ -17,19 +17,17 @@ import (
// Validate handles validating admission request
// Checks the target resources for rules defined in the policy
func Validate(policy kyverno.Policy, resource unstructured.Unstructured) ([]info.RuleInfo, error) {
//TODO: convert rawResource to unstructured to avoid unmarhalling all the time for get some resource information
//TODO: pass unstructured instead of rawResource ?
func Validate(policy kyverno.Policy, resource unstructured.Unstructured) EngineResponse {
resourceRaw, err := resource.MarshalJSON()
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
return nil, err
glog.V(4).Infof("Skip processing validating rule, unable to marshal resource : %v\n", err)
return EngineResponse{PatchedResource: resource}
}
var resourceInt interface{}
if err := json.Unmarshal(resourceRaw, &resourceInt); err != nil {
glog.V(4).Infof("unable to unmarshal resource : %v", err)
return nil, err
glog.V(4).Infof("unable to unmarshal resource : %v\n", err)
return EngineResponse{PatchedResource: resource}
}
var ruleInfos []info.RuleInfo
@ -40,7 +38,7 @@ func Validate(policy kyverno.Policy, resource unstructured.Unstructured) ([]info
}
// check if the resource satisfies the filter conditions defined in the rule
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := MatchesResourceDescription(resource, rule)
if !ok {
@ -61,7 +59,7 @@ func Validate(policy kyverno.Policy, resource unstructured.Unstructured) ([]info
ruleInfos = append(ruleInfos, ruleInfo)
}
return ruleInfos, nil
return EngineResponse{RuleInfos: ruleInfos}
}
// validateResourceWithPattern is a start of element-by-element validation process

View file

@ -4,9 +4,8 @@ import (
"encoding/json"
"testing"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
kubepolicy "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestValidateString_AsteriskTest(t *testing.T) {
@ -1570,11 +1569,10 @@ func TestValidate_ServiceTest(t *testing.T) {
var policy kubepolicy.Policy
json.Unmarshal(rawPolicy, &policy)
gvk := metav1.GroupVersionKind{
Kind: "Service",
}
_, err := Validate(policy, rawResource, gvk)
assert.Assert(t, err == nil)
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
res := Validate(policy, *resourceUnstructured)
assert.Assert(t, len(res.RuleInfos) == 0)
}
func TestValidate_MapHasFloats(t *testing.T) {
@ -1668,10 +1666,8 @@ func TestValidate_MapHasFloats(t *testing.T) {
var policy kubepolicy.Policy
json.Unmarshal(rawPolicy, &policy)
gvk := metav1.GroupVersionKind{
Kind: "Deployment",
}
_, err := Validate(policy, rawResource, gvk)
resourceUnstructured, err := ConvertToUnstructured(rawResource)
assert.NilError(t, err)
res := Validate(policy, *resourceUnstructured)
assert.Assert(t, len(res.RuleInfos) == 0)
}

View file

@ -85,7 +85,6 @@ func (gen *Generator) Run(workers int, stopCh <-chan struct{}) {
go wait.Until(gen.runWorker, time.Second, stopCh)
}
<-stopCh
}
func (gen *Generator) runWorker() {

View file

@ -21,8 +21,8 @@ const (
func (k MsgKey) String() string {
return [...]string{
"Failed to satisfy policy on resource '%s'.The following rule(s) '%s' failed to apply. Created Policy Violation",
"Failed to process rule '%s' of policy '%s'. Created Policy Violation",
"Policy violation on resource '%s'. The rule(s) '%s' failed to apply",
"Failed to process rule '%s' of policy '%s'.",
"Policy applied successfully on the resource '%s'",
"Rule(s) '%s' of Policy '%s' applied successfully",
"Resource %s creation blocked by rule(s) %s",

View file

@ -0,0 +1,160 @@
package gencontroller
import (
"fmt"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/golang/glog"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"k8s.io/apimachinery/pkg/api/errors"
v1Informer "k8s.io/client-go/informers/core/v1"
v1CoreLister "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
//Controller watches the 'Namespace' resource creation/update and applied the generation rules on them
type Controller struct {
client *client.Client
namespaceLister v1CoreLister.NamespaceLister
namespaceSynced cache.InformerSynced
policyLister policyLister.PolicyLister
eventController event.Generator
violationBuilder violation.Generator
annotationsController annotations.Controller
workqueue workqueue.RateLimitingInterface
}
//NewGenController returns a new Controller to manage generation rules
func NewGenController(client *client.Client,
eventController event.Generator,
policyInformer policySharedInformer.PolicyInformer,
violationBuilder violation.Generator,
namespaceInformer v1Informer.NamespaceInformer,
annotationsController annotations.Controller) *Controller {
// create the controller
controller := &Controller{
client: client,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,
policyLister: policyInformer.GetLister(),
eventController: eventController,
violationBuilder: violationBuilder,
annotationsController: annotationsController,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), wqNamespace),
}
namespaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.createNamespaceHandler,
UpdateFunc: controller.updateNamespaceHandler,
})
return controller
}
func (c *Controller) createNamespaceHandler(resource interface{}) {
c.enqueueNamespace(resource)
}
func (c *Controller) updateNamespaceHandler(oldResoruce, newResource interface{}) {
// DO we need to anything if the namespace is modified ?
}
func (c *Controller) enqueueNamespace(obj interface{}) {
var key string
var err error
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
glog.Error(err)
return
}
c.workqueue.Add(key)
}
//Run to run the controller
func (c *Controller) Run(stopCh <-chan struct{}) error {
if ok := cache.WaitForCacheSync(stopCh, c.namespaceSynced); !ok {
return fmt.Errorf("failed to wait for caches to sync")
}
for i := 0; i < workerCount; i++ {
go wait.Until(c.runWorker, time.Second, stopCh)
}
glog.Info("started namespace controller workers")
return nil
}
//Stop to stop the controller
func (c *Controller) Stop() {
c.workqueue.ShutDown()
glog.Info("shutting down namespace controller workers")
}
func (c *Controller) runWorker() {
for c.processNextWorkItem() {
}
}
func (c *Controller) processNextWorkItem() bool {
obj, shutdown := c.workqueue.Get()
if shutdown {
return false
}
err := func(obj interface{}) error {
defer c.workqueue.Done(obj)
err := c.syncHandler(obj)
c.handleErr(err, obj)
return nil
}(obj)
if err != nil {
glog.Error(err)
return true
}
return true
}
func (c *Controller) handleErr(err error, key interface{}) {
if err == nil {
c.workqueue.Forget(key)
return
}
if c.workqueue.NumRequeues(key) < wqRetryLimit {
glog.Warningf("Error syncing events %v: %v", key, err)
c.workqueue.AddRateLimited(key)
return
}
c.workqueue.Forget(key)
glog.Error(err)
glog.Warningf("Dropping the key %q out of the queue: %v", key, err)
}
func (c *Controller) syncHandler(obj interface{}) error {
var key string
var ok bool
if key, ok = obj.(string); !ok {
return fmt.Errorf("expected string in workqueue but got %v", obj)
}
// Namespace is cluster wide resource
_, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
glog.Errorf("invalid namespace key: %s", key)
return err
}
// Get Namespace
ns, err := c.namespaceLister.Get(name)
if err != nil {
if errors.IsNotFound(err) {
glog.Errorf("namespace '%s' in work queue no longer exists", key)
return nil
}
}
//TODO: need to find a way to store the policy such that we can directly queury the
// policies with generation policies
// PolicyListerExpansion
c.processNamespace(ns)
return nil
}

View file

@ -0,0 +1,155 @@
package gencontroller
import (
"encoding/json"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/engine"
event "github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/info"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
)
func (c *Controller) processNamespace(ns *corev1.Namespace) error {
//Get all policies and then verify if the namespace matches any of the defined selectors
policies, err := c.listPolicies(ns)
if err != nil {
return err
}
// process policy on namespace
for _, p := range policies {
c.processPolicy(ns, p)
}
return nil
}
func (c *Controller) listPolicies(ns *corev1.Namespace) ([]*v1alpha1.Policy, error) {
var fpolicies []*v1alpha1.Policy
policies, err := c.policyLister.List(labels.NewSelector())
if err != nil {
glog.Error("Unable to connect to policy controller. Unable to access policies not applying GENERATION rules")
return nil, err
}
for _, p := range policies {
// Check if the policy contains a generatoin rule
for _, r := range p.Spec.Rules {
if r.Generation != nil {
// Check if the resource meets the description
data, err := json.Marshal(ns)
if err != nil {
glog.Error(err)
continue
}
// convert types of GVK
nsGvk := schema.FromAPIVersionAndKind("v1", "Namespace")
// Hardcode as we have a informer on specified gvk
gvk := metav1.GroupVersionKind{Group: nsGvk.Group, Kind: nsGvk.Kind, Version: nsGvk.Version}
if engine.ResourceMeetsDescription(data, r.MatchResources.ResourceDescription, r.ExcludeResources.ResourceDescription, gvk) {
fpolicies = append(fpolicies, p)
break
}
}
}
}
return fpolicies, nil
}
func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) {
var eventInfo *event.Info
var onViolation bool
var msg string
policyInfo := info.NewPolicyInfo(p.Name,
"Namespace",
ns.Name,
"",
p.Spec.ValidationFailureAction) // Namespace has no namespace..WOW
// convert to unstructured
unstrMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ns)
if err != nil {
glog.Error(err)
return
}
unstObj := unstructured.Unstructured{Object: unstrMap}
ruleInfos := engine.Generate(c.client, p, unstObj)
policyInfo.AddRuleInfos(ruleInfos)
// generate annotations on namespace
c.createAnnotations(policyInfo)
//TODO generate namespace on created resources
if !policyInfo.IsSuccessful() {
glog.Infof("Failed to apply policy %s on resource %s %s", p.Name, ns.Kind, ns.Name)
for _, r := range ruleInfos {
glog.Warning(r.Msgs)
if msg = strings.Join(r.Msgs, " "); strings.Contains(msg, "rule configuration not present in resource") {
onViolation = true
msg = fmt.Sprintf(`Resource creation violates generate rule '%s' of policy '%s'`, r.Name, policyInfo.Name)
}
}
if onViolation {
glog.Infof("Adding violation for generation rule of policy %s\n", policyInfo.Name)
// Policy Violation
v := violation.BuldNewViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation.String(), policyInfo.FailedRules())
c.violationBuilder.Add(v)
} else {
// Event
eventInfo = event.NewEvent(policyKind, "", policyInfo.Name, event.RequestBlocked,
event.FPolicyApplyBlockCreate, policyInfo.RNamespace+"/"+policyInfo.RName, policyInfo.GetRuleNames(false))
glog.V(2).Infof("Request blocked event info has prepared for %s/%s\n", policyKind, policyInfo.Name)
c.eventController.Add(eventInfo)
}
return
}
glog.Infof("Generation from policy %s has succesfully applied to %s/%s", p.Name, policyInfo.RKind, policyInfo.RName)
eventInfo = event.NewEvent(policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName,
event.PolicyApplied, event.SRulesApply, policyInfo.GetRuleNames(true), policyInfo.Name)
glog.V(2).Infof("Success event info has prepared for %s/%s\n", policyInfo.RKind, policyInfo.RName)
c.eventController.Add(eventInfo)
}
func (c *Controller) createAnnotations(pi *info.PolicyInfo) {
//get resource
obj, err := c.client.GetResource(pi.RKind, pi.RNamespace, pi.RName)
if err != nil {
glog.Error(err)
return
}
// add annotation for policy application
ann := obj.GetAnnotations()
// Generation rules
gpatch, err := annotations.PatchAnnotations(ann, pi, info.Generation)
if err != nil {
glog.Error(err)
return
}
if gpatch == nil {
// nothing to patch
return
}
// add the anotation to the resource
_, err = c.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, gpatch)
if err != nil {
glog.Error(err)
return
}
}

View file

@ -104,7 +104,7 @@ type RuleInfo struct {
Name string
RuleType RuleType
Msgs []string
Changes string // this will store the mutation patch being applied by the rule
Patches [][]byte // this will store the mutation patch being applied by the rule
success bool
}

View file

@ -31,10 +31,9 @@ func applyPolicy(policy kyverno.Policy, resource unstructured.Unstructured) (inf
}
//VALIDATION
vruleInfos, err := engine.Validate(policy, resource)
policyInfo.AddRuleInfos(vruleInfos)
if err != nil {
return policyInfo, err
engineResponse := engine.Validate(policy, resource)
if len(engineResponse.RuleInfos) != 0 {
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
}
//TODO: GENERATION
@ -42,7 +41,9 @@ func applyPolicy(policy kyverno.Policy, resource unstructured.Unstructured) (inf
}
func mutation(policy kyverno.Policy, resource unstructured.Unstructured) ([]info.RuleInfo, error) {
patches, ruleInfos := engine.Mutate(policy, resource)
engineResponse := engine.Mutate(policy, resource)
patches := engineResponse.Patches
ruleInfos := engineResponse.RuleInfos
if len(ruleInfos) == 0 {
//no rules processed
return nil, nil

113
pkg/webhooks/annotations.go Normal file
View file

@ -0,0 +1,113 @@
package webhooks
import (
"encoding/json"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/info"
)
const (
policyAnnotation = "policies.kyverno.io"
// lastAppliedPatches = policyAnnotation + "last-applied-patches"
)
type policyPatch struct {
PolicyName string `json:"policyname"`
// RulePatches []string `json:"patches"`
RulePatches interface{} `json:"patches"`
}
type rulePatch struct {
RuleName string `json:"rulename"`
Op string `json:"op"`
Path string `json:"path"`
}
type response struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}
func prepareAnnotationPatches(resource *unstructured.Unstructured, policyInfos []info.PolicyInfo) []byte {
annots := resource.GetAnnotations()
if annots == nil {
annots = map[string]string{}
}
var patchResponse response
value := annotationFromPolicies(policyInfos)
if _, ok := annots[policyAnnotation]; ok {
// create update patch string
patchResponse = response{
Op: "replace",
Path: "/metadata/annotations/" + policyAnnotation,
Value: string(value),
}
} else {
patchResponse = response{
Op: "add",
Path: "/metadata/annotations",
Value: map[string]string{policyAnnotation: string(value)},
}
}
patchByte, _ := json.Marshal(patchResponse)
// check the patch
_, err := jsonpatch.DecodePatch([]byte("[" + string(patchByte) + "]"))
if err != nil {
glog.Errorf("Failed to make patch from annotation'%s', err: %v\n ", string(patchByte), err)
}
return patchByte
}
func annotationFromPolicies(policyInfos []info.PolicyInfo) []byte {
var policyPatches []policyPatch
for _, policyInfo := range policyInfos {
var pp policyPatch
pp.PolicyName = policyInfo.Name
pp.RulePatches = annotationFromPolicy(policyInfo)
policyPatches = append(policyPatches, pp)
}
result, _ := json.Marshal(policyPatches)
return result
}
func annotationFromPolicy(policyInfo info.PolicyInfo) []rulePatch {
if !policyInfo.IsSuccessful() {
glog.V(2).Infof("Policy %s failed, skip preparing annotation\n", policyInfo.Name)
return nil
}
var rulePatches []rulePatch
for _, ruleInfo := range policyInfo.Rules {
for _, patch := range ruleInfo.Patches {
var patchmap map[string]string
if err := json.Unmarshal(patch, &patchmap); err != nil {
glog.Errorf("Failed to parse patch bytes, err: %v\n", err)
continue
}
rp := rulePatch{
RuleName: ruleInfo.Name,
Op: patchmap["op"],
Path: patchmap["path"]}
rulePatches = append(rulePatches, rp)
glog.V(4).Infof("Annotation value prepared: %v\n", rulePatches)
}
}
return rulePatches
}

View file

@ -6,99 +6,85 @@ import (
"github.com/nirmata/kyverno/pkg/info"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// HandleMutation handles mutating webhook admission request
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool, engine.EngineResponse) {
var patches [][]byte
var policyInfos []info.PolicyInfo
// map to store the mutation changes on the resource
// mAnn := map[string]string{}
glog.V(4).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
glog.V(5).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
resource, err := engine.ConvertToUnstructured(request.Object.Raw)
if err != nil {
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
}
//TODO: check if resource gvk is available in raw resource,
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: check if the name and namespace is also passed right in the resource?
engineResponse := engine.EngineResponse{PatchedResource: *resource}
policies, err := ws.pLister.List(labels.NewSelector())
if err != nil {
//TODO check if the CRD is created ?
// Unable to connect to policy Lister to access policies
glog.Error("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
glog.Errorln("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
glog.Warning(err)
return &v1beta1.AdmissionResponse{
Allowed: true,
}
return true, engineResponse
}
resource, err := convertToUnstructured(request.Object.Raw)
if err != nil {
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
}
//TODO: check if resource gvk is available in raw resource,
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: check if the name and namespace is also passed right in the resource?
// all the patches to be applied on the resource
for _, policy := range policies {
// check if policy has a rule for the admission request kind
if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) {
continue
}
policyInfo := info.NewPolicyInfo(policy.Name, resource.GetKind(), resource.GetName(), resource.GetNamespace(), policy.Spec.ValidationFailureAction)
glog.V(4).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
glog.V(4).Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
// resource, err := utils.ConvertToUnstructured(request.Object.Raw)
// if err != nil {
// glog.Errorf("unable to process policy %s resource %v: %v", policy.GetName(), request.Resource, err)
// continue
// }
//TODO: check if the GVK information is present in the request of we set it explicity here ?
// resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: passing policy value as we dont wont to modify the policy
engineResponse = engine.Mutate(*policy, *resource)
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
policyPatches, ruleInfos := engine.Mutate(*policy, *resource)
policyInfo.AddRuleInfos(ruleInfos)
policyInfos = append(policyInfos, policyInfo)
if !policyInfo.IsSuccessful() {
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s", policy.Name, resource.GetNamespace(), resource.GetName())
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s\n", policy.Name, resource.GetNamespace(), resource.GetName())
glog.V(4).Info("Failed rule details")
for _, r := range ruleInfos {
for _, r := range engineResponse.RuleInfos {
glog.V(4).Infof("%s: %s\n", r.Name, r.Msgs)
}
continue
}
// build annotations per policy being applied to show the mutation changes
patches = append(patches, policyPatches...)
patches = append(patches, engineResponse.Patches...)
policyInfos = append(policyInfos, policyInfo)
glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
}
// ADD ANNOTATIONS
// TODO: merge the annotation patch with the patch response
// ADD EVENTS
if len(patches) > 0 {
eventsInfo := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation)
ws.eventGen.Add(eventsInfo...)
annotation := prepareAnnotationPatches(resource, policyInfos)
patches = append(patches, annotation)
}
// ADD POLICY VIOLATIONS
ok, msg := isAdmSuccesful(policyInfos)
if ok {
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
Allowed: true,
Patch: engine.JoinPatches(patches),
PatchType: &patchType,
}
}
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: msg,
},
engineResponse.Patches = patches
return true, engineResponse
}
glog.Errorf("Failed to mutate the resource: %s\n", msg)
return false, engineResponse
}

View file

@ -13,16 +13,39 @@ import (
//HandlePolicyValidation performs the validation check on policy resource
func (ws *WebhookServer) HandlePolicyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
return ws.validateUniqueRuleName(request.Object.Raw)
var policy *kyverno.Policy
admissionResp := &v1beta1.AdmissionResponse{
Allowed: true,
}
raw := request.Object.Raw
if request.Operation == v1beta1.Delete {
raw = request.OldObject.Raw
}
if err := json.Unmarshal(raw, &policy); err != nil {
glog.Errorf("Failed to unmarshal policy admission request, err %v\n", err)
return &v1beta1.AdmissionResponse{Allowed: false}
}
if request.Operation != v1beta1.Delete {
admissionResp = ws.validateUniqueRuleName(policy)
}
if admissionResp.Allowed {
ws.manageWebhookConfigurations(*policy, request.Operation)
}
return admissionResp
}
// Verify if the Rule names are unique within a policy
func (ws *WebhookServer) validateUniqueRuleName(rawPolicy []byte) *v1beta1.AdmissionResponse {
var policy *kyverno.Policy
func (ws *WebhookServer) validateUniqueRuleName(policy *kyverno.Policy) *v1beta1.AdmissionResponse {
// =======
// func (ws *WebhookServer) validateUniqueRuleName(rawPolicy []byte) *v1beta1.AdmissionResponse {
// var policy *kyverno.Policy
// >>>>>>> policyViolation
var ruleNames []string
json.Unmarshal(rawPolicy, &policy)
for _, rule := range policy.Spec.Rules {
if utils.Contains(ruleNames, rule.Name) {
msg := fmt.Sprintf(`The policy "%s" is invalid: duplicate rule name: "%s"`, policy.Name, rule.Name)

View file

@ -9,10 +9,10 @@ import (
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/tevino/abool"
admregapi "k8s.io/api/admissionregistration/v1beta1"
errorsapi "k8s.io/apimachinery/pkg/api/errors"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
admregclient "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
rest "k8s.io/client-go/rest"
)
@ -23,11 +23,14 @@ type WebhookRegistrationClient struct {
client *client.Client
clientConfig *rest.Config
// serverIP should be used if running Kyverno out of clutser
serverIP string
serverIP string
timeoutSeconds int32
MutationRegistered *abool.AtomicBool
ValidationRegistered *abool.AtomicBool
}
// NewWebhookRegistrationClient creates new WebhookRegistrationClient instance
func NewWebhookRegistrationClient(clientConfig *rest.Config, client *client.Client, serverIP string) (*WebhookRegistrationClient, error) {
func NewWebhookRegistrationClient(clientConfig *rest.Config, client *client.Client, serverIP string, webhookTimeout int32) (*WebhookRegistrationClient, error) {
registrationClient, err := admregclient.NewForConfig(clientConfig)
if err != nil {
return nil, err
@ -36,10 +39,13 @@ func NewWebhookRegistrationClient(clientConfig *rest.Config, client *client.Clie
glog.V(3).Infof("Registering webhook client using serverIP %s\n", serverIP)
return &WebhookRegistrationClient{
registrationClient: registrationClient,
client: client,
clientConfig: clientConfig,
serverIP: serverIP,
registrationClient: registrationClient,
client: client,
clientConfig: clientConfig,
serverIP: serverIP,
timeoutSeconds: webhookTimeout,
MutationRegistered: abool.New(),
ValidationRegistered: abool.New(),
}, nil
}
@ -48,68 +54,114 @@ func (wrc *WebhookRegistrationClient) Register() error {
if wrc.serverIP != "" {
glog.Infof("Registering webhook with url https://%s\n", wrc.serverIP)
}
// For the case if cluster already has this configs
wrc.Deregister()
// For the case if cluster already has this configs
wrc.DeregisterAll()
// register policy validating webhook during inital start
return wrc.RegisterPolicyValidatingWebhook()
}
func (wrc *WebhookRegistrationClient) RegisterMutatingWebhook() error {
mutatingWebhookConfig, err := wrc.constructMutatingWebhookConfig(wrc.clientConfig)
if err != nil {
return err
}
_, err = wrc.registrationClient.MutatingWebhookConfigurations().Create(mutatingWebhookConfig)
if err != nil {
if _, err = wrc.registrationClient.MutatingWebhookConfigurations().Create(mutatingWebhookConfig); err != nil {
return err
}
wrc.MutationRegistered.Set()
return nil
}
func (wrc *WebhookRegistrationClient) RegisterValidatingWebhook() error {
validationWebhookConfig, err := wrc.constructValidatingWebhookConfig(wrc.clientConfig)
if err != nil {
return err
}
_, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(validationWebhookConfig)
if err != nil {
if _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(validationWebhookConfig); err != nil {
return err
}
wrc.ValidationRegistered.Set()
return nil
}
func (wrc *WebhookRegistrationClient) RegisterPolicyValidatingWebhook() error {
policyValidationWebhookConfig, err := wrc.contructPolicyValidatingWebhookConfig()
if err != nil {
return err
}
_, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(policyValidationWebhookConfig)
if err != nil {
if _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(policyValidationWebhookConfig); err != nil {
return err
}
glog.V(3).Infoln("Policy validating webhook registered")
return nil
}
// Deregister deletes webhook configs from cluster
// DeregisterAll deletes webhook configs from cluster
// This function does not fail on error:
// Register will fail if the config exists, so there is no need to fail on error
func (wrc *WebhookRegistrationClient) Deregister() {
func (wrc *WebhookRegistrationClient) DeregisterAll() {
wrc.deregisterMutatingWebhook()
wrc.deregisterValidatingWebhook()
if wrc.serverIP != "" {
if err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationDebug, &meta.DeleteOptions{}); err != nil {
if !errorsapi.IsNotFound(err) {
glog.Errorf("Failed to deregister debug mutatingWebhookConfiguratinos, err: %v\n", err)
}
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationDebug, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
if err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationDebug, &meta.DeleteOptions{}); err != nil {
if !errorsapi.IsNotFound(err) {
glog.Errorf("Failed to deregister debug validatingWebhookConfiguratinos, err: %v\n", err)
}
}
if err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationDebug, &meta.DeleteOptions{}); err != nil {
if !errorsapi.IsNotFound(err) {
glog.Errorf("Failed to deregister debug policyValidatingWebhookConfiguratinos, err: %v\n", err)
}
}
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationName, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
}
func (wrc *WebhookRegistrationClient) deregister() {
wrc.deregisterMutatingWebhook()
wrc.deregisterValidatingWebhook()
}
func (wrc *WebhookRegistrationClient) deregisterMutatingWebhook() {
if wrc.serverIP != "" {
err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationDebug, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
} else {
wrc.MutationRegistered.UnSet()
}
return
}
wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationName, &meta.DeleteOptions{})
wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationName, &meta.DeleteOptions{})
wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationName, &meta.DeleteOptions{})
err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationName, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
} else {
wrc.MutationRegistered.UnSet()
}
}
func (wrc *WebhookRegistrationClient) deregisterValidatingWebhook() {
if wrc.serverIP != "" {
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationDebug, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
wrc.ValidationRegistered.UnSet()
return
}
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationName, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
wrc.ValidationRegistered.UnSet()
}
func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configuration *rest.Config) (*admregapi.MutatingWebhookConfiguration, error) {
@ -130,10 +182,10 @@ func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configurati
}
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: meta.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: config.MutatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []meta.OwnerReference{
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
@ -142,7 +194,9 @@ func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configurati
config.MutatingWebhookName,
config.MutatingWebhookServicePath,
caData,
false),
false,
wrc.timeoutSeconds,
),
},
}, nil
}
@ -152,7 +206,7 @@ func (wrc *WebhookRegistrationClient) contructDebugMutatingWebhookConfig(caData
glog.V(3).Infof("Debug MutatingWebhookConfig is registered with url %s\n", url)
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: meta.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: config.MutatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
@ -161,7 +215,8 @@ func (wrc *WebhookRegistrationClient) contructDebugMutatingWebhookConfig(caData
config.MutatingWebhookName,
url,
caData,
false),
false,
wrc.timeoutSeconds),
},
}
}
@ -183,10 +238,10 @@ func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(configura
}
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: meta.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []meta.OwnerReference{
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
@ -195,7 +250,8 @@ func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(configura
config.ValidatingWebhookName,
config.ValidatingWebhookServicePath,
caData,
true),
true,
wrc.timeoutSeconds),
},
}, nil
}
@ -205,7 +261,7 @@ func (wrc *WebhookRegistrationClient) contructDebugValidatingWebhookConfig(caDat
glog.V(3).Infof("Debug ValidatingWebhookConfig is registered with url %s\n", url)
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: meta.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
@ -214,7 +270,8 @@ func (wrc *WebhookRegistrationClient) contructDebugValidatingWebhookConfig(caDat
config.ValidatingWebhookName,
url,
caData,
true),
true,
wrc.timeoutSeconds),
},
}
}
@ -236,10 +293,10 @@ func (wrc *WebhookRegistrationClient) contructPolicyValidatingWebhookConfig() (*
}
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: meta.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyValidatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []meta.OwnerReference{
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
@ -248,7 +305,8 @@ func (wrc *WebhookRegistrationClient) contructPolicyValidatingWebhookConfig() (*
config.PolicyValidatingWebhookName,
config.PolicyValidatingWebhookServicePath,
caData,
true),
true,
wrc.timeoutSeconds),
},
}, nil
}
@ -258,7 +316,7 @@ func (wrc *WebhookRegistrationClient) contructDebugPolicyValidatingWebhookConfig
glog.V(3).Infof("Debug PolicyValidatingWebhookConfig is registered with url %s\n", url)
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: meta.ObjectMeta{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyValidatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
@ -267,12 +325,13 @@ func (wrc *WebhookRegistrationClient) contructDebugPolicyValidatingWebhookConfig
config.PolicyValidatingWebhookName,
url,
caData,
true),
true,
wrc.timeoutSeconds),
},
}
}
func constructWebhook(name, servicePath string, caData []byte, validation bool) admregapi.Webhook {
func constructWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32) admregapi.Webhook {
resource := "*/*"
apiGroups := "*"
apiversions := "*"
@ -317,10 +376,11 @@ func constructWebhook(name, servicePath string, caData []byte, validation bool)
},
},
},
TimeoutSeconds: &timeoutSeconds,
}
}
func constructDebugWebhook(name, url string, caData []byte, validation bool) admregapi.Webhook {
func constructDebugWebhook(name, url string, caData []byte, validation bool, timeoutSeconds int32) admregapi.Webhook {
resource := "*/*"
apiGroups := "*"
apiversions := "*"
@ -361,18 +421,19 @@ func constructDebugWebhook(name, url string, caData []byte, validation bool) adm
},
},
},
TimeoutSeconds: &timeoutSeconds,
}
}
func (wrc *WebhookRegistrationClient) constructOwner() meta.OwnerReference {
func (wrc *WebhookRegistrationClient) constructOwner() v1.OwnerReference {
kubePolicyDeployment, err := wrc.client.GetKubePolicyDeployment()
if err != nil {
glog.Errorf("Error when constructing OwnerReference, err: %v\n", err)
return meta.OwnerReference{}
return v1.OwnerReference{}
}
return meta.OwnerReference{
return v1.OwnerReference{
APIVersion: config.DeploymentAPIVersion,
Kind: config.DeploymentKind,
Name: kubePolicyDeployment.ObjectMeta.Name,

View file

@ -23,7 +23,7 @@ func newEventInfoFromPolicyInfo(policyInfoList []info.PolicyInfo, onUpdate bool,
if !onUpdate {
// CREATE
eventsInfo = append(eventsInfo,
event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyApplyBlockCreate, pi.RName, ruleNames))
event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyApplyBlockCreate, pi.RNamespace+"/"+pi.RName, ruleNames))
glog.V(3).Infof("Rule(s) %s of policy %s blocked resource creation, error: %s\n", ruleNames, pi.Name, msg)
} else {
@ -31,7 +31,7 @@ func newEventInfoFromPolicyInfo(policyInfoList []info.PolicyInfo, onUpdate bool,
eventsInfo = append(eventsInfo,
event.NewEvent(pi.RKind, pi.RNamespace, pi.RName, event.RequestBlocked, event.FPolicyApplyBlockUpdate, ruleNames, pi.Name))
eventsInfo = append(eventsInfo,
event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyBlockResourceUpdate, pi.RName, ruleNames))
event.NewEvent(policyKind, "", pi.Name, event.RequestBlocked, event.FPolicyBlockResourceUpdate, pi.RNamespace+"/"+pi.RName, ruleNames))
glog.V(3).Infof("Request blocked events info has prepared for %s/%s and %s/%s\n", policyKind, pi.Name, pi.RKind, pi.RName)
}
}

View file

@ -10,10 +10,12 @@ import (
"net/http"
"time"
"github.com/nirmata/kyverno/pkg/engine"
"github.com/golang/glog"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1alpha1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1"
lister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
@ -26,15 +28,16 @@ import (
// WebhookServer contains configured TLS server with MutationWebhook.
// MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient.
type WebhookServer struct {
server http.Server
client *client.Client
kyvernoClient *kyvernoclient.Clientset
pLister kyvernolister.PolicyLister
pvLister kyvernolister.PolicyViolationLister
pListerSynced cache.InformerSynced
pvListerSynced cache.InformerSynced
eventGen event.Interface
filterK8Resources []utils.K8Resource
server http.Server
client *client.Client
kyvernoClient *kyvernoclient.Clientset
pLister lister.PolicyLister
pvLister lister.PolicyViolationLister
pListerSynced cache.InformerSynced
pvListerSynced cache.InformerSynced
eventGen event.Interface
webhookRegistrationClient *WebhookRegistrationClient
filterK8Resources []utils.K8Resource
}
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
@ -46,6 +49,7 @@ func NewWebhookServer(
pInformer kyvernoinformer.PolicyInformer,
pvInormer kyvernoinformer.PolicyViolationInformer,
eventGen event.Interface,
webhookRegistrationClient *WebhookRegistrationClient,
filterK8Resources string) (*WebhookServer, error) {
if tlsPair == nil {
@ -60,14 +64,16 @@ func NewWebhookServer(
tlsConfig.Certificates = []tls.Certificate{pair}
ws := &WebhookServer{
client: client,
kyvernoClient: kyvernoClient,
pLister: pInformer.Lister(),
pvLister: pvInormer.Lister(),
pListerSynced: pInformer.Informer().HasSynced,
pvListerSynced: pInformer.Informer().HasSynced,
eventGen: eventGen,
filterK8Resources: utils.ParseKinds(filterK8Resources),
client: client,
kyvernoClient: kyvernoClient,
pLister: pInformer.Lister(),
pvLister: pvInormer.Lister(),
pListerSynced: pInformer.Informer().HasSynced,
pvListerSynced: pInformer.Informer().HasSynced,
eventGen: eventGen,
webhookRegistrationClient: webhookRegistrationClient,
filterK8Resources: utils.ParseKinds(filterK8Resources),
}
mux := http.NewServeMux()
mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve)
@ -102,9 +108,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
// Resource UPDATE
switch r.URL.Path {
case config.MutatingWebhookServicePath:
admissionReview.Response = ws.HandleMutation(admissionReview.Request)
case config.ValidatingWebhookServicePath:
admissionReview.Response = ws.HandleValidation(admissionReview.Request)
admissionReview.Response = ws.HandleAdmissionRequest(admissionReview.Request)
case config.PolicyValidatingWebhookServicePath:
admissionReview.Response = ws.HandlePolicyValidation(admissionReview.Request)
}
@ -124,6 +128,27 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
}
}
func (ws *WebhookServer) HandleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
var response *v1beta1.AdmissionResponse
allowed, engineResponse := ws.HandleMutation(request)
if !allowed {
// TODO: add failure message to response
return &v1beta1.AdmissionResponse{
Allowed: false,
}
}
response = ws.HandleValidation(request, engineResponse.PatchedResource)
if response.Allowed && len(engineResponse.Patches) > 0 {
patchType := v1beta1.PatchTypeJSONPatch
response.Patch = engine.JoinPatches(engineResponse.Patches)
response.PatchType = &patchType
}
return response
}
// RunAsync TLS server in separate thread and returns control immediately
func (ws *WebhookServer) RunAsync() {
go func(ws *WebhookServer) {

View file

@ -7,7 +7,6 @@ import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const policyKind = "Policy"
@ -81,18 +80,10 @@ const (
func toBlock(pis []info.PolicyInfo) bool {
for _, pi := range pis {
if pi.ValidationFailureAction != ReportViolation {
glog.V(3).Infoln("ValidationFailureAction set to enforce, blocking resource ceation")
return true
}
}
glog.V(3).Infoln("ValidationFailureAction set to audit, allowing resource creation, reporting with violation")
return false
}
func convertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
resource := &unstructured.Unstructured{}
err := resource.UnmarshalJSON(data)
if err != nil {
glog.V(4).Infof("failed to unmarshall resource: %v", err)
return nil, err
}
return resource, nil
}

View file

@ -8,16 +8,17 @@ import (
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// HandleValidation handles validating webhook admission request
// If there are no errors in validating rule we apply generation rules
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, resource unstructured.Unstructured) *v1beta1.AdmissionResponse {
var policyInfos []info.PolicyInfo
glog.V(4).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
glog.V(5).Infof("Receive request in validating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
policies, err := ws.pLister.List(labels.NewSelector())
@ -31,10 +32,6 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
}
}
resource, err := convertToUnstructured(request.Object.Raw)
if err != nil {
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
}
//TODO: check if resource gvk is available in raw resource,
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
@ -52,30 +49,28 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
glog.V(4).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
glog.V(4).Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
glog.V(4).Infof("Validating resource %s/%s/%s with policy %s with %d rules\n", resource.GetKind(), resource.GetNamespace(), resource.GetName(), policy.ObjectMeta.Name, len(policy.Spec.Rules))
ruleInfos, err := engine.Validate(*policy, *resource)
if err != nil {
// This is not policy error
// but if unable to parse request raw resource
// TODO : create event ? dont think so
glog.Error(err)
engineResponse := engine.Validate(*policy, resource)
if len(engineResponse.RuleInfos) == 0 {
continue
}
policyInfo.AddRuleInfos(ruleInfos)
policyInfos = append(policyInfos, policyInfo)
if len(engineResponse.RuleInfos) > 0 {
glog.V(4).Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
}
policyInfo.AddRuleInfos(engineResponse.RuleInfos)
if !policyInfo.IsSuccessful() {
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, resource.GetNamespace(), resource.GetName())
glog.V(4).Info("Failed rule details")
for _, r := range ruleInfos {
glog.V(4).Infof("%s: %s\n", r.Name, r.Msgs)
for _, r := range engineResponse.RuleInfos {
glog.Warningf("%s: %s\n", r.Name, r.Msgs)
}
continue
}
if len(ruleInfos) > 0 {
glog.V(4).Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
}
policyInfos = append(policyInfos, policyInfo)
}
// ADD EVENTS
@ -86,6 +81,10 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
ws.eventGen.Add(eventsInfo...)
}
// If Validation fails then reject the request
// violations are created if "audit" flag is set
// and if there are any then we dont block the resource creation
// Even if one the policy being applied
ok, msg := isAdmSuccesful(policyInfos)
if !ok && toBlock(policyInfos) {
return &v1beta1.AdmissionResponse{
@ -102,5 +101,4 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
return &v1beta1.AdmissionResponse{
Allowed: true,
}
// Generation rules applied via generation controller
}

View file

@ -0,0 +1,49 @@
package webhooks
import (
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
v1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/labels"
)
type policyType int
const (
none policyType = iota
mutate
validate
all
)
func (ws *WebhookServer) manageWebhookConfigurations(policy kyverno.Policy, op v1beta1.Operation) {
switch op {
case v1beta1.Create:
ws.registerWebhookConfigurations(policy)
case v1beta1.Delete:
ws.deregisterWebhookConfigurations(policy)
}
}
func (ws *WebhookServer) registerWebhookConfigurations(policy kyverno.Policy) error {
if !ws.webhookRegistrationClient.MutationRegistered.IsSet() {
if err := ws.webhookRegistrationClient.RegisterMutatingWebhook(); err != nil {
return err
}
glog.Infof("Mutating webhook registered")
}
return nil
}
func (ws *WebhookServer) deregisterWebhookConfigurations(policy kyverno.Policy) error {
policies, _ := ws.pLister.List(labels.NewSelector())
// deregister webhook if no policy found in cluster
if len(policies) == 1 {
ws.webhookRegistrationClient.deregisterMutatingWebhook()
glog.Infoln("Mutating webhook deregistered")
}
return nil
}

View file

@ -18,7 +18,7 @@ dep ensure -v || exit 2
echo "# Building executable ${project_name}..."
chmod +x scripts/update-codegen.sh
scripts/update-codegen.sh
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o ${project_name} . || exit 3
make build || exit 3
echo "# Building docker image ${hub_user_name}/${project_name}:${version}"
cat <<EOF > Dockerfile

View file

@ -0,0 +1,41 @@
#!/bin/bash
### To use this script to generate resource:
### ./resource.sh --file=resource.yaml --replica=10
for i in "$@"
do
case $i in
--file=*)
file="${i#*=}"
shift
;;
--replica=*)
replica="${i#*=}"
shift
;;
esac
done
if [ -z "${file}" ]; then
echo -e "Please specify '--file' where resource is located."
exit 1
fi
if [ -z "${replica}" ]; then
echo -e "Please specify '--replica' of the number of replicas you want to create."
exit 1
fi
echo "loading resource from ${file}"
RESOURCE=$(cat ${file} | sed -n -e 's/^ name: //p')
echo "generating ${replica} replicas from resource $RESOURCE"
for i in $(seq 1 ${replica})
do
# echo `cat ${file} | sed "s/name: ${RESOURCE}/name: ${RESOURCE}-${i}/"`
dstfile=`sed 's/.\{5\}$/-$i&/' <<< "${file}"`
cat ${file} | sed "s/name: ${RESOURCE}/name: ${RESOURCE}-${i}/" > ${dstfile}
done

View file

@ -0,0 +1,73 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
ioutil "io/ioutil"
"os"
"path/filepath"
"strconv"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
yaml "k8s.io/apimachinery/pkg/util/yaml"
)
var policyPath, replica string
func main() {
generatePolicies()
}
func generatePolicies() error {
var policy *kubepolicy.Policy
file, err := ioutil.ReadFile(policyPath)
if err != nil {
return fmt.Errorf("failed to load file: %v", err)
}
fmt.Printf("Generating policies from %s\n", policyPath)
rawPolicy, err := yaml.ToJSON(file)
if err != nil {
return err
}
if err := json.Unmarshal(rawPolicy, &policy); err != nil {
return fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err)
}
oldName := policy.Name
repl, _ := strconv.Atoi(replica)
for i := 0; i < repl; i++ {
newName := oldName + "-" + strconv.Itoa(i)
data := bytes.Replace(file, []byte(oldName), []byte(newName), -1)
writeToFile(data, "./.policy/"+newName+".yaml")
}
return nil
}
func writeToFile(data []byte, filename string) {
dir := filepath.Dir(filename)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.MkdirAll(dir, 0755); err != nil {
fmt.Println(err)
}
}
if err := ioutil.WriteFile(filename, data, 0755); err != nil {
fmt.Println(err)
}
}
func init() {
flag.StringVar(&policyPath, "policyPath", "", "Path to a policy")
flag.StringVar(&replica, "replica", "10", "the number of replicas to generate")
flag.Parse()
}

5
vendor/github.com/evanphx/json-patch/go.mod generated vendored Normal file
View file

@ -0,0 +1,5 @@
module github.com/evanphx/json-patch
go 1.12
require github.com/pkg/errors v0.8.1

2
vendor/github.com/evanphx/json-patch/go.sum generated vendored Normal file
View file

@ -0,0 +1,2 @@
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View file

@ -245,7 +245,7 @@ func (g *Group) load(ctx Context, key string, dest Sink) (value ByteView, destPo
// be only one entry for this key.
//
// Consider the following serialized event ordering for two
// goroutines in which this callback gets called twice for hte
// goroutines in which this callback gets called twice for the
// same key:
// 1: Get("key")
// 2: Get("key")

View file

@ -57,6 +57,7 @@ import (
)
const secondInNanos = int64(time.Second / time.Nanosecond)
const maxSecondsInDuration = 315576000000
// Marshaler is a configurable object for converting between
// protocol buffer objects and a JSON representation for them.
@ -182,7 +183,12 @@ func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeU
return fmt.Errorf("failed to marshal type URL %q to JSON: %v", typeURL, err)
}
js["@type"] = (*json.RawMessage)(&turl)
if b, err = json.Marshal(js); err != nil {
if m.Indent != "" {
b, err = json.MarshalIndent(js, indent, m.Indent)
} else {
b, err = json.Marshal(js)
}
if err != nil {
return err
}
}
@ -206,19 +212,26 @@ func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeU
// Any is a bit more involved.
return m.marshalAny(out, v, indent)
case "Duration":
// "Generated output always contains 0, 3, 6, or 9 fractional digits,
// depending on required precision."
s, ns := s.Field(0).Int(), s.Field(1).Int()
if s < -maxSecondsInDuration || s > maxSecondsInDuration {
return fmt.Errorf("seconds out of range %v", s)
}
if ns <= -secondInNanos || ns >= secondInNanos {
return fmt.Errorf("ns out of range (%v, %v)", -secondInNanos, secondInNanos)
}
if (s > 0 && ns < 0) || (s < 0 && ns > 0) {
return errors.New("signs of seconds and nanos do not match")
}
if s < 0 {
// Generated output always contains 0, 3, 6, or 9 fractional digits,
// depending on required precision, followed by the suffix "s".
f := "%d.%09d"
if ns < 0 {
ns = -ns
if s == 0 {
f = "-%d.%09d"
}
}
x := fmt.Sprintf("%d.%09d", s, ns)
x := fmt.Sprintf(f, s, ns)
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, ".000")

View file

@ -473,10 +473,17 @@ var marshalingTests = []struct {
{"Any with message and indent", marshalerAllOptions, anySimple, anySimplePrettyJSON},
{"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON},
{"Any with WKT and indent", marshalerAllOptions, anyWellKnown, anyWellKnownPrettyJSON},
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}, `{"dur":"3s"}`},
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3, Nanos: 1e6}}, `{"dur":"3.001s"}`},
{"Duration beyond float64 precision", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 100000000, Nanos: 1}}, `{"dur":"100000000.000000001s"}`},
{"negative Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: -123, Nanos: -456}}, `{"dur":"-123.000000456s"}`},
{"Duration empty", marshaler, &durpb.Duration{}, `"0s"`},
{"Duration with secs", marshaler, &durpb.Duration{Seconds: 3}, `"3s"`},
{"Duration with -secs", marshaler, &durpb.Duration{Seconds: -3}, `"-3s"`},
{"Duration with nanos", marshaler, &durpb.Duration{Nanos: 1e6}, `"0.001s"`},
{"Duration with -nanos", marshaler, &durpb.Duration{Nanos: -1e6}, `"-0.001s"`},
{"Duration with large secs", marshaler, &durpb.Duration{Seconds: 1e10, Nanos: 1}, `"10000000000.000000001s"`},
{"Duration with 6-digit nanos", marshaler, &durpb.Duration{Nanos: 1e4}, `"0.000010s"`},
{"Duration with 3-digit nanos", marshaler, &durpb.Duration{Nanos: 1e6}, `"0.001s"`},
{"Duration with -secs -nanos", marshaler, &durpb.Duration{Seconds: -123, Nanos: -450}, `"-123.000000450s"`},
{"Duration max value", marshaler, &durpb.Duration{Seconds: 315576000000, Nanos: 999999999}, `"315576000000.999999999s"`},
{"Duration min value", marshaler, &durpb.Duration{Seconds: -315576000000, Nanos: -999999999}, `"-315576000000.999999999s"`},
{"Struct", marshaler, &pb.KnownTypes{St: &stpb.Struct{
Fields: map[string]*stpb.Value{
"one": {Kind: &stpb.Value_StringValue{"loneliest number"}},
@ -549,15 +556,17 @@ func TestMarshalIllegalTime(t *testing.T) {
pb proto.Message
fail bool
}{
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: 0}}, false},
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: 0}}, false},
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: -1}}, true},
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: 1}}, true},
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: 1000000000}}, true},
{&pb.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: -1000000000}}, true},
{&pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: 1}}, false},
{&pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: -1}}, true},
{&pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: 1000000000}}, true},
{&durpb.Duration{Seconds: 1, Nanos: 0}, false},
{&durpb.Duration{Seconds: -1, Nanos: 0}, false},
{&durpb.Duration{Seconds: 1, Nanos: -1}, true},
{&durpb.Duration{Seconds: -1, Nanos: 1}, true},
{&durpb.Duration{Seconds: 315576000001}, true},
{&durpb.Duration{Seconds: -315576000001}, true},
{&durpb.Duration{Seconds: 1, Nanos: 1000000000}, true},
{&durpb.Duration{Seconds: -1, Nanos: -1000000000}, true},
{&tspb.Timestamp{Seconds: 1, Nanos: 1}, false},
{&tspb.Timestamp{Seconds: 1, Nanos: -1}, true},
{&tspb.Timestamp{Seconds: 1, Nanos: 1000000000}, true},
}
for _, tt := range tests {
_, err := marshaler.MarshalToString(tt.pb)
@ -598,6 +607,28 @@ func TestMarshalAnyJSONPBMarshaler(t *testing.T) {
if str != expected {
t.Errorf("marshalling JSON produced incorrect output: got %s, wanted %s", str, expected)
}
// Do it again, but this time with indentation:
marshaler := Marshaler{Indent: " "}
str, err = marshaler.MarshalToString(a)
if err != nil {
t.Errorf("an unexpected error occurred when marshalling Any to JSON: %v", err)
}
// same as expected above, but pretty-printed w/ indentation
expected = `{
"@type": "type.googleapis.com/` + dynamicMessageName + `",
"baz": [
0,
1,
2,
3
],
"foo": "bar"
}`
if str != expected {
t.Errorf("marshalling JSON produced incorrect output: got %s, wanted %s", str, expected)
}
}
func TestMarshalWithCustomValidation(t *testing.T) {

View file

@ -38,7 +38,6 @@ package proto
import (
"fmt"
"log"
"os"
"reflect"
"sort"
"strconv"
@ -194,7 +193,7 @@ func (p *Properties) Parse(s string) {
// "bytes,49,opt,name=foo,def=hello!"
fields := strings.Split(s, ",") // breaks def=, but handled below.
if len(fields) < 2 {
fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
log.Printf("proto: tag has too few fields: %q", s)
return
}
@ -214,7 +213,7 @@ func (p *Properties) Parse(s string) {
p.WireType = WireBytes
// no numeric converter for non-numeric types
default:
fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
log.Printf("proto: tag has unknown wire type: %q", s)
return
}

View file

@ -54,6 +54,8 @@ const generatedCodeVersion = 4
const (
contextPkgPath = "context"
grpcPkgPath = "google.golang.org/grpc"
codePkgPath = "google.golang.org/grpc/codes"
statusPkgPath = "google.golang.org/grpc/status"
)
func init() {
@ -216,6 +218,12 @@ func (g *grpc) generateService(file *generator.FileDescriptor, service *pb.Servi
g.P("}")
g.P()
// Server Unimplemented struct for forward compatability.
if deprecated {
g.P(deprecationComment)
}
g.generateUnimplementedServer(servName, service)
// Server registration.
if deprecated {
g.P(deprecationComment)
@ -269,6 +277,35 @@ func (g *grpc) generateService(file *generator.FileDescriptor, service *pb.Servi
g.P()
}
// generateUnimplementedServer creates the unimplemented server struct
func (g *grpc) generateUnimplementedServer(servName string, service *pb.ServiceDescriptorProto) {
serverType := servName + "Server"
g.P("// Unimplemented", serverType, " can be embedded to have forward compatible implementations.")
g.P("type Unimplemented", serverType, " struct {")
g.P("}")
g.P()
// Unimplemented<service_name>Server's concrete methods
for _, method := range service.Method {
g.generateServerMethodConcrete(servName, method)
}
g.P()
}
// generateServerMethodConcrete returns unimplemented methods which ensure forward compatibility
func (g *grpc) generateServerMethodConcrete(servName string, method *pb.MethodDescriptorProto) {
header := g.generateServerSignatureWithParamNames(servName, method)
g.P("func (*Unimplemented", servName, "Server) ", header, " {")
var nilArg string
if !method.GetServerStreaming() && !method.GetClientStreaming() {
nilArg = "nil, "
}
methName := generator.CamelCase(method.GetName())
statusPkg := string(g.gen.AddImport(statusPkgPath))
codePkg := string(g.gen.AddImport(codePkgPath))
g.P("return ", nilArg, statusPkg, `.Errorf(`, codePkg, `.Unimplemented, "method `, methName, ` not implemented")`)
g.P("}")
}
// generateClientSignature returns the client-side signature for a method.
func (g *grpc) generateClientSignature(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()
@ -368,6 +405,30 @@ func (g *grpc) generateClientMethod(servName, fullServName, serviceDescVar strin
}
}
// generateServerSignatureWithParamNames returns the server-side signature for a method with parameter names.
func (g *grpc) generateServerSignatureWithParamNames(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()
methName := generator.CamelCase(origMethName)
if reservedClientName[methName] {
methName += "_"
}
var reqArgs []string
ret := "error"
if !method.GetServerStreaming() && !method.GetClientStreaming() {
reqArgs = append(reqArgs, "ctx "+contextPkg+".Context")
ret = "(*" + g.typeName(method.GetOutputType()) + ", error)"
}
if !method.GetClientStreaming() {
reqArgs = append(reqArgs, "req *"+g.typeName(method.GetInputType()))
}
if method.GetServerStreaming() || method.GetClientStreaming() {
reqArgs = append(reqArgs, "srv "+servName+"_"+generator.CamelCase(origMethName)+"Server")
}
return methName + "(" + strings.Join(reqArgs, ", ") + ") " + ret
}
// generateServerSignature returns the server-side signature for a method.
func (g *grpc) generateServerSignature(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()

View file

@ -10,6 +10,8 @@ import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
@ -235,6 +237,15 @@ type DeprecatedServiceServer interface {
DeprecatedCall(context.Context, *DeprecatedRequest) (*DeprecatedResponse, error)
}
// Deprecated: Do not use.
// UnimplementedDeprecatedServiceServer can be embedded to have forward compatible implementations.
type UnimplementedDeprecatedServiceServer struct {
}
func (*UnimplementedDeprecatedServiceServer) DeprecatedCall(ctx context.Context, req *DeprecatedRequest) (*DeprecatedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeprecatedCall not implemented")
}
// Deprecated: Do not use.
func RegisterDeprecatedServiceServer(s *grpc.Server, srv DeprecatedServiceServer) {
s.RegisterService(&_DeprecatedService_serviceDesc, srv)

View file

@ -8,6 +8,8 @@ import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
@ -321,6 +323,23 @@ type TestServer interface {
Bidi(Test_BidiServer) error
}
// UnimplementedTestServer can be embedded to have forward compatible implementations.
type UnimplementedTestServer struct {
}
func (*UnimplementedTestServer) UnaryCall(ctx context.Context, req *SimpleRequest) (*SimpleResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnaryCall not implemented")
}
func (*UnimplementedTestServer) Downstream(req *SimpleRequest, srv Test_DownstreamServer) error {
return status.Errorf(codes.Unimplemented, "method Downstream not implemented")
}
func (*UnimplementedTestServer) Upstream(srv Test_UpstreamServer) error {
return status.Errorf(codes.Unimplemented, "method Upstream not implemented")
}
func (*UnimplementedTestServer) Bidi(srv Test_BidiServer) error {
return status.Errorf(codes.Unimplemented, "method Bidi not implemented")
}
func RegisterTestServer(s *grpc.Server, srv TestServer) {
s.RegisterService(&_Test_serviceDesc, srv)
}

View file

@ -0,0 +1,79 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: grpc/grpc_empty.proto
package testing
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
func init() { proto.RegisterFile("grpc/grpc_empty.proto", fileDescriptor_c580a37f1c90e9b1) }
var fileDescriptor_c580a37f1c90e9b1 = []byte{
// 125 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4d, 0x2f, 0x2a, 0x48,
0xd6, 0x07, 0x11, 0xf1, 0xa9, 0xb9, 0x05, 0x25, 0x95, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42,
0x3c, 0x20, 0x11, 0xbd, 0x92, 0xd4, 0xe2, 0x92, 0xcc, 0xbc, 0x74, 0x23, 0x3e, 0x2e, 0x1e, 0x57,
0x90, 0x64, 0x70, 0x6a, 0x51, 0x59, 0x66, 0x72, 0xaa, 0x93, 0x43, 0x94, 0x5d, 0x7a, 0x66, 0x49,
0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x7a, 0x7e, 0x4e, 0x62, 0x5e, 0xba, 0x3e, 0x58,
0x63, 0x52, 0x69, 0x1a, 0x84, 0x91, 0xac, 0x9b, 0x9e, 0x9a, 0xa7, 0x9b, 0x9e, 0xaf, 0x0f, 0x32,
0x23, 0x25, 0xb1, 0x24, 0x11, 0x6c, 0x87, 0x35, 0xd4, 0xc4, 0x24, 0x36, 0xb0, 0x22, 0x63, 0x40,
0x00, 0x00, 0x00, 0xff, 0xff, 0x93, 0x1d, 0xf2, 0x47, 0x7f, 0x00, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// EmptyServiceClient is the client API for EmptyService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type EmptyServiceClient interface {
}
type emptyServiceClient struct {
cc *grpc.ClientConn
}
func NewEmptyServiceClient(cc *grpc.ClientConn) EmptyServiceClient {
return &emptyServiceClient{cc}
}
// EmptyServiceServer is the server API for EmptyService service.
type EmptyServiceServer interface {
}
// UnimplementedEmptyServiceServer can be embedded to have forward compatible implementations.
type UnimplementedEmptyServiceServer struct {
}
func RegisterEmptyServiceServer(s *grpc.Server, srv EmptyServiceServer) {
s.RegisterService(&_EmptyService_serviceDesc, srv)
}
var _EmptyService_serviceDesc = grpc.ServiceDesc{
ServiceName: "grpc.testing.EmptyService",
HandlerType: (*EmptyServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{},
Metadata: "grpc/grpc_empty.proto",
}

View file

@ -0,0 +1,38 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2019 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package grpc.testing;
option go_package = "github.com/golang/protobuf/protoc-gen-go/testdata/grpc;testing";
service EmptyService {}

View file

@ -13,12 +13,6 @@
cd
mkdir -p local
# Install swift
SWIFT_URL=https://swift.org/builds/swift-4.0-branch/ubuntu1404/swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-01-a/swift-4.0-DEVELOPMENT-SNAPSHOT-2017-09-01-a-ubuntu14.04.tar.gz
echo $SWIFT_URL
curl -fSsL $SWIFT_URL -o swift.tar.gz
tar -xzf swift.tar.gz --strip-components=2 --directory=local
# Install protoc
PROTOC_URL=https://github.com/google/protobuf/releases/download/v3.4.0/protoc-3.4.0-linux-x86_64.zip
echo $PROTOC_URL

View file

@ -29,21 +29,3 @@ install:
script:
- go test . -v
- pushd plugins/gnostic-go-generator/examples/v2.0/bookstore
- make test
- popd
- pushd plugins/gnostic-go-generator/examples/v2.0/sample
- make test
- popd
- pushd plugins/gnostic-go-generator/examples/v3.0/bookstore
- make test
- popd
- export PATH=.:$HOME/local/bin:$PATH
- export LD_LIBRARY_PATH=$HOME/local/lib
- pushd plugins/gnostic-swift-generator
- make install
- cd examples/bookstore
- make
- .build/debug/Server &
- make test

View file

@ -8,9 +8,5 @@ build:
cd apps/petstore-builder; go get; go install
cd plugins/gnostic-summary; go get; go install
cd plugins/gnostic-analyze; go get; go install
cd plugins/gnostic-go-generator; go get; go install
rm -f $(GOPATH)/bin/gnostic-go-client $(GOPATH)/bin/gnostic-go-server
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-client
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-server
cd extensions/sample; make

View file

@ -7105,15 +7105,15 @@ func (m *Any) ToRawInfo() interface{} {
// ToRawInfo returns a description of ApiKeySecurity suitable for JSON or YAML export.
func (m *ApiKeySecurity) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
if m.In != "" {
info = append(info, yaml.MapItem{Key: "in", Value: m.In})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
// always include this required field.
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
// always include this required field.
info = append(info, yaml.MapItem{Key: "in", Value: m.In})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7129,9 +7129,11 @@ func (m *ApiKeySecurity) ToRawInfo() interface{} {
// ToRawInfo returns a description of BasicAuthenticationSecurity suitable for JSON or YAML export.
func (m *BasicAuthenticationSecurity) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7147,21 +7149,21 @@ func (m *BasicAuthenticationSecurity) ToRawInfo() interface{} {
// ToRawInfo returns a description of BodyParameter suitable for JSON or YAML export.
func (m *BodyParameter) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
if m.In != "" {
info = append(info, yaml.MapItem{Key: "in", Value: m.In})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
// always include this required field.
info = append(info, yaml.MapItem{Key: "in", Value: m.In})
if m.Required != false {
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
}
if m.Schema != nil {
info = append(info, yaml.MapItem{Key: "schema", Value: m.Schema.ToRawInfo()})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "schema", Value: m.Schema.ToRawInfo()})
// &{Name:schema Type:Schema StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.VendorExtension != nil {
for _, item := range m.VendorExtension {
@ -7175,6 +7177,9 @@ func (m *BodyParameter) ToRawInfo() interface{} {
// ToRawInfo returns a description of Contact suitable for JSON or YAML export.
func (m *Contact) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7196,6 +7201,9 @@ func (m *Contact) ToRawInfo() interface{} {
// ToRawInfo returns a description of Default suitable for JSON or YAML export.
func (m *Default) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7208,6 +7216,9 @@ func (m *Default) ToRawInfo() interface{} {
// ToRawInfo returns a description of Definitions suitable for JSON or YAML export.
func (m *Definitions) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7220,12 +7231,13 @@ func (m *Definitions) ToRawInfo() interface{} {
// ToRawInfo returns a description of Document suitable for JSON or YAML export.
func (m *Document) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Swagger != "" {
info = append(info, yaml.MapItem{Key: "swagger", Value: m.Swagger})
}
if m.Info != nil {
info = append(info, yaml.MapItem{Key: "info", Value: m.Info.ToRawInfo()})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "swagger", Value: m.Swagger})
// always include this required field.
info = append(info, yaml.MapItem{Key: "info", Value: m.Info.ToRawInfo()})
// &{Name:info Type:Info StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.Host != "" {
info = append(info, yaml.MapItem{Key: "host", Value: m.Host})
@ -7242,9 +7254,8 @@ func (m *Document) ToRawInfo() interface{} {
if len(m.Produces) != 0 {
info = append(info, yaml.MapItem{Key: "produces", Value: m.Produces})
}
if m.Paths != nil {
info = append(info, yaml.MapItem{Key: "paths", Value: m.Paths.ToRawInfo()})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "paths", Value: m.Paths.ToRawInfo()})
// &{Name:paths Type:Paths StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.Definitions != nil {
info = append(info, yaml.MapItem{Key: "definitions", Value: m.Definitions.ToRawInfo()})
@ -7294,6 +7305,9 @@ func (m *Document) ToRawInfo() interface{} {
// ToRawInfo returns a description of Examples suitable for JSON or YAML export.
func (m *Examples) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7306,12 +7320,14 @@ func (m *Examples) ToRawInfo() interface{} {
// ToRawInfo returns a description of ExternalDocs suitable for JSON or YAML export.
func (m *ExternalDocs) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
if m.Url != "" {
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
if m.VendorExtension != nil {
for _, item := range m.VendorExtension {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7324,6 +7340,9 @@ func (m *ExternalDocs) ToRawInfo() interface{} {
// ToRawInfo returns a description of FileSchema suitable for JSON or YAML export.
func (m *FileSchema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Format != "" {
info = append(info, yaml.MapItem{Key: "format", Value: m.Format})
}
@ -7340,9 +7359,8 @@ func (m *FileSchema) ToRawInfo() interface{} {
if len(m.Required) != 0 {
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
if m.ReadOnly != false {
info = append(info, yaml.MapItem{Key: "readOnly", Value: m.ReadOnly})
}
@ -7366,6 +7384,9 @@ func (m *FileSchema) ToRawInfo() interface{} {
// ToRawInfo returns a description of FormDataParameterSubSchema suitable for JSON or YAML export.
func (m *FormDataParameterSubSchema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Required != false {
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
}
@ -7451,9 +7472,11 @@ func (m *FormDataParameterSubSchema) ToRawInfo() interface{} {
// ToRawInfo returns a description of Header suitable for JSON or YAML export.
func (m *Header) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
if m.Format != "" {
info = append(info, yaml.MapItem{Key: "format", Value: m.Format})
}
@ -7524,6 +7547,9 @@ func (m *Header) ToRawInfo() interface{} {
// ToRawInfo returns a description of HeaderParameterSubSchema suitable for JSON or YAML export.
func (m *HeaderParameterSubSchema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Required != false {
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
}
@ -7606,6 +7632,9 @@ func (m *HeaderParameterSubSchema) ToRawInfo() interface{} {
// ToRawInfo returns a description of Headers suitable for JSON or YAML export.
func (m *Headers) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7618,12 +7647,13 @@ func (m *Headers) ToRawInfo() interface{} {
// ToRawInfo returns a description of Info suitable for JSON or YAML export.
func (m *Info) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Title != "" {
info = append(info, yaml.MapItem{Key: "title", Value: m.Title})
}
if m.Version != "" {
info = append(info, yaml.MapItem{Key: "version", Value: m.Version})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "title", Value: m.Title})
// always include this required field.
info = append(info, yaml.MapItem{Key: "version", Value: m.Version})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7650,6 +7680,9 @@ func (m *Info) ToRawInfo() interface{} {
// ToRawInfo returns a description of ItemsItem suitable for JSON or YAML export.
func (m *ItemsItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if len(m.Schema) != 0 {
items := make([]interface{}, 0)
for _, item := range m.Schema {
@ -7664,9 +7697,11 @@ func (m *ItemsItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of JsonReference suitable for JSON or YAML export.
func (m *JsonReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.XRef != "" {
info = append(info, yaml.MapItem{Key: "$ref", Value: m.XRef})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "$ref", Value: m.XRef})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7676,9 +7711,11 @@ func (m *JsonReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of License suitable for JSON or YAML export.
func (m *License) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m.Url != "" {
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
}
@ -7694,6 +7731,9 @@ func (m *License) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedAny suitable for JSON or YAML export.
func (m *NamedAny) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7704,6 +7744,9 @@ func (m *NamedAny) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedHeader suitable for JSON or YAML export.
func (m *NamedHeader) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7714,6 +7757,9 @@ func (m *NamedHeader) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedParameter suitable for JSON or YAML export.
func (m *NamedParameter) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7724,6 +7770,9 @@ func (m *NamedParameter) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedPathItem suitable for JSON or YAML export.
func (m *NamedPathItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7734,6 +7783,9 @@ func (m *NamedPathItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedResponse suitable for JSON or YAML export.
func (m *NamedResponse) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7744,6 +7796,9 @@ func (m *NamedResponse) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedResponseValue suitable for JSON or YAML export.
func (m *NamedResponseValue) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7754,6 +7809,9 @@ func (m *NamedResponseValue) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedSchema suitable for JSON or YAML export.
func (m *NamedSchema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7764,6 +7822,9 @@ func (m *NamedSchema) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedSecurityDefinitionsItem suitable for JSON or YAML export.
func (m *NamedSecurityDefinitionsItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7774,6 +7835,9 @@ func (m *NamedSecurityDefinitionsItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedString suitable for JSON or YAML export.
func (m *NamedString) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7786,6 +7850,9 @@ func (m *NamedString) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedStringArray suitable for JSON or YAML export.
func (m *NamedStringArray) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7823,22 +7890,21 @@ func (m *NonBodyParameter) ToRawInfo() interface{} {
// ToRawInfo returns a description of Oauth2AccessCodeSecurity suitable for JSON or YAML export.
func (m *Oauth2AccessCodeSecurity) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
}
if m.Flow != "" {
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
// always include this required field.
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m.Scopes != nil {
info = append(info, yaml.MapItem{Key: "scopes", Value: m.Scopes.ToRawInfo()})
}
// &{Name:scopes Type:Oauth2Scopes StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.AuthorizationUrl != "" {
info = append(info, yaml.MapItem{Key: "authorizationUrl", Value: m.AuthorizationUrl})
}
if m.TokenUrl != "" {
info = append(info, yaml.MapItem{Key: "tokenUrl", Value: m.TokenUrl})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "authorizationUrl", Value: m.AuthorizationUrl})
// always include this required field.
info = append(info, yaml.MapItem{Key: "tokenUrl", Value: m.TokenUrl})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7854,19 +7920,19 @@ func (m *Oauth2AccessCodeSecurity) ToRawInfo() interface{} {
// ToRawInfo returns a description of Oauth2ApplicationSecurity suitable for JSON or YAML export.
func (m *Oauth2ApplicationSecurity) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
}
if m.Flow != "" {
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
// always include this required field.
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m.Scopes != nil {
info = append(info, yaml.MapItem{Key: "scopes", Value: m.Scopes.ToRawInfo()})
}
// &{Name:scopes Type:Oauth2Scopes StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.TokenUrl != "" {
info = append(info, yaml.MapItem{Key: "tokenUrl", Value: m.TokenUrl})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "tokenUrl", Value: m.TokenUrl})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7882,19 +7948,19 @@ func (m *Oauth2ApplicationSecurity) ToRawInfo() interface{} {
// ToRawInfo returns a description of Oauth2ImplicitSecurity suitable for JSON or YAML export.
func (m *Oauth2ImplicitSecurity) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
}
if m.Flow != "" {
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
// always include this required field.
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m.Scopes != nil {
info = append(info, yaml.MapItem{Key: "scopes", Value: m.Scopes.ToRawInfo()})
}
// &{Name:scopes Type:Oauth2Scopes StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.AuthorizationUrl != "" {
info = append(info, yaml.MapItem{Key: "authorizationUrl", Value: m.AuthorizationUrl})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "authorizationUrl", Value: m.AuthorizationUrl})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7910,19 +7976,19 @@ func (m *Oauth2ImplicitSecurity) ToRawInfo() interface{} {
// ToRawInfo returns a description of Oauth2PasswordSecurity suitable for JSON or YAML export.
func (m *Oauth2PasswordSecurity) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
}
if m.Flow != "" {
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
// always include this required field.
info = append(info, yaml.MapItem{Key: "flow", Value: m.Flow})
if m.Scopes != nil {
info = append(info, yaml.MapItem{Key: "scopes", Value: m.Scopes.ToRawInfo()})
}
// &{Name:scopes Type:Oauth2Scopes StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.TokenUrl != "" {
info = append(info, yaml.MapItem{Key: "tokenUrl", Value: m.TokenUrl})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "tokenUrl", Value: m.TokenUrl})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7938,6 +8004,9 @@ func (m *Oauth2PasswordSecurity) ToRawInfo() interface{} {
// ToRawInfo returns a description of Oauth2Scopes suitable for JSON or YAML export.
func (m *Oauth2Scopes) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
// &{Name:additionalProperties Type:NamedString StringEnumValues:[] MapType:string Repeated:true Pattern: Implicit:true Description:}
return info
}
@ -7945,6 +8014,9 @@ func (m *Oauth2Scopes) ToRawInfo() interface{} {
// ToRawInfo returns a description of Operation suitable for JSON or YAML export.
func (m *Operation) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if len(m.Tags) != 0 {
info = append(info, yaml.MapItem{Key: "tags", Value: m.Tags})
}
@ -7975,9 +8047,8 @@ func (m *Operation) ToRawInfo() interface{} {
info = append(info, yaml.MapItem{Key: "parameters", Value: items})
}
// &{Name:parameters Type:ParametersItem StringEnumValues:[] MapType: Repeated:true Pattern: Implicit:false Description:The parameters needed to send a valid API call.}
if m.Responses != nil {
info = append(info, yaml.MapItem{Key: "responses", Value: m.Responses.ToRawInfo()})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "responses", Value: m.Responses.ToRawInfo()})
// &{Name:responses Type:Responses StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if len(m.Schemes) != 0 {
info = append(info, yaml.MapItem{Key: "schemes", Value: m.Schemes})
@ -8022,6 +8093,9 @@ func (m *Parameter) ToRawInfo() interface{} {
// ToRawInfo returns a description of ParameterDefinitions suitable for JSON or YAML export.
func (m *ParameterDefinitions) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8051,6 +8125,9 @@ func (m *ParametersItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of PathItem suitable for JSON or YAML export.
func (m *PathItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.XRef != "" {
info = append(info, yaml.MapItem{Key: "$ref", Value: m.XRef})
}
@ -8102,9 +8179,11 @@ func (m *PathItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of PathParameterSubSchema suitable for JSON or YAML export.
func (m *PathParameterSubSchema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Required != false {
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
if m.In != "" {
info = append(info, yaml.MapItem{Key: "in", Value: m.In})
}
@ -8184,6 +8263,9 @@ func (m *PathParameterSubSchema) ToRawInfo() interface{} {
// ToRawInfo returns a description of Paths suitable for JSON or YAML export.
func (m *Paths) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.VendorExtension != nil {
for _, item := range m.VendorExtension {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8202,6 +8284,9 @@ func (m *Paths) ToRawInfo() interface{} {
// ToRawInfo returns a description of PrimitivesItems suitable for JSON or YAML export.
func (m *PrimitivesItems) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
}
@ -8272,6 +8357,9 @@ func (m *PrimitivesItems) ToRawInfo() interface{} {
// ToRawInfo returns a description of Properties suitable for JSON or YAML export.
func (m *Properties) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8284,6 +8372,9 @@ func (m *Properties) ToRawInfo() interface{} {
// ToRawInfo returns a description of QueryParameterSubSchema suitable for JSON or YAML export.
func (m *QueryParameterSubSchema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Required != false {
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
}
@ -8369,9 +8460,11 @@ func (m *QueryParameterSubSchema) ToRawInfo() interface{} {
// ToRawInfo returns a description of Response suitable for JSON or YAML export.
func (m *Response) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
if m.Schema != nil {
info = append(info, yaml.MapItem{Key: "schema", Value: m.Schema.ToRawInfo()})
}
@ -8396,6 +8489,9 @@ func (m *Response) ToRawInfo() interface{} {
// ToRawInfo returns a description of ResponseDefinitions suitable for JSON or YAML export.
func (m *ResponseDefinitions) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8425,6 +8521,9 @@ func (m *ResponseValue) ToRawInfo() interface{} {
// ToRawInfo returns a description of Responses suitable for JSON or YAML export.
func (m *Responses) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.ResponseCode != nil {
for _, item := range m.ResponseCode {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8443,6 +8542,9 @@ func (m *Responses) ToRawInfo() interface{} {
// ToRawInfo returns a description of Schema suitable for JSON or YAML export.
func (m *Schema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.XRef != "" {
info = append(info, yaml.MapItem{Key: "$ref", Value: m.XRef})
}
@ -8588,6 +8690,9 @@ func (m *SchemaItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of SecurityDefinitions suitable for JSON or YAML export.
func (m *SecurityDefinitions) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8637,6 +8742,9 @@ func (m *SecurityDefinitionsItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of SecurityRequirement suitable for JSON or YAML export.
func (m *SecurityRequirement) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8654,9 +8762,11 @@ func (m *StringArray) ToRawInfo() interface{} {
// ToRawInfo returns a description of Tag suitable for JSON or YAML export.
func (m *Tag) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -8676,6 +8786,9 @@ func (m *Tag) ToRawInfo() interface{} {
// ToRawInfo returns a description of TypeItem suitable for JSON or YAML export.
func (m *TypeItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if len(m.Value) != 0 {
info = append(info, yaml.MapItem{Key: "value", Value: m.Value})
}
@ -8685,6 +8798,9 @@ func (m *TypeItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of VendorExtension suitable for JSON or YAML export.
func (m *VendorExtension) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8697,6 +8813,9 @@ func (m *VendorExtension) ToRawInfo() interface{} {
// ToRawInfo returns a description of Xml suitable for JSON or YAML export.
func (m *Xml) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}

View file

@ -6714,6 +6714,9 @@ func (m *AnyOrExpression) ToRawInfo() interface{} {
// ToRawInfo returns a description of AnysOrExpressions suitable for JSON or YAML export.
func (m *AnysOrExpressions) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -6726,6 +6729,9 @@ func (m *AnysOrExpressions) ToRawInfo() interface{} {
// ToRawInfo returns a description of Callback suitable for JSON or YAML export.
func (m *Callback) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Path != nil {
for _, item := range m.Path {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -6761,6 +6767,9 @@ func (m *CallbackOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of CallbacksOrReferences suitable for JSON or YAML export.
func (m *CallbacksOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -6773,6 +6782,9 @@ func (m *CallbacksOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of Components suitable for JSON or YAML export.
func (m *Components) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Schemas != nil {
info = append(info, yaml.MapItem{Key: "schemas", Value: m.Schemas.ToRawInfo()})
}
@ -6821,6 +6833,9 @@ func (m *Components) ToRawInfo() interface{} {
// ToRawInfo returns a description of Contact suitable for JSON or YAML export.
func (m *Contact) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -6861,9 +6876,11 @@ func (m *DefaultType) ToRawInfo() interface{} {
// ToRawInfo returns a description of Discriminator suitable for JSON or YAML export.
func (m *Discriminator) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.PropertyName != "" {
info = append(info, yaml.MapItem{Key: "propertyName", Value: m.PropertyName})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "propertyName", Value: m.PropertyName})
if m.Mapping != nil {
info = append(info, yaml.MapItem{Key: "mapping", Value: m.Mapping.ToRawInfo()})
}
@ -6874,12 +6891,13 @@ func (m *Discriminator) ToRawInfo() interface{} {
// ToRawInfo returns a description of Document suitable for JSON or YAML export.
func (m *Document) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Openapi != "" {
info = append(info, yaml.MapItem{Key: "openapi", Value: m.Openapi})
}
if m.Info != nil {
info = append(info, yaml.MapItem{Key: "info", Value: m.Info.ToRawInfo()})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "openapi", Value: m.Openapi})
// always include this required field.
info = append(info, yaml.MapItem{Key: "info", Value: m.Info.ToRawInfo()})
// &{Name:info Type:Info StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if len(m.Servers) != 0 {
items := make([]interface{}, 0)
@ -6889,9 +6907,8 @@ func (m *Document) ToRawInfo() interface{} {
info = append(info, yaml.MapItem{Key: "servers", Value: items})
}
// &{Name:servers Type:Server StringEnumValues:[] MapType: Repeated:true Pattern: Implicit:false Description:}
if m.Paths != nil {
info = append(info, yaml.MapItem{Key: "paths", Value: m.Paths.ToRawInfo()})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "paths", Value: m.Paths.ToRawInfo()})
// &{Name:paths Type:Paths StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.Components != nil {
info = append(info, yaml.MapItem{Key: "components", Value: m.Components.ToRawInfo()})
@ -6929,6 +6946,9 @@ func (m *Document) ToRawInfo() interface{} {
// ToRawInfo returns a description of Encoding suitable for JSON or YAML export.
func (m *Encoding) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.ContentType != "" {
info = append(info, yaml.MapItem{Key: "contentType", Value: m.ContentType})
}
@ -6957,6 +6977,9 @@ func (m *Encoding) ToRawInfo() interface{} {
// ToRawInfo returns a description of Encodings suitable for JSON or YAML export.
func (m *Encodings) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -6969,6 +6992,9 @@ func (m *Encodings) ToRawInfo() interface{} {
// ToRawInfo returns a description of Example suitable for JSON or YAML export.
func (m *Example) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Summary != "" {
info = append(info, yaml.MapItem{Key: "summary", Value: m.Summary})
}
@ -7008,6 +7034,9 @@ func (m *ExampleOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of ExamplesOrReferences suitable for JSON or YAML export.
func (m *ExamplesOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7020,6 +7049,9 @@ func (m *ExamplesOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of Expression suitable for JSON or YAML export.
func (m *Expression) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7032,12 +7064,14 @@ func (m *Expression) ToRawInfo() interface{} {
// ToRawInfo returns a description of ExternalDocs suitable for JSON or YAML export.
func (m *ExternalDocs) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
if m.Url != "" {
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
if m.SpecificationExtension != nil {
for _, item := range m.SpecificationExtension {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7050,6 +7084,9 @@ func (m *ExternalDocs) ToRawInfo() interface{} {
// ToRawInfo returns a description of Header suitable for JSON or YAML export.
func (m *Header) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7116,6 +7153,9 @@ func (m *HeaderOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of HeadersOrReferences suitable for JSON or YAML export.
func (m *HeadersOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7128,9 +7168,11 @@ func (m *HeadersOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of Info suitable for JSON or YAML export.
func (m *Info) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Title != "" {
info = append(info, yaml.MapItem{Key: "title", Value: m.Title})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "title", Value: m.Title})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7145,9 +7187,8 @@ func (m *Info) ToRawInfo() interface{} {
info = append(info, yaml.MapItem{Key: "license", Value: m.License.ToRawInfo()})
}
// &{Name:license Type:License StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.Version != "" {
info = append(info, yaml.MapItem{Key: "version", Value: m.Version})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "version", Value: m.Version})
if m.SpecificationExtension != nil {
for _, item := range m.SpecificationExtension {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7160,6 +7201,9 @@ func (m *Info) ToRawInfo() interface{} {
// ToRawInfo returns a description of ItemsItem suitable for JSON or YAML export.
func (m *ItemsItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if len(m.SchemaOrReference) != 0 {
items := make([]interface{}, 0)
for _, item := range m.SchemaOrReference {
@ -7174,9 +7218,11 @@ func (m *ItemsItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of License suitable for JSON or YAML export.
func (m *License) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m.Url != "" {
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
}
@ -7192,6 +7238,9 @@ func (m *License) ToRawInfo() interface{} {
// ToRawInfo returns a description of Link suitable for JSON or YAML export.
func (m *Link) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.OperationRef != "" {
info = append(info, yaml.MapItem{Key: "operationRef", Value: m.OperationRef})
}
@ -7242,6 +7291,9 @@ func (m *LinkOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of LinksOrReferences suitable for JSON or YAML export.
func (m *LinksOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7254,6 +7306,9 @@ func (m *LinksOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of MediaType suitable for JSON or YAML export.
func (m *MediaType) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Schema != nil {
info = append(info, yaml.MapItem{Key: "schema", Value: m.Schema.ToRawInfo()})
}
@ -7282,6 +7337,9 @@ func (m *MediaType) ToRawInfo() interface{} {
// ToRawInfo returns a description of MediaTypes suitable for JSON or YAML export.
func (m *MediaTypes) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7294,6 +7352,9 @@ func (m *MediaTypes) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedAny suitable for JSON or YAML export.
func (m *NamedAny) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7304,6 +7365,9 @@ func (m *NamedAny) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedAnyOrExpression suitable for JSON or YAML export.
func (m *NamedAnyOrExpression) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7314,6 +7378,9 @@ func (m *NamedAnyOrExpression) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedCallbackOrReference suitable for JSON or YAML export.
func (m *NamedCallbackOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7324,6 +7391,9 @@ func (m *NamedCallbackOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedEncoding suitable for JSON or YAML export.
func (m *NamedEncoding) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7334,6 +7404,9 @@ func (m *NamedEncoding) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedExampleOrReference suitable for JSON or YAML export.
func (m *NamedExampleOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7344,6 +7417,9 @@ func (m *NamedExampleOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedHeaderOrReference suitable for JSON or YAML export.
func (m *NamedHeaderOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7354,6 +7430,9 @@ func (m *NamedHeaderOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedLinkOrReference suitable for JSON or YAML export.
func (m *NamedLinkOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7364,6 +7443,9 @@ func (m *NamedLinkOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedMediaType suitable for JSON or YAML export.
func (m *NamedMediaType) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7374,6 +7456,9 @@ func (m *NamedMediaType) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedParameterOrReference suitable for JSON or YAML export.
func (m *NamedParameterOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7384,6 +7469,9 @@ func (m *NamedParameterOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedPathItem suitable for JSON or YAML export.
func (m *NamedPathItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7394,6 +7482,9 @@ func (m *NamedPathItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedRequestBodyOrReference suitable for JSON or YAML export.
func (m *NamedRequestBodyOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7404,6 +7495,9 @@ func (m *NamedRequestBodyOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedResponseOrReference suitable for JSON or YAML export.
func (m *NamedResponseOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7414,6 +7508,9 @@ func (m *NamedResponseOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedSchemaOrReference suitable for JSON or YAML export.
func (m *NamedSchemaOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7424,6 +7521,9 @@ func (m *NamedSchemaOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedSecuritySchemeOrReference suitable for JSON or YAML export.
func (m *NamedSecuritySchemeOrReference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7434,6 +7534,9 @@ func (m *NamedSecuritySchemeOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedServerVariable suitable for JSON or YAML export.
func (m *NamedServerVariable) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7444,6 +7547,9 @@ func (m *NamedServerVariable) ToRawInfo() interface{} {
// ToRawInfo returns a description of NamedString suitable for JSON or YAML export.
func (m *NamedString) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
@ -7456,6 +7562,9 @@ func (m *NamedString) ToRawInfo() interface{} {
// ToRawInfo returns a description of OauthFlow suitable for JSON or YAML export.
func (m *OauthFlow) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AuthorizationUrl != "" {
info = append(info, yaml.MapItem{Key: "authorizationUrl", Value: m.AuthorizationUrl})
}
@ -7481,6 +7590,9 @@ func (m *OauthFlow) ToRawInfo() interface{} {
// ToRawInfo returns a description of OauthFlows suitable for JSON or YAML export.
func (m *OauthFlows) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Implicit != nil {
info = append(info, yaml.MapItem{Key: "implicit", Value: m.Implicit.ToRawInfo()})
}
@ -7509,6 +7621,9 @@ func (m *OauthFlows) ToRawInfo() interface{} {
// ToRawInfo returns a description of Object suitable for JSON or YAML export.
func (m *Object) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7521,6 +7636,9 @@ func (m *Object) ToRawInfo() interface{} {
// ToRawInfo returns a description of Operation suitable for JSON or YAML export.
func (m *Operation) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if len(m.Tags) != 0 {
info = append(info, yaml.MapItem{Key: "tags", Value: m.Tags})
}
@ -7549,9 +7667,8 @@ func (m *Operation) ToRawInfo() interface{} {
info = append(info, yaml.MapItem{Key: "requestBody", Value: m.RequestBody.ToRawInfo()})
}
// &{Name:requestBody Type:RequestBodyOrReference StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.Responses != nil {
info = append(info, yaml.MapItem{Key: "responses", Value: m.Responses.ToRawInfo()})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "responses", Value: m.Responses.ToRawInfo()})
// &{Name:responses Type:Responses StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.Callbacks != nil {
info = append(info, yaml.MapItem{Key: "callbacks", Value: m.Callbacks.ToRawInfo()})
@ -7588,12 +7705,13 @@ func (m *Operation) ToRawInfo() interface{} {
// ToRawInfo returns a description of Parameter suitable for JSON or YAML export.
func (m *Parameter) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}
if m.In != "" {
info = append(info, yaml.MapItem{Key: "in", Value: m.In})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
// always include this required field.
info = append(info, yaml.MapItem{Key: "in", Value: m.In})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -7660,6 +7778,9 @@ func (m *ParameterOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of ParametersOrReferences suitable for JSON or YAML export.
func (m *ParametersOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7672,6 +7793,9 @@ func (m *ParametersOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of PathItem suitable for JSON or YAML export.
func (m *PathItem) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.XRef != "" {
info = append(info, yaml.MapItem{Key: "$ref", Value: m.XRef})
}
@ -7741,6 +7865,9 @@ func (m *PathItem) ToRawInfo() interface{} {
// ToRawInfo returns a description of Paths suitable for JSON or YAML export.
func (m *Paths) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Path != nil {
for _, item := range m.Path {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7759,6 +7886,9 @@ func (m *Paths) ToRawInfo() interface{} {
// ToRawInfo returns a description of Properties suitable for JSON or YAML export.
func (m *Properties) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7771,15 +7901,20 @@ func (m *Properties) ToRawInfo() interface{} {
// ToRawInfo returns a description of Reference suitable for JSON or YAML export.
func (m *Reference) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.XRef != "" {
info = append(info, yaml.MapItem{Key: "$ref", Value: m.XRef})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "$ref", Value: m.XRef})
return info
}
// ToRawInfo returns a description of RequestBodiesOrReferences suitable for JSON or YAML export.
func (m *RequestBodiesOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7792,12 +7927,14 @@ func (m *RequestBodiesOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of RequestBody suitable for JSON or YAML export.
func (m *RequestBody) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
if m.Content != nil {
info = append(info, yaml.MapItem{Key: "content", Value: m.Content.ToRawInfo()})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "content", Value: m.Content.ToRawInfo()})
// &{Name:content Type:MediaTypes StringEnumValues:[] MapType: Repeated:false Pattern: Implicit:false Description:}
if m.Required != false {
info = append(info, yaml.MapItem{Key: "required", Value: m.Required})
@ -7831,9 +7968,11 @@ func (m *RequestBodyOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of Response suitable for JSON or YAML export.
func (m *Response) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
if m.Headers != nil {
info = append(info, yaml.MapItem{Key: "headers", Value: m.Headers.ToRawInfo()})
}
@ -7875,6 +8014,9 @@ func (m *ResponseOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of Responses suitable for JSON or YAML export.
func (m *Responses) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Default != nil {
info = append(info, yaml.MapItem{Key: "default", Value: m.Default.ToRawInfo()})
}
@ -7897,6 +8039,9 @@ func (m *Responses) ToRawInfo() interface{} {
// ToRawInfo returns a description of ResponsesOrReferences suitable for JSON or YAML export.
func (m *ResponsesOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -7909,6 +8054,9 @@ func (m *ResponsesOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of Schema suitable for JSON or YAML export.
func (m *Schema) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Nullable != false {
info = append(info, yaml.MapItem{Key: "nullable", Value: m.Nullable})
}
@ -8076,6 +8224,9 @@ func (m *SchemaOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of SchemasOrReferences suitable for JSON or YAML export.
func (m *SchemasOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8088,15 +8239,20 @@ func (m *SchemasOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of SecurityRequirement suitable for JSON or YAML export.
func (m *SecurityRequirement) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
return info
}
// ToRawInfo returns a description of SecurityScheme suitable for JSON or YAML export.
func (m *SecurityScheme) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Type != "" {
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "type", Value: m.Type})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -8148,6 +8304,9 @@ func (m *SecuritySchemeOrReference) ToRawInfo() interface{} {
// ToRawInfo returns a description of SecuritySchemesOrReferences suitable for JSON or YAML export.
func (m *SecuritySchemesOrReferences) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8160,9 +8319,11 @@ func (m *SecuritySchemesOrReferences) ToRawInfo() interface{} {
// ToRawInfo returns a description of Server suitable for JSON or YAML export.
func (m *Server) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Url != "" {
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "url", Value: m.Url})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -8182,12 +8343,14 @@ func (m *Server) ToRawInfo() interface{} {
// ToRawInfo returns a description of ServerVariable suitable for JSON or YAML export.
func (m *ServerVariable) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if len(m.Enum) != 0 {
info = append(info, yaml.MapItem{Key: "enum", Value: m.Enum})
}
if m.Default != "" {
info = append(info, yaml.MapItem{Key: "default", Value: m.Default})
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "default", Value: m.Default})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -8203,6 +8366,9 @@ func (m *ServerVariable) ToRawInfo() interface{} {
// ToRawInfo returns a description of ServerVariables suitable for JSON or YAML export.
func (m *ServerVariables) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.AdditionalProperties != nil {
for _, item := range m.AdditionalProperties {
info = append(info, yaml.MapItem{Key: item.Name, Value: item.Value.ToRawInfo()})
@ -8239,6 +8405,9 @@ func (m *StringArray) ToRawInfo() interface{} {
// ToRawInfo returns a description of Strings suitable for JSON or YAML export.
func (m *Strings) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
// &{Name:additionalProperties Type:NamedString StringEnumValues:[] MapType:string Repeated:true Pattern: Implicit:true Description:}
return info
}
@ -8246,9 +8415,11 @@ func (m *Strings) ToRawInfo() interface{} {
// ToRawInfo returns a description of Tag suitable for JSON or YAML export.
func (m *Tag) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m == nil {
return info
}
// always include this required field.
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
if m.Description != "" {
info = append(info, yaml.MapItem{Key: "description", Value: m.Description})
}
@ -8268,6 +8439,9 @@ func (m *Tag) ToRawInfo() interface{} {
// ToRawInfo returns a description of Xml suitable for JSON or YAML export.
func (m *Xml) ToRawInfo() interface{} {
info := yaml.MapSlice{}
if m == nil {
return info
}
if m.Name != "" {
info = append(info, yaml.MapItem{Key: "name", Value: m.Name})
}

View file

@ -93,12 +93,9 @@ Protocol Buffer description. This is mainly for use in testing and debugging.
go install github.com/googleapis/gnostic/apps/report
report petstore.pb
8. **gnostic** supports plugins. This builds and runs a sample plugin
that reports some basic information about an API. The "-" causes the plugin to
write its output to stdout.
go install github.com/googleapis/gnostic/plugins/gnostic-go-sample
gnostic examples/v2.0/json/petstore.json --go-sample-out=-
8. **gnostic** supports plugins. Some are already implemented in the `plugins` directory.
Others, like [gnostic-go-generator](https://github.com/googleapis/gnostic-go-generator),
are separated into their own repositories.
## Copyright

View file

@ -285,8 +285,10 @@ func OpenAPIv3(api *discovery.Document) (*openapi3.Document, error) {
d.Components = &openapi3.Components{}
d.Components.Schemas = &openapi3.SchemasOrReferences{}
for _, pair := range api.Schemas.AdditionalProperties {
addOpenAPI3SchemaForSchema(d, pair.Name, pair.Value)
if api.Schemas != nil {
for _, pair := range api.Schemas.AdditionalProperties {
addOpenAPI3SchemaForSchema(d, pair.Name, pair.Value)
}
}
d.Paths = &openapi3.Paths{}

View file

@ -20,6 +20,7 @@ import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/golang/protobuf/proto"
@ -28,18 +29,17 @@ import (
pb "github.com/googleapis/gnostic/OpenAPIv2"
)
func readDocumentFromFileWithName(filename string) *pb.Document {
func readDocumentFromFileWithName(filename string) (*pb.Document, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Printf("File error: %v\n", err)
os.Exit(1)
return nil, err
}
document := &pb.Document{}
err = proto.Unmarshal(data, document)
if err != nil {
panic(err)
return nil, err
}
return document
return document, nil
}
func printDocument(code *printer.Code, document *pb.Document) {
@ -229,8 +229,12 @@ func main() {
return
}
document := readDocumentFromFileWithName(args[0])
document, err := readDocumentFromFileWithName(args[0])
if err != nil {
log.Printf("Error reading %s. This sample expects OpenAPI v2.", args[0])
os.Exit(-1)
}
code := &printer.Code{}
code.Print("API REPORT")
code.Print("----------")

View file

@ -17,13 +17,14 @@ package compiler
import (
"errors"
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
"net/http"
"net/url"
"path/filepath"
"strings"
yaml "gopkg.in/yaml.v2"
)
var fileCache map[string][]byte
@ -31,6 +32,8 @@ var infoCache map[string]interface{}
var count int64
var verboseReader = false
var fileCacheEnable = true
var infoCacheEnable = true
func initializeFileCache() {
if fileCache == nil {
@ -44,29 +47,56 @@ func initializeInfoCache() {
}
}
func DisableFileCache() {
fileCacheEnable = false
}
func DisableInfoCache() {
infoCacheEnable = false
}
func RemoveFromFileCache(fileurl string) {
if !fileCacheEnable {
return
}
initializeFileCache()
delete(fileCache, fileurl)
}
func RemoveFromInfoCache(filename string) {
if !infoCacheEnable {
return
}
initializeInfoCache()
delete(infoCache, filename)
}
// FetchFile gets a specified file from the local filesystem or a remote location.
func FetchFile(fileurl string) ([]byte, error) {
var bytes []byte
initializeFileCache()
bytes, ok := fileCache[fileurl]
if ok {
if verboseReader {
log.Printf("Cache hit %s", fileurl)
if fileCacheEnable {
bytes, ok := fileCache[fileurl]
if ok {
if verboseReader {
log.Printf("Cache hit %s", fileurl)
}
return bytes, nil
}
if verboseReader {
log.Printf("Fetching %s", fileurl)
}
return bytes, nil
}
if verboseReader {
log.Printf("Fetching %s", fileurl)
}
response, err := http.Get(fileurl)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != 200 {
return nil, errors.New(fmt.Sprintf("Error downloading %s: %s", fileurl, response.Status))
}
defer response.Body.Close()
bytes, err = ioutil.ReadAll(response.Body)
if err == nil {
if fileCacheEnable && err == nil {
fileCache[fileurl] = bytes
}
return bytes, err
@ -95,22 +125,24 @@ func ReadBytesForFile(filename string) ([]byte, error) {
// ReadInfoFromBytes unmarshals a file as a yaml.MapSlice.
func ReadInfoFromBytes(filename string, bytes []byte) (interface{}, error) {
initializeInfoCache()
cachedInfo, ok := infoCache[filename]
if ok {
if verboseReader {
log.Printf("Cache hit info for file %s", filename)
if infoCacheEnable {
cachedInfo, ok := infoCache[filename]
if ok {
if verboseReader {
log.Printf("Cache hit info for file %s", filename)
}
return cachedInfo, nil
}
if verboseReader {
log.Printf("Reading info for file %s", filename)
}
return cachedInfo, nil
}
if verboseReader {
log.Printf("Reading info for file %s", filename)
}
var info yaml.MapSlice
err := yaml.Unmarshal(bytes, &info)
if err != nil {
return nil, err
}
if len(filename) > 0 {
if infoCacheEnable && len(filename) > 0 {
infoCache[filename] = info
}
return info, nil
@ -119,7 +151,7 @@ func ReadInfoFromBytes(filename string, bytes []byte) (interface{}, error) {
// ReadInfoForRef reads a file and return the fragment needed to resolve a $ref.
func ReadInfoForRef(basefile string, ref string) (interface{}, error) {
initializeInfoCache()
{
if infoCacheEnable {
info, ok := infoCache[ref]
if ok {
if verboseReader {
@ -127,9 +159,9 @@ func ReadInfoForRef(basefile string, ref string) (interface{}, error) {
}
return info, nil
}
}
if verboseReader {
log.Printf("Reading info for ref %s#%s", basefile, ref)
if verboseReader {
log.Printf("Reading info for ref %s#%s", basefile, ref)
}
}
count = count + 1
basedir, _ := filepath.Split(basefile)
@ -170,6 +202,8 @@ func ReadInfoForRef(basefile string, ref string) (interface{}, error) {
}
}
}
infoCache[ref] = info
if infoCacheEnable {
infoCache[ref] = info
}
return info, nil
}

View file

@ -0,0 +1,82 @@
package compiler
import (
. "gopkg.in/check.v1"
"io"
"net/http"
"testing"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) {
TestingT(t)
}
var mockSever *http.Server
type ReaderTestingSuite struct{}
var _ = Suite(&ReaderTestingSuite{})
func (s *ReaderTestingSuite) SetUpSuite(c *C) {
mockSever = &http.Server{Addr: "127.0.0.1:8080", Handler:
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
yamlBytes, err := ReadBytesForFile("testdata/petstore.yaml")
c.Assert(err, IsNil)
io.WriteString(w, string(yamlBytes))
})}
go func() {
mockSever.ListenAndServe()
}()
}
func (s *ReaderTestingSuite) TearDownSuite(c *C) {
mockSever.Close()
}
func (s *ReaderTestingSuite) TestRemoveFromInfoCache(c *C) {
fileName := "testdata/petstore.yaml"
yamlBytes, err := ReadBytesForFile(fileName)
c.Assert(err, IsNil)
c.Assert(len(yamlBytes) > 0, Equals, true)
petstore, err := ReadInfoFromBytes(fileName, yamlBytes)
c.Assert(err, IsNil)
c.Assert(petstore, NotNil)
c.Assert(len(infoCache), Equals, 1)
RemoveFromInfoCache(fileName)
c.Assert(len(infoCache), Equals, 0)
}
func (s *ReaderTestingSuite) TestDisableInfoCache(c *C) {
fileName := "testdata/petstore.yaml"
yamlBytes, err := ReadBytesForFile(fileName)
c.Assert(err, IsNil)
c.Assert(len(yamlBytes) > 0, Equals, true)
DisableInfoCache()
petstore, err := ReadInfoFromBytes(fileName, yamlBytes)
c.Assert(err, IsNil)
c.Assert(petstore, NotNil)
c.Assert(len(infoCache), Equals, 0)
}
func (s *ReaderTestingSuite) TestRemoveFromFileCache(c *C) {
fileUrl := "http://127.0.0.1:8080/petstore"
yamlBytes, err := FetchFile(fileUrl)
c.Assert(err, IsNil)
c.Assert(len(yamlBytes) > 0, Equals, true)
c.Assert(len(fileCache), Equals, 1)
RemoveFromFileCache(fileUrl)
c.Assert(len(fileCache), Equals, 0)
}
func (s *ReaderTestingSuite) TestDisableFileCache(c *C) {
DisableFileCache()
fileUrl := "http://127.0.0.1:8080/petstore"
yamlBytes, err := FetchFile(fileUrl)
c.Assert(err, IsNil)
c.Assert(len(yamlBytes) > 0, Equals, true)
c.Assert(len(fileCache), Equals, 0)
}

View file

@ -0,0 +1,110 @@
openapi: "3.0"
info:
version: 1.0.0
title: OpenAPI Petstore
license:
name: MIT
servers:
- url: https://petstore.openapis.org/v1
description: Development server
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
"200":
description: An paged array of pets
headers:
x-next:
schema:
type: string
description: A link to the next page of responses
content:
application/json:
schema:
$ref: '#/components/schemas/Pets'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
"201":
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
"200":
description: Expected response to a valid request
content:
application/json:
schema:
$ref: '#/components/schemas/Pets'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Pet:
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: '#/components/schemas/Pet'
Error:
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string

View file

@ -0,0 +1,6 @@
swagger: "2.0"
info:
version: ""
title: ""
description: ""
paths:

View file

@ -0,0 +1,6 @@
openapi: "3.0"
info:
version:
title:
description:
paths:

View file

@ -1,24 +1,12 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: extension.proto
/*
Package openapiextension_v1 is a generated protocol buffer package.
It is generated from these files:
extension.proto
It has these top-level messages:
Version
ExtensionHandlerRequest
ExtensionHandlerResponse
Wrapper
*/
package openapiextension_v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/any"
import any "github.com/golang/protobuf/ptypes/any"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@ -33,18 +21,40 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// The version number of OpenAPI compiler.
type Version struct {
Major int32 `protobuf:"varint,1,opt,name=major" json:"major,omitempty"`
Minor int32 `protobuf:"varint,2,opt,name=minor" json:"minor,omitempty"`
Patch int32 `protobuf:"varint,3,opt,name=patch" json:"patch,omitempty"`
Major int32 `protobuf:"varint,1,opt,name=major,proto3" json:"major,omitempty"`
Minor int32 `protobuf:"varint,2,opt,name=minor,proto3" json:"minor,omitempty"`
Patch int32 `protobuf:"varint,3,opt,name=patch,proto3" json:"patch,omitempty"`
// A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should
// be empty for mainline stable releases.
Suffix string `protobuf:"bytes,4,opt,name=suffix" json:"suffix,omitempty"`
Suffix string `protobuf:"bytes,4,opt,name=suffix,proto3" json:"suffix,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Version) Reset() { *m = Version{} }
func (m *Version) String() string { return proto.CompactTextString(m) }
func (*Version) ProtoMessage() {}
func (*Version) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *Version) Reset() { *m = Version{} }
func (m *Version) String() string { return proto.CompactTextString(m) }
func (*Version) ProtoMessage() {}
func (*Version) Descriptor() ([]byte, []int) {
return fileDescriptor_extension_d25f09c742c58c90, []int{0}
}
func (m *Version) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Version.Unmarshal(m, b)
}
func (m *Version) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Version.Marshal(b, m, deterministic)
}
func (dst *Version) XXX_Merge(src proto.Message) {
xxx_messageInfo_Version.Merge(dst, src)
}
func (m *Version) XXX_Size() int {
return xxx_messageInfo_Version.Size(m)
}
func (m *Version) XXX_DiscardUnknown() {
xxx_messageInfo_Version.DiscardUnknown(m)
}
var xxx_messageInfo_Version proto.InternalMessageInfo
func (m *Version) GetMajor() int32 {
if m != nil {
@ -78,15 +88,37 @@ func (m *Version) GetSuffix() string {
type ExtensionHandlerRequest struct {
// The OpenAPI descriptions that were explicitly listed on the command line.
// The specifications will appear in the order they are specified to gnostic.
Wrapper *Wrapper `protobuf:"bytes,1,opt,name=wrapper" json:"wrapper,omitempty"`
Wrapper *Wrapper `protobuf:"bytes,1,opt,name=wrapper,proto3" json:"wrapper,omitempty"`
// The version number of openapi compiler.
CompilerVersion *Version `protobuf:"bytes,3,opt,name=compiler_version,json=compilerVersion" json:"compiler_version,omitempty"`
CompilerVersion *Version `protobuf:"bytes,3,opt,name=compiler_version,json=compilerVersion,proto3" json:"compiler_version,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ExtensionHandlerRequest) Reset() { *m = ExtensionHandlerRequest{} }
func (m *ExtensionHandlerRequest) String() string { return proto.CompactTextString(m) }
func (*ExtensionHandlerRequest) ProtoMessage() {}
func (*ExtensionHandlerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *ExtensionHandlerRequest) Reset() { *m = ExtensionHandlerRequest{} }
func (m *ExtensionHandlerRequest) String() string { return proto.CompactTextString(m) }
func (*ExtensionHandlerRequest) ProtoMessage() {}
func (*ExtensionHandlerRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_extension_d25f09c742c58c90, []int{1}
}
func (m *ExtensionHandlerRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ExtensionHandlerRequest.Unmarshal(m, b)
}
func (m *ExtensionHandlerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ExtensionHandlerRequest.Marshal(b, m, deterministic)
}
func (dst *ExtensionHandlerRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ExtensionHandlerRequest.Merge(dst, src)
}
func (m *ExtensionHandlerRequest) XXX_Size() int {
return xxx_messageInfo_ExtensionHandlerRequest.Size(m)
}
func (m *ExtensionHandlerRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ExtensionHandlerRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ExtensionHandlerRequest proto.InternalMessageInfo
func (m *ExtensionHandlerRequest) GetWrapper() *Wrapper {
if m != nil {
@ -105,7 +137,7 @@ func (m *ExtensionHandlerRequest) GetCompilerVersion() *Version {
// The extensions writes an encoded ExtensionHandlerResponse to stdout.
type ExtensionHandlerResponse struct {
// true if the extension is handled by the extension handler; false otherwise
Handled bool `protobuf:"varint,1,opt,name=handled" json:"handled,omitempty"`
Handled bool `protobuf:"varint,1,opt,name=handled,proto3" json:"handled,omitempty"`
// Error message. If non-empty, the extension handling failed.
// The extension handler process should exit with status code zero
// even if it reports an error in this way.
@ -115,15 +147,37 @@ type ExtensionHandlerResponse struct {
// itself -- such as the input Document being unparseable -- should be
// reported by writing a message to stderr and exiting with a non-zero
// status code.
Error []string `protobuf:"bytes,2,rep,name=error" json:"error,omitempty"`
Error []string `protobuf:"bytes,2,rep,name=error,proto3" json:"error,omitempty"`
// text output
Value *google_protobuf.Any `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"`
Value *any.Any `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ExtensionHandlerResponse) Reset() { *m = ExtensionHandlerResponse{} }
func (m *ExtensionHandlerResponse) String() string { return proto.CompactTextString(m) }
func (*ExtensionHandlerResponse) ProtoMessage() {}
func (*ExtensionHandlerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *ExtensionHandlerResponse) Reset() { *m = ExtensionHandlerResponse{} }
func (m *ExtensionHandlerResponse) String() string { return proto.CompactTextString(m) }
func (*ExtensionHandlerResponse) ProtoMessage() {}
func (*ExtensionHandlerResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_extension_d25f09c742c58c90, []int{2}
}
func (m *ExtensionHandlerResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ExtensionHandlerResponse.Unmarshal(m, b)
}
func (m *ExtensionHandlerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ExtensionHandlerResponse.Marshal(b, m, deterministic)
}
func (dst *ExtensionHandlerResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ExtensionHandlerResponse.Merge(dst, src)
}
func (m *ExtensionHandlerResponse) XXX_Size() int {
return xxx_messageInfo_ExtensionHandlerResponse.Size(m)
}
func (m *ExtensionHandlerResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ExtensionHandlerResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ExtensionHandlerResponse proto.InternalMessageInfo
func (m *ExtensionHandlerResponse) GetHandled() bool {
if m != nil {
@ -139,7 +193,7 @@ func (m *ExtensionHandlerResponse) GetError() []string {
return nil
}
func (m *ExtensionHandlerResponse) GetValue() *google_protobuf.Any {
func (m *ExtensionHandlerResponse) GetValue() *any.Any {
if m != nil {
return m.Value
}
@ -148,17 +202,39 @@ func (m *ExtensionHandlerResponse) GetValue() *google_protobuf.Any {
type Wrapper struct {
// version of the OpenAPI specification in which this extension was written.
Version string `protobuf:"bytes,1,opt,name=version" json:"version,omitempty"`
Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
// Name of the extension
ExtensionName string `protobuf:"bytes,2,opt,name=extension_name,json=extensionName" json:"extension_name,omitempty"`
ExtensionName string `protobuf:"bytes,2,opt,name=extension_name,json=extensionName,proto3" json:"extension_name,omitempty"`
// Must be a valid yaml for the proto
Yaml string `protobuf:"bytes,3,opt,name=yaml" json:"yaml,omitempty"`
Yaml string `protobuf:"bytes,3,opt,name=yaml,proto3" json:"yaml,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Wrapper) Reset() { *m = Wrapper{} }
func (m *Wrapper) String() string { return proto.CompactTextString(m) }
func (*Wrapper) ProtoMessage() {}
func (*Wrapper) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *Wrapper) Reset() { *m = Wrapper{} }
func (m *Wrapper) String() string { return proto.CompactTextString(m) }
func (*Wrapper) ProtoMessage() {}
func (*Wrapper) Descriptor() ([]byte, []int) {
return fileDescriptor_extension_d25f09c742c58c90, []int{3}
}
func (m *Wrapper) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Wrapper.Unmarshal(m, b)
}
func (m *Wrapper) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Wrapper.Marshal(b, m, deterministic)
}
func (dst *Wrapper) XXX_Merge(src proto.Message) {
xxx_messageInfo_Wrapper.Merge(dst, src)
}
func (m *Wrapper) XXX_Size() int {
return xxx_messageInfo_Wrapper.Size(m)
}
func (m *Wrapper) XXX_DiscardUnknown() {
xxx_messageInfo_Wrapper.DiscardUnknown(m)
}
var xxx_messageInfo_Wrapper proto.InternalMessageInfo
func (m *Wrapper) GetVersion() string {
if m != nil {
@ -188,9 +264,9 @@ func init() {
proto.RegisterType((*Wrapper)(nil), "openapiextension.v1.Wrapper")
}
func init() { proto.RegisterFile("extension.proto", fileDescriptor0) }
func init() { proto.RegisterFile("extension.proto", fileDescriptor_extension_d25f09c742c58c90) }
var fileDescriptor0 = []byte{
var fileDescriptor_extension_d25f09c742c58c90 = []byte{
// 357 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0x4d, 0x4b, 0xc3, 0x40,
0x18, 0x84, 0x49, 0xbf, 0x62, 0x56, 0x6c, 0x65, 0x2d, 0x1a, 0xc5, 0x43, 0x09, 0x08, 0x45, 0x64,

View file

@ -798,14 +798,17 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
code.Print("return nil")
} else {
code.Print("info := yaml.MapSlice{}")
code.Print("if m == nil {return info}")
for _, propertyModel := range typeModel.Properties {
isRequired := typeModel.IsRequired(propertyModel.Name)
switch propertyModel.Type {
case "string":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != \"\" {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != \"\" {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
@ -814,9 +817,10 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
case "bool":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != false {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != false {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
@ -825,9 +829,10 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
case "int":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != 0 {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
@ -836,9 +841,10 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
case "float":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != 0.0 {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != 0.0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
code.PrintIf(!isRequired, "}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName())
@ -849,7 +855,8 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
if propertyName == "value" {
code.Print("// %+v", propertyModel)
} else if !propertyModel.Repeated {
code.Print("if m.%s != nil {", propertyModel.FieldName())
code.PrintIf(isRequired, "// always include this required field.")
code.PrintIf(!isRequired, "if m.%s != nil {", propertyModel.FieldName())
if propertyModel.Type == "TypeItem" {
code.Print("if len(m.Type.Value) == 1 {")
code.Print("info = append(info, yaml.MapItem{Key:\"type\", Value:m.Type.Value[0]})")
@ -870,7 +877,7 @@ func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeNam
code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value:m.%s.ToRawInfo()})",
propertyName, propertyModel.FieldName())
}
code.Print("}")
code.PrintIf(!isRequired, "}")
code.Print("// %+v", propertyModel)
} else if propertyModel.MapType == "string" {
code.Print("// %+v", propertyModel)

View file

@ -130,3 +130,12 @@ func NewTypeModel() *TypeModel {
typeModel.Properties = make([]*TypeProperty, 0)
return typeModel
}
func (typeModel *TypeModel) IsRequired(propertyName string) bool {
for _, requiredName := range typeModel.Required {
if requiredName == propertyName {
return true
}
}
return false
}

View file

@ -186,8 +186,10 @@ func (p *pluginCall) perform(document proto.Message, sourceFormat int, sourceNam
// any logging messages are written to stderr only.
return nil, errors.New("Invalid plugin response (plugins must write log messages to stderr, not stdout).")
}
plugins.HandleResponse(response, outputLocation)
return response.Messages, nil
err = plugins.HandleResponse(response, outputLocation)
return response.Messages, err
}
return nil, nil
}

View file

@ -10,17 +10,18 @@ import (
)
func testCompiler(t *testing.T, inputFile string, referenceFile string, expectErrors bool) {
textFile := strings.Replace(filepath.Base(inputFile), filepath.Ext(inputFile), ".text", 1)
outputFormat := filepath.Ext(referenceFile)[1:]
outputFile := strings.Replace(filepath.Base(inputFile), filepath.Ext(inputFile), "."+outputFormat, 1)
errorsFile := strings.Replace(filepath.Base(inputFile), filepath.Ext(inputFile), ".errors", 1)
// remove any preexisting output files
os.Remove(textFile)
os.Remove(outputFile)
os.Remove(errorsFile)
// run the compiler
var err error
var cmd = exec.Command(
"gnostic",
inputFile,
"--text-out=.",
"--"+outputFormat+"-out=.",
"--errors-out=.",
"--resolve-refs")
//t.Log(cmd.Args)
@ -30,19 +31,19 @@ func testCompiler(t *testing.T, inputFile string, referenceFile string, expectEr
t.FailNow()
}
// verify the output against a reference
var outputFile string
var testFile string
if expectErrors {
outputFile = errorsFile
testFile = errorsFile
} else {
outputFile = textFile
testFile = outputFile
}
err = exec.Command("diff", outputFile, referenceFile).Run()
err = exec.Command("diff", testFile, referenceFile).Run()
if err != nil {
t.Logf("Diff failed: %+v", err)
t.FailNow()
} else {
// if the test succeeded, clean up
os.Remove(textFile)
os.Remove(outputFile)
os.Remove(errorsFile)
}
}
@ -451,3 +452,17 @@ func TestPetstoreJSON_30(t *testing.T) {
"examples/v3.0/json/petstore.json",
"test/v3.0/petstore.text")
}
// Test that empty required fields are exported.
func TestEmptyRequiredFields_v2(t *testing.T) {
testNormal(t,
"examples/v2.0/yaml/empty-v2.yaml",
"test/v2.0/json/empty-v2.json")
}
func TestEmptyRequiredFields_v3(t *testing.T) {
testNormal(t,
"examples/v3.0/yaml/empty-v3.yaml",
"test/v3.0/json/empty-v3.json")
}

View file

@ -105,7 +105,7 @@ When the -plugin option is specified, these flags are ignored.`)
env.Request.AddModel("openapi.v2.Document", documentv2)
// include experimental API surface model
surfaceModel, err := surface.NewModelFromOpenAPI2(documentv2)
if err != nil {
if err == nil {
env.Request.AddModel("surface.v1.Model", surfaceModel)
}
return env, err
@ -117,7 +117,7 @@ When the -plugin option is specified, these flags are ignored.`)
env.Request.AddModel("openapi.v3.Document", documentv3)
// include experimental API surface model
surfaceModel, err := surface.NewModelFromOpenAPI3(documentv3)
if err != nil {
if err == nil {
env.Request.AddModel("surface.v1.Model", surfaceModel)
}
return env, err

View file

@ -1,9 +0,0 @@
build:
go get golang.org/x/tools/cmd/goimports
go install github.com/googleapis/gnostic
go install github.com/googleapis/gnostic/plugins/gnostic-go-generator
rm -f $(GOPATH)/bin/gnostic-go-client $(GOPATH)/bin/gnostic-go-server
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-client
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-server

View file

@ -1,18 +0,0 @@
# Go Generator Plugin
This directory contains a `gnostic` plugin that can be used to generate a Go client library and scaffolding for a Go server for an API with an OpenAPI description.
The plugin can be invoked like this:
gnostic bookstore.json --go-generator-out=bookstore
`bookstore` is the name of a directory where the generated code will be written.
`bookstore` will also be the package name used for generated code.
By default, both client and server code will be generated. If the `gnostic-go-generator` binary is also linked from the names `gnostic-go-client` and `gnostic-go-server`, then only client or only server code can be generated as follows:
gnostic bookstore.json --go-client-out=bookstore
gnostic bookstore.json --go-server-out=bookstore
For example usage, see the [examples/v2.0/bookstore](examples/v2.0/bookstore) directory.

View file

@ -1,31 +0,0 @@
# googleauth
This directory contains support code that can be used to get an OAuth2 token for a Google API user.
It is designed to work on computers with attached displays.
Use it to write command-line tools and test programs that call Google APIs.
## Instructions
Import this package and make the following call to request a token.
client, err := googleauth.NewOAuth2Client(scopes)
`scopes` should be a string containing the OAuth scopes needed by the APIs to be called.
For example, the URL Shortener API would require "https://www.googleapis.com/auth/urlshortener".
This call will then open a local browser that will redirect to a Google signin page
with information about the app that is requesting a token.
## Application Credentials
To use this package, you need to download a "client secrets" file and
save it as `client_secrets.json` in the directory where your tool is run.
To get this file, visit the {{ Google Cloud Console }}{{ https://cloud.google.com/console }}
and create a project. Then go to the API Manager to enable the APIs that you want to use
and create OAuth2 credentials. You'll then be able to download these credentials
as JSON. Save this file as `client_secrets.json`
For more information about the `client_secrets.json` file format, please visit:
https://developers.google.com/api-client-library/python/guide/aaa_client_secrets

View file

@ -1,220 +0,0 @@
//
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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 googleauth
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
const missingClientSecretsMessage = `
Please configure OAuth 2.0
To make this sample run, you need to populate the client_secrets.json file
found at:
%v
with information from the {{ Google Cloud Console }}
{{ https://cloud.google.com/console }}
For more information about the client_secrets.json file format, please visit:
https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
`
var (
clientSecretsFile = flag.String("secrets", "client_secrets.json", "Client Secrets configuration")
cacheFile = flag.String("cache", "request.token", "Token cache file")
)
// ClientConfig is a data structure definition for the client_secrets.json file.
// The code unmarshals the JSON configuration file into this structure.
type ClientConfig struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURIs []string `json:"redirect_uris"`
AuthURI string `json:"auth_uri"`
TokenURI string `json:"token_uri"`
}
// Config is a root-level configuration object.
type Config struct {
Installed ClientConfig `json:"installed"`
Web ClientConfig `json:"web"`
}
// openURL opens a browser window to the specified location.
// This code originally appeared at:
// http://stackoverflow.com/questions/10377243/how-can-i-launch-a-process-that-is-not-a-file-in-go
func openURL(url string) error {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", "http://localhost:4001/").Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("Cannot open URL %s on this platform", url)
}
return err
}
// readConfig reads the configuration from clientSecretsFile.
// It returns an oauth configuration object for use with the Google API client.
func readConfig(scopes []string) (*oauth2.Config, error) {
// Read the secrets file
data, err := ioutil.ReadFile(*clientSecretsFile)
if err != nil {
pwd, _ := os.Getwd()
fullPath := filepath.Join(pwd, *clientSecretsFile)
return nil, fmt.Errorf(missingClientSecretsMessage, fullPath)
}
cfg := new(Config)
err = json.Unmarshal(data, &cfg)
if err != nil {
return nil, err
}
var redirectURI string
if len(cfg.Web.RedirectURIs) > 0 {
redirectURI = cfg.Web.RedirectURIs[0]
} else if len(cfg.Installed.RedirectURIs) > 0 {
redirectURI = cfg.Installed.RedirectURIs[0]
} else {
return nil, errors.New("Must specify a redirect URI in config file or when creating OAuth client")
}
return &oauth2.Config{
ClientID: cfg.Installed.ClientID,
ClientSecret: cfg.Installed.ClientSecret,
Scopes: scopes,
Endpoint: oauth2.Endpoint{cfg.Installed.AuthURI, cfg.Installed.TokenURI},
RedirectURL: redirectURI,
}, nil
}
// startWebServer starts a web server that listens on http://localhost:8080.
// The webserver waits for an oauth code in the three-legged auth flow.
func startWebServer() (codeCh chan string, err error) {
listener, err := net.Listen("tcp", "localhost:8080")
if err != nil {
return nil, err
}
codeCh = make(chan string)
go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
codeCh <- code // send code to OAuth flow
listener.Close()
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code)
}))
return codeCh, nil
}
// NewOAuth2Client takes the user through the three-legged OAuth flow.
// It opens a browser in the native OS or outputs a URL, then blocks until
// the redirect completes to the /oauth2callback URI.
// It returns an instance of an HTTP client that can be passed to the
// constructor of an OAuth client.
// scopes is a variable number of OAuth scopes
func NewOAuth2Client(scopes ...string) (*http.Client, error) {
var ctx context.Context
tokenSource, err := NewOAuth2TokenSource(scopes...)
if err == nil {
return oauth2.NewClient(ctx, tokenSource), nil
}
return nil, err
}
// NewOAuth2TokenSource takes the user through the three-legged OAuth flow.
// It opens a browser in the native OS or outputs a URL, then blocks until
// the redirect completes to the /oauth2callback URI.
// It returns an instance of an OAuth token source that can be passed to the
// constructor of an OAuth client.
// scopes is a variable number of OAuth scopes
func NewOAuth2TokenSource(scopes ...string) (oauth2.TokenSource, error) {
config, err := readConfig(scopes)
if err != nil {
msg := fmt.Sprintf("Cannot read configuration file: %v", err)
return nil, errors.New(msg)
}
var ctx context.Context
// Try to read the token from the cache file.
// If an error occurs, do the three-legged OAuth flow because
// the token is invalid or doesn't exist.
//token, err := config.TokenCache.Token()
var token *oauth2.Token
data, err := ioutil.ReadFile(*cacheFile)
if err == nil {
err = json.Unmarshal(data, &token)
}
if (err != nil) || !token.Valid() {
// Start web server.
// This is how this program receives the authorization code
// when the browser redirects.
codeCh, err := startWebServer()
if err != nil {
return nil, err
}
// Open url in browser
url := config.AuthCodeURL("")
err = openURL(url)
if err != nil {
fmt.Println("Visit the URL below to get a code.",
" This program will pause until the site is visted.")
} else {
fmt.Println("Your browser has been opened to an authorization URL.",
" This program will resume once authorization has been provided.\n")
}
fmt.Println(url)
// Wait for the web server to get the code.
code := <-codeCh
// This code caches the authorization code on the local
// filesystem, if necessary, as long as the TokenCache
// attribute in the config is set.
token, err = config.Exchange(ctx, code)
if err != nil {
return nil, err
}
data, err := json.Marshal(token)
ioutil.WriteFile(*cacheFile, data, 0644)
}
return oauth2.StaticTokenSource(token), nil
}

View file

@ -1,4 +0,0 @@
all:
gnostic swagger.yaml --go-client-out=apis_guru
go install

View file

@ -1,45 +0,0 @@
// +build ignore
// This file is omitted when getting with `go get github.com/googleapis/gnostic/...`
package main
import (
"fmt"
"sort"
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/apis_guru/apis_guru"
)
func main() {
c := apis_guru.NewClient("http://api.apis.guru/v2")
metrics, err := c.GetMetrics()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", metrics)
apis, err := c.ListAPIs()
if err != nil {
panic(err)
}
keys := make([]string, 0)
for key, _ := range *apis.OK {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
api := (*apis.OK)[key]
versions := make([]string, 0)
for key, _ := range api.Versions {
versions = append(versions, key)
}
sort.Strings(versions)
fmt.Printf("[%s]:%+v\n", key, versions)
}
api := (*apis.OK)["xkcd.com"].Versions["1.0.0"]
fmt.Printf("%+v\n", api.SwaggerUrl)
}

View file

@ -1,186 +0,0 @@
swagger: '2.0'
schemes:
- https
host: api.apis.guru
basePath: /v2/
info:
contact:
email: founders@apis.guru
name: APIs.guru
url: 'http://APIs.guru'
description: |
Wikipedia for Web APIs. Repository of API specs in OpenAPI(fka Swagger) 2.0 format.
**Warning**: If you want to be notified about changes in advance please subscribe to our [Gitter channel](https://gitter.im/APIs-guru/api-models).
Client sample: [[Demo]](https://apis.guru/simple-ui) [[Repo]](https://github.com/APIs-guru/simple-ui)
license:
name: CC0 1.0
url: 'https://github.com/APIs-guru/api-models#licenses'
title: APIs.guru
version: '2.0'
x-logo:
url: 'https://apis.guru/branding/logo_vertical.svg'
externalDocs:
url: 'https://github.com/APIs-guru/api-models/blob/master/API.md'
produces:
- application/json
security: []
paths:
/list.json:
get:
description: |
List all APIs in the directory.
Returns links to OpenAPI specification for each API in the directory.
If API exist in multiply versions `preferred` one is explicitly marked.
Some basic info from OpenAPI spec is cached inside each object.
This allows to generate some simple views without need to fetch OpenAPI spec for each API.
operationId: listAPIs
responses:
'200':
description: OK
schema:
$ref: '#/definitions/APIs'
summary: List all APIs
/metrics.json:
get:
description: |
Some basic metrics for the entire directory.
Just stunning numbers to put on a front page and are intended purely for WoW effect :)
operationId: getMetrics
responses:
'200':
description: OK
schema:
$ref: '#/definitions/Metrics'
summary: Get basic metrics
definitions:
API:
additionalProperties: false
description: Meta information about API
properties:
added:
description: Timestamp when the API was first added to the directory
format: date-time
type: string
preferred:
description: Recommended version
type: string
versions:
additionalProperties:
$ref: '#/definitions/ApiVersion'
description: List of supported versions of the API
minProperties: 1
type: object
required:
- added
- preferred
- versions
type: object
APIs:
additionalProperties:
$ref: '#/definitions/API'
description: |
List of API details.
It is a JSON object with API IDs(`<provider>[:<service>]`) as keys.
example:
'googleapis.com:drive':
added: '2015-02-22T20:00:45.000Z'
preferred: v3
versions:
v2:
added: '2015-02-22T20:00:45.000Z'
info:
title: Drive
version: v2
x-apiClientRegistration:
url: 'https://console.developers.google.com'
x-logo:
url: 'https://api.apis.guru/v2/cache/logo/https_www.gstatic.com_images_icons_material_product_2x_drive_32dp.png'
x-origin:
format: google
url: 'https://www.googleapis.com/discovery/v1/apis/drive/v2/rest'
version: v1
x-preferred: false
x-providerName: googleapis.com
x-serviceName: drive
swaggerUrl: 'https://api.apis.guru/v2/specs/googleapis.com/drive/v2/swagger.json'
swaggerYamlUrl: 'https://api.apis.guru/v2/specs/googleapis.com/drive/v2/swagger.yaml'
updated: '2016-06-17T00:21:44.000Z'
v3:
added: '2015-12-12T00:25:13.000Z'
info:
title: Drive
version: v3
x-apiClientRegistration:
url: 'https://console.developers.google.com'
x-logo:
url: 'https://api.apis.guru/v2/cache/logo/https_www.gstatic.com_images_icons_material_product_2x_drive_32dp.png'
x-origin:
format: google
url: 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'
version: v1
x-preferred: true
x-providerName: googleapis.com
x-serviceName: drive
swaggerUrl: 'https://api.apis.guru/v2/specs/googleapis.com/drive/v3/swagger.json'
swaggerYamlUrl: 'https://api.apis.guru/v2/specs/googleapis.com/drive/v3/swagger.yaml'
updated: '2016-06-17T00:21:44.000Z'
minProperties: 1
type: object
ApiVersion:
additionalProperties: false
properties:
added:
description: Timestamp when the version was added
format: date-time
type: string
info:
description: Copy of `info` section from Swagger spec
minProperties: 1
type: object
swaggerUrl:
description: URL to Swagger spec in JSON format
format: url
type: string
swaggerYamlUrl:
description: URL to Swagger spec in YAML format
format: url
type: string
updated:
description: Timestamp when the version was updated
format: date-time
type: string
required:
- added
- updated
- swaggerUrl
- swaggerYamlUrl
- info
type: object
Metrics:
additionalProperties: false
description: List of basic metrics
example:
numAPIs: 238
numEndpoints: 6448
numSpecs: 302
properties:
numAPIs:
description: Number of APIs
minimum: 1
type: integer
numEndpoints:
description: Total number of endpoints inside all specifications
minimum: 1
type: integer
numSpecs:
description: Number of API specifications including different versions of the same API
minimum: 1
type: integer
required:
- numSpecs
- numAPIs
- numEndpoints
type: object

View file

@ -1,20 +0,0 @@
build:
go get golang.org/x/tools/cmd/goimports
go install github.com/googleapis/gnostic
go install github.com/googleapis/gnostic/plugins/gnostic-go-generator
rm -f $(GOPATH)/bin/gnostic-go-client $(GOPATH)/bin/gnostic-go-server
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-client
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-server
all: build
gnostic bookstore.json --go-generator-out=bookstore
clean:
rm -rf bookstore bookstore.text service/service
test: all
killall service; true # ignore errors due to no matching processes
cd service; go get .; go build; ./service &
go test
killall service

View file

@ -1,23 +0,0 @@
# Bookstore Example
This directory contains an OpenAPI description of a simple bookstore API.
Use this example to try the `gnostic-go-generator` plugin, which implements
`gnostic-go-client` and `gnostic-go-server` for generating API client and
server code, respectively.
Run "make all" to build and install `gnostic` and the Go plugins.
It will generate both client and server code. The API client and
server code will be in the `bookstore` package.
The `service` directory contains additional code that completes the server.
To build and run the service, `cd service` and do the following:
go get .
go build
./service &
To test the service with the generated client, go back up to the top-level
directory and run `go test`. The test in `bookstore_test.go` uses client
code generated in `bookstore` to verify the service.

View file

@ -1,357 +0,0 @@
{
"swagger": "2.0",
"info": {
"description": "A simple Bookstore API example.",
"title": "Bookstore",
"version": "1.0.0"
},
"host": "generated-bookstore.appspot.com",
"basePath": "/",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"schemes": [
"https"
],
"paths": {
"/shelves": {
"get": {
"description": "Return all shelves in the bookstore.",
"operationId": "listShelves",
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "List of shelves in the bookstore.",
"schema": {
"$ref": "#/definitions/listShelvesResponse"
}
}
},
"security": [
]
},
"post": {
"description": "Create a new shelf in the bookstore.",
"operationId": "createShelf",
"parameters": [
{
"description": "A shelf resource to create.",
"in": "body",
"name": "shelf",
"required": true,
"schema": {
"$ref": "#/definitions/shelf"
}
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "A newly created shelf resource.",
"schema": {
"$ref": "#/definitions/shelf"
}
}
}
},
"delete": {
"description": "Delete all shelves.",
"operationId": "deleteShelves",
"responses": {
"default": {
"description": "An empty response body."
}
}
}
},
"/shelves/{shelf}": {
"get": {
"description": "Get a single shelf resource with the given ID.",
"operationId": "getShelf",
"parameters": [
{
"description": "ID of the shelf to get.",
"format": "int64",
"in": "path",
"name": "shelf",
"required": true,
"type": "integer"
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "A shelf resource.",
"schema": {
"$ref": "#/definitions/shelf"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/error"
}
}
}
},
"delete": {
"description": "Delete a single shelf with the given ID.",
"operationId": "deleteShelf",
"parameters": [
{
"description": "ID of the shelf to delete.",
"format": "int64",
"in": "path",
"name": "shelf",
"required": true,
"type": "integer"
}
],
"responses": {
"default": {
"description": "An empty response body."
}
}
}
},
"/shelves/{shelf}/books": {
"get": {
"description": "Return all books in a shelf with the given ID.",
"operationId": "listBooks",
"parameters": [
{
"description": "ID of the shelf whose books should be returned.",
"format": "int64",
"in": "path",
"name": "shelf",
"required": true,
"type": "integer"
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "List of books on the specified shelf.",
"schema": {
"$ref": "#/definitions/listBooksResponse"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/error"
}
}
}
},
"post": {
"description": "Create a new book on the shelf.",
"operationId": "createBook",
"parameters": [
{
"description": "ID of the shelf where the book should be created.",
"format": "int64",
"in": "path",
"name": "shelf",
"required": true,
"type": "integer"
},
{
"description": "Book to create.",
"in": "body",
"name": "book",
"required": true,
"schema": {
"$ref": "#/definitions/book"
}
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "A newly created book resource.",
"schema": {
"$ref": "#/definitions/book"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/shelves/{shelf}/books/{book}": {
"get": {
"description": "Get a single book with a given ID from a shelf.",
"operationId": "getBook",
"parameters": [
{
"description": "ID of the shelf from which to get the book.",
"format": "int64",
"in": "path",
"name": "shelf",
"required": true,
"type": "integer"
},
{
"description": "ID of the book to get from the shelf.",
"format": "int64",
"in": "path",
"name": "book",
"required": true,
"type": "integer"
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "A book resource.",
"schema": {
"$ref": "#/definitions/book"
}
},
"default": {
"description": "unexpected error",
"schema": {
"$ref": "#/definitions/error"
}
}
}
},
"delete": {
"description": "Delete a single book with a given ID from a shelf.",
"operationId": "deleteBook",
"parameters": [
{
"description": "ID of the shelf from which to delete the book.",
"format": "int64",
"in": "path",
"name": "shelf",
"required": true,
"type": "integer"
},
{
"description": "ID of the book to delete from the shelf.",
"format": "int64",
"in": "path",
"name": "book",
"required": true,
"type": "integer"
}
],
"responses": {
"default": {
"description": "An empty response body."
}
}
}
}
},
"definitions": {
"book": {
"properties": {
"author": {
"type": "string"
},
"name": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"name",
"author",
"title"
]
},
"listBooksResponse": {
"properties": {
"books": {
"items": {
"$ref": "#/definitions/book"
},
"type": "array"
}
},
"required": [
"books"
],
"type": "object"
},
"listShelvesResponse": {
"properties": {
"shelves": {
"items": {
"$ref": "#/definitions/shelf"
},
"type": "array"
}
},
"type": "object"
},
"shelf": {
"properties": {
"name": {
"type": "string"
},
"theme": {
"type": "string"
}
},
"required": [
"name",
"theme"
]
},
"error": {
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
}
}
},
"security": [
{
"api_key": [
]
}
],
"securityDefinitions": {
"api_key": {
"in": "query",
"name": "key",
"type": "apiKey"
}
}
}

View file

@ -1,19 +0,0 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 bookstore exists to allow this repo to work with recursive go get.
// It will be filled in with auto generated code.
package bookstore

View file

@ -1,239 +0,0 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 test
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/bookstore/bookstore"
)
const service = "http://localhost:8080"
//const service = "http://generated-bookstore.appspot.com"
func TestBookstore(t *testing.T) {
// create a client
b := bookstore.NewClient(service, nil)
// reset the service by deleting all shelves
{
err := b.DeleteShelves()
if err != nil {
t.Log("delete shelves failed")
t.Fail()
}
}
// verify that the service has no shelves
{
response, err := b.ListShelves()
if err != nil {
t.Log("list shelves failed")
t.Fail()
}
if (response == nil) || (response.OK == nil) || (response.OK.Shelves != nil) {
t.Log(fmt.Sprintf("list shelves failed %+v", response.OK))
t.Log(fmt.Sprintf("list shelves failed len=%d", len(response.OK.Shelves)))
t.Fail()
}
}
// attempting to get a shelf should return an error
{
_, err := b.GetShelf(1)
if err == nil {
t.Log("get shelf failed to return an error")
t.Fail()
}
}
// attempting to get a book should return an error
{
_, err := b.GetBook(1, 2)
if err == nil {
t.Log("get book failed to return an error")
t.Fail()
}
}
// add a shelf
{
var shelf bookstore.Shelf
shelf.Theme = "mysteries"
response, err := b.CreateShelf(shelf)
if err != nil {
t.Log("create shelf mysteries failed")
t.Fail()
}
if (response.OK.Name != "shelves/1") ||
(response.OK.Theme != "mysteries") {
t.Log("create shelf mysteries failed")
t.Fail()
}
}
// add another shelf
{
var shelf bookstore.Shelf
shelf.Theme = "comedies"
response, err := b.CreateShelf(shelf)
if err != nil {
t.Log("create shelf comedies failed")
t.Fail()
}
if (response.OK.Name != "shelves/2") ||
(response.OK.Theme != "comedies") {
t.Log("create shelf comedies failed")
t.Fail()
}
}
// get the first shelf that was added
{
response, err := b.GetShelf(1)
if err != nil {
t.Log("get shelf mysteries failed")
t.Fail()
}
if (response.OK.Name != "shelves/1") ||
(response.OK.Theme != "mysteries") {
t.Log("get shelf mysteries failed")
t.Fail()
}
}
// list shelves and verify that there are 2
{
response, err := b.ListShelves()
if err != nil {
t.Log("list shelves failed")
t.Fail()
}
if len(response.OK.Shelves) != 2 {
t.Log("list shelves failed")
t.Fail()
}
}
// delete a shelf
{
err := b.DeleteShelf(2)
if err != nil {
t.Log("delete shelf failed")
t.Fail()
}
}
// list shelves and verify that there is only 1
{
response, err := b.ListShelves()
if err != nil {
t.Log("list shelves failed")
t.Fail()
}
if len(response.OK.Shelves) != 1 {
t.Log("list shelves failed")
t.Fail()
}
}
// list books on a shelf, verify that there are none
{
response, err := b.ListBooks(1)
if err != nil {
t.Log("list books failed")
t.Fail()
}
if len(response.OK.Books) != 0 {
t.Log("list books failed")
t.Fail()
}
}
// create a book
{
var book bookstore.Book
book.Author = "Agatha Christie"
book.Title = "And Then There Were None"
_, err := b.CreateBook(1, book)
if err != nil {
t.Log("create book failed")
t.Fail()
}
}
// create another book
{
var book bookstore.Book
book.Author = "Agatha Christie"
book.Title = "Murder on the Orient Express"
_, err := b.CreateBook(1, book)
if err != nil {
t.Log("create book failed")
t.Fail()
}
}
// get the first book that was added
{
_, err := b.GetBook(1, 1)
if err != nil {
t.Log("get book failed")
t.Fail()
}
}
// list the books on a shelf and verify that there are 2
{
response, err := b.ListBooks(1)
if err != nil {
t.Log("list books failed")
t.Fail()
}
if len(response.OK.Books) != 2 {
t.Log("list books failed")
t.Fail()
}
}
// delete a book
{
err := b.DeleteBook(1, 2)
if err != nil {
t.Log("delete book failed")
t.Fail()
}
}
// list the books on a shelf and verify that is only 1
{
response, err := b.ListBooks(1)
if err != nil {
t.Log("list books failed")
t.Fail()
}
if len(response.OK.Books) != 1 {
t.Log("list books failed")
t.Fail()
}
}
// verify the handling of a badly-formed request
{
req, err := http.NewRequest("POST", service+"/shelves", strings.NewReader(""))
if err != nil {
t.Log("bad request failed")
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
// we expect a 400 (Bad Request) code
if resp.StatusCode != 400 {
t.Log("bad request failed")
t.Fail()
}
return
}
}

View file

@ -1,9 +0,0 @@
application: bookstore
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
- url: /
static_dir: static

View file

@ -1,27 +0,0 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 main
import (
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/bookstore/bookstore"
)
// init() is called when the package is loaded
// this allows this app to be trivially deployed to Google App Engine, which does not call main()
func init() {
bookstore.Initialize(NewService())
}

View file

@ -1,34 +0,0 @@
// +build !appengine
// This file is omitted when the app is built for Google App Engine
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 main
import (
"log"
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/bookstore/bookstore"
)
func main() {
err := bookstore.ServeHTTP(":8080")
if err != nil {
log.Printf("%v", err)
}
}

View file

@ -1,195 +0,0 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 main
import (
"errors"
"fmt"
"net/http"
"sync"
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/bookstore/bookstore"
)
//
// The Service type implements a bookstore service.
// All objects are managed in an in-memory non-persistent store.
//
type Service struct {
// shelves are stored in a map keyed by shelf id
// books are stored in a two level map, keyed first by shelf id and then by book id
Shelves map[int64]*bookstore.Shelf
Books map[int64]map[int64]*bookstore.Book
LastShelfID int64 // the id of the last shelf that was added
LastBookID int64 // the id of the last book that was added
Mutex sync.Mutex // global mutex to synchronize service access
}
func NewService() *Service {
return &Service{
Shelves: make(map[int64]*bookstore.Shelf),
Books: make(map[int64]map[int64]*bookstore.Book),
}
}
func (service *Service) ListShelves(responses *bookstore.ListShelvesResponses) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// copy shelf ids from Shelves map keys
shelves := make([]bookstore.Shelf, 0, len(service.Shelves))
for _, shelf := range service.Shelves {
shelves = append(shelves, *shelf)
}
response := &bookstore.ListShelvesResponse{}
response.Shelves = shelves
(*responses).OK = response
return err
}
func (service *Service) CreateShelf(parameters *bookstore.CreateShelfParameters, responses *bookstore.CreateShelfResponses) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// assign an id and name to a shelf and add it to the Shelves map.
shelf := parameters.Shelf
service.LastShelfID++
sid := service.LastShelfID
shelf.Name = fmt.Sprintf("shelves/%d", sid)
service.Shelves[sid] = &shelf
(*responses).OK = &shelf
return err
}
func (service *Service) DeleteShelves() (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// delete everything by reinitializing the Shelves and Books maps.
service.Shelves = make(map[int64]*bookstore.Shelf)
service.Books = make(map[int64]map[int64]*bookstore.Book)
service.LastShelfID = 0
service.LastBookID = 0
return nil
}
func (service *Service) GetShelf(parameters *bookstore.GetShelfParameters, responses *bookstore.GetShelfResponses) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// look up a shelf from the Shelves map.
shelf, err := service.getShelf(parameters.Shelf)
if err != nil {
(*responses).Default = &bookstore.Error{Code: int32(http.StatusNotFound), Message: err.Error()}
return nil
} else {
(*responses).OK = shelf
return nil
}
}
func (service *Service) DeleteShelf(parameters *bookstore.DeleteShelfParameters) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// delete a shelf by removing the shelf from the Shelves map and the associated books from the Books map.
delete(service.Shelves, parameters.Shelf)
delete(service.Books, parameters.Shelf)
return nil
}
func (service *Service) ListBooks(parameters *bookstore.ListBooksParameters, responses *bookstore.ListBooksResponses) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// list the books in a shelf
_, err = service.getShelf(parameters.Shelf)
if err != nil {
(*responses).Default = &bookstore.Error{Code: int32(http.StatusNotFound), Message: err.Error()}
return nil
}
shelfBooks := service.Books[parameters.Shelf]
books := make([]bookstore.Book, 0, len(shelfBooks))
for _, book := range shelfBooks {
books = append(books, *book)
}
response := &bookstore.ListBooksResponse{}
response.Books = books
(*responses).OK = response
return nil
}
func (service *Service) CreateBook(parameters *bookstore.CreateBookParameters, responses *bookstore.CreateBookResponses) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// return "not found" if the shelf doesn't exist
shelf, err := service.getShelf(parameters.Shelf)
if err != nil {
(*responses).Default = &bookstore.Error{Code: int32(http.StatusNotFound), Message: err.Error()}
return nil
}
// assign an id and name to a book and add it to the Books map.
service.LastBookID++
bid := service.LastBookID
book := parameters.Book
book.Name = fmt.Sprintf("%s/books/%d", shelf.Name, bid)
if service.Books[parameters.Shelf] == nil {
service.Books[parameters.Shelf] = make(map[int64]*bookstore.Book)
}
service.Books[parameters.Shelf][bid] = &book
(*responses).OK = &book
return err
}
func (service *Service) GetBook(parameters *bookstore.GetBookParameters, responses *bookstore.GetBookResponses) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// get a book from the Books map
book, err := service.getBook(parameters.Shelf, parameters.Book)
if err != nil {
(*responses).Default = &bookstore.Error{Code: int32(http.StatusNotFound), Message: err.Error()}
} else {
(*responses).OK = book
}
return nil
}
func (service *Service) DeleteBook(parameters *bookstore.DeleteBookParameters) (err error) {
service.Mutex.Lock()
defer service.Mutex.Unlock()
// delete a book by removing the book from the Books map.
delete(service.Books[parameters.Shelf], parameters.Book)
return nil
}
// internal helpers
func (service *Service) getShelf(sid int64) (shelf *bookstore.Shelf, err error) {
shelf, ok := service.Shelves[sid]
if !ok {
return nil, errors.New(fmt.Sprintf("Couldn't find shelf %d", sid))
} else {
return shelf, nil
}
}
func (service *Service) getBook(sid int64, bid int64) (book *bookstore.Book, err error) {
_, err = service.getShelf(sid)
if err != nil {
return nil, err
}
book, ok := service.Books[sid][bid]
if !ok {
return nil, errors.New(fmt.Sprintf("Couldn't find book %d on shelf %d", bid, sid))
} else {
return book, nil
}
}

View file

@ -1,20 +0,0 @@
build:
go get golang.org/x/tools/cmd/goimports
go install github.com/googleapis/gnostic
go install github.com/googleapis/gnostic/plugins/gnostic-go-generator
rm -f $(GOPATH)/bin/gnostic-go-client $(GOPATH)/bin/gnostic-go-server
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-client
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-server
all: build
gnostic sample.yaml --go-generator-out=sample
clean:
rm -rf sample service/service
test: all
killall service; true # ignore errors due to no matching processes
cd service; go get .; go build; ./service &
go test
killall service

View file

@ -1,24 +0,0 @@
# API Sample
This directory contains an OpenAPI description of a sample API
that exercises various OpenAPI features.
Use this example to try the `gnostic-go-generator` plugin, which implements
`gnostic-go-client` and `gnostic-go-server` for generating API client and
server code, respectively.
Run "make all" to build and install `gnostic` and the Go plugins.
It will generate both client and server code. The API client and
server code will be in the `sample` package.
The `service` directory contains additional code that completes the server.
To build and run the service, `cd service` and do the following:
go get .
go build
./service &
To test the service with the generated client, go back up to the top-level
directory and run `go test`. The test in `sample_test.go` uses client
code generated in `sample` to verify the service.

View file

@ -1,67 +0,0 @@
swagger: '2.0'
schemes:
- https
host: sample.io
basePath: /
info:
title: sample.io
version: '1.0'
consumes:
- application/json
produces:
- application/json;charset=UTF-8
securityDefinitions:
api_key:
in: query
name: key
type: apiKey
paths:
/sample/{id}:
get:
operationId: "GetSample"
parameters:
- description: identifier
in: path
name: id
required: true
type: string
responses:
'200':
description: sample response
schema:
$ref: '#/definitions/Sample'
'401':
description: User doesn't have a valid session.
schema:
$ref: '#/definitions/APIError'
'404':
description: Unable to find supplied extractor ID.
schema:
$ref: '#/definitions/APIError'
security:
- api_key: []
summary: Get a sample response
tags:
- sample
- demo
definitions:
APIError:
properties:
code:
description: Internal error code
format: int
type: integer
message:
description: A message containing a brief description of the error
type: string
type: object
Sample:
properties:
id:
type: string
thing:
type: object
count:
format: int32
type: integer
type: object

View file

@ -1,19 +0,0 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 sample exists to allow this repo to work with recursive go get.
// It will be filled in with auto generated code.
package sample

View file

@ -1,68 +0,0 @@
/*
Copyright 2018 Google Inc. All Rights Reserved.
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 test
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/sample/sample"
)
const service = "http://localhost:8080"
func TestSample(t *testing.T) {
// create a client
s := sample.NewClient(service, nil)
// verify a sample request
{
message := "hello world"
response, err := s.GetSample(message)
if err != nil {
t.Log("get sample failed")
t.Fail()
}
if response.OK.Id != message || response.OK.Count != int32(len(message)) {
t.Log(fmt.Sprintf("get sample received %+v", response.OK))
t.Fail()
}
if (response == nil) || (response.OK == nil) {
t.Log(fmt.Sprintf("get sample failed %+v", response.OK))
t.Fail()
}
}
// verify the handling of an invalid request
{
req, err := http.NewRequest("GET", service+"/unsupported", strings.NewReader(""))
if err != nil {
t.Log("bad request failed")
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
// we expect a 404 (Not Found) code
if resp.StatusCode != 404 {
t.Log("bad request failed")
t.Fail()
}
return
}
}

View file

@ -1,9 +0,0 @@
application: sample
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
- url: /
static_dir: static

View file

@ -1,27 +0,0 @@
/*
Copyright 2018 Google Inc. All Rights Reserved.
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 main
import (
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/sample/sample"
)
// init() is called when the package is loaded
// this allows this app to be trivially deployed to Google App Engine, which does not call main()
func init() {
sample.Initialize(NewService())
}

View file

@ -1,34 +0,0 @@
// +build !appengine
// This file is omitted when the app is built for Google App Engine
/*
Copyright 2018 Google Inc. All Rights Reserved.
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 main
import (
"log"
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/sample/sample"
)
func main() {
err := sample.ServeHTTP(":8080")
if err != nil {
log.Printf("%v", err)
}
}

View file

@ -1,38 +0,0 @@
/*
Copyright 2018 Google Inc. All Rights Reserved.
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 main
import (
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/sample/sample"
)
//
// The Service type implements a sample service.
//
type Service struct{}
func NewService() *Service {
return &Service{}
}
func (service *Service) GetSample(parameters *sample.GetSampleParameters, responses *sample.GetSampleResponses) (err error) {
(*responses).OK = &sample.Sample{
Id: parameters.Id,
Thing: map[string]interface{}{"thing": 123},
Count: int32(len(parameters.Id))}
return err
}

View file

@ -1,3 +0,0 @@
all:
gnostic swagger.json --go-client-out=xkcd
go install

View file

@ -1,23 +0,0 @@
package main
import (
"fmt"
"github.com/googleapis/gnostic/plugins/gnostic-go-generator/examples/v2.0/xkcd/xkcd"
)
func main() {
c := xkcd.NewClient("http://xkcd.com")
comic, err := c.Get_info_0_json()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", comic)
comic, err = c.Get_comicId_info_0_json(1800)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", comic)
}

View file

@ -1,111 +0,0 @@
{
"swagger": "2.0",
"schemes": [
"http"
],
"host": "xkcd.com",
"basePath": "/",
"info": {
"description": "Webcomic of romance, sarcasm, math, and language.",
"title": "XKCD",
"version": "1.0.0",
"x-apisguru-categories": [
"media"
],
"x-logo": {
"url": "https://api.apis.guru/v2/cache/logo/http_imgs.xkcd.com_static_terrible_small_logo.png"
},
"x-origin": {
"format": "swagger",
"url": "https://raw.githubusercontent.com/APIs-guru/unofficial_openapi_specs/master/xkcd.com/1.0.0/swagger.yaml",
"version": "2.0"
},
"x-preferred": true,
"x-providerName": "xkcd.com",
"x-tags": [
"humor",
"comics"
],
"x-unofficialSpec": true
},
"externalDocs": {
"url": "https://xkcd.com/json.html"
},
"securityDefinitions": {},
"paths": {
"/info.0.json": {
"get": {
"description": "Fetch current comic and metadata.\n",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/comic"
}
}
}
}
},
"/{comicId}/info.0.json": {
"get": {
"description": "Fetch comics and metadata by comic id.\n",
"parameters": [
{
"in": "path",
"name": "comicId",
"required": true,
"type": "number"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/comic"
}
}
}
}
}
},
"definitions": {
"comic": {
"properties": {
"alt": {
"type": "string"
},
"day": {
"type": "string"
},
"img": {
"type": "string"
},
"link": {
"type": "string"
},
"month": {
"type": "string"
},
"news": {
"type": "string"
},
"num": {
"type": "number"
},
"safe_title": {
"type": "string"
},
"title": {
"type": "string"
},
"transcript": {
"type": "string"
},
"year": {
"type": "string"
}
},
"type": "object"
}
}
}

View file

@ -1,19 +0,0 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 xkcd exists to allow this repo to work with recursive go get.
// It will be filled in with auto generated code.
package xkcd

View file

@ -1,20 +0,0 @@
build:
go get golang.org/x/tools/cmd/goimports
go install github.com/googleapis/gnostic
go install github.com/googleapis/gnostic/plugins/gnostic-go-generator
rm -f $(GOPATH)/bin/gnostic-go-client $(GOPATH)/bin/gnostic-go-server
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-client
ln -s $(GOPATH)/bin/gnostic-go-generator $(GOPATH)/bin/gnostic-go-server
all: build
gnostic bookstore.json --go-generator-out=bookstore
clean:
rm -rf bookstore bookstore.text service/service
test: all
killall service; true # ignore errors due to no matching processes
cd service; go get .; go build; ./service &
go test
killall service

View file

@ -1,23 +0,0 @@
# Bookstore Example
This directory contains an OpenAPI description of a simple bookstore API.
Use this example to try the `gnostic-go-generator` plugin, which implements
`gnostic-go-client` and `gnostic-go-server` for generating API client and
server code, respectively.
Run "make all" to build and install `gnostic` and the Go plugins.
It will generate both client and server code. The API client and
server code will be in the `bookstore` package.
The `service` directory contains additional code that completes the server.
To build and run the service, `cd service` and do the following:
go get .
go build
./service &
To test the service with the generated client, go back up to the top-level
directory and run `go test`. The test in `bookstore_test.go` uses client
code generated in `bookstore` to verify the service.

View file

@ -1,392 +0,0 @@
{
"openapi": "3.0.0",
"servers": [
{
"url": "https://generated-bookstore.appspot.com/"
}
],
"info": {
"description": "A simple Bookstore API example.",
"title": "Bookstore",
"version": "1.0.0"
},
"paths": {
"/shelves": {
"get": {
"description": "Return all shelves in the bookstore.",
"operationId": "listShelves",
"responses": {
"200": {
"description": "List of shelves in the bookstore.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/listShelvesResponse"
}
}
}
}
},
"security": []
},
"post": {
"description": "Create a new shelf in the bookstore.",
"operationId": "createShelf",
"responses": {
"200": {
"description": "A newly created shelf resource.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/shelf"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/shelf"
}
}
},
"description": "A shelf resource to create.",
"required": true
}
},
"delete": {
"description": "Delete all shelves.",
"operationId": "deleteShelves",
"responses": {
"default": {
"description": "An empty response body."
}
}
}
},
"/shelves/{shelf}": {
"get": {
"description": "Get a single shelf resource with the given ID.",
"operationId": "getShelf",
"parameters": [
{
"description": "ID of the shelf to get.",
"in": "path",
"name": "shelf",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "A shelf resource.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/shelf"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
}
},
"delete": {
"description": "Delete a single shelf with the given ID.",
"operationId": "deleteShelf",
"parameters": [
{
"description": "ID of the shelf to delete.",
"in": "path",
"name": "shelf",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"default": {
"description": "An empty response body."
}
}
}
},
"/shelves/{shelf}/books": {
"get": {
"description": "Return all books in a shelf with the given ID.",
"operationId": "listBooks",
"parameters": [
{
"description": "ID of the shelf whose books should be returned.",
"in": "path",
"name": "shelf",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "List of books on the specified shelf.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/listBooksResponse"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
}
},
"post": {
"description": "Create a new book on the shelf.",
"operationId": "createBook",
"parameters": [
{
"description": "ID of the shelf where the book should be created.",
"in": "path",
"name": "shelf",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "A newly created book resource.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/book"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/book"
}
}
},
"description": "Book to create.",
"required": true
}
}
},
"/shelves/{shelf}/books/{book}": {
"get": {
"description": "Get a single book with a given ID from a shelf.",
"operationId": "getBook",
"parameters": [
{
"description": "ID of the shelf from which to get the book.",
"in": "path",
"name": "shelf",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
},
{
"description": "ID of the book to get from the shelf.",
"in": "path",
"name": "book",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"200": {
"description": "A book resource.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/book"
}
}
}
},
"default": {
"description": "unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error"
}
}
}
}
}
},
"delete": {
"description": "Delete a single book with a given ID from a shelf.",
"operationId": "deleteBook",
"parameters": [
{
"description": "ID of the shelf from which to delete the book.",
"in": "path",
"name": "shelf",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
},
{
"description": "ID of the book to delete from the shelf.",
"in": "path",
"name": "book",
"required": true,
"schema": {
"type": "integer",
"format": "int64"
}
}
],
"responses": {
"default": {
"description": "An empty response body."
}
}
}
}
},
"security": [
{
"api_key": []
}
],
"components": {
"schemas": {
"book": {
"properties": {
"author": {
"type": "string"
},
"name": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"name",
"author",
"title"
],
"type": "object"
},
"listBooksResponse": {
"properties": {
"books": {
"items": {
"$ref": "#/components/schemas/book"
},
"type": "array"
}
},
"required": [
"books"
],
"type": "object"
},
"listShelvesResponse": {
"properties": {
"shelves": {
"items": {
"$ref": "#/components/schemas/shelf"
},
"type": "array"
}
},
"type": "object"
},
"shelf": {
"properties": {
"name": {
"type": "string"
},
"theme": {
"type": "string"
}
},
"required": [
"name",
"theme"
],
"type": "object"
},
"error": {
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
}
},
"type": "object"
}
},
"securitySchemes": {
"api_key": {
"in": "query",
"name": "key",
"type": "apiKey"
}
}
}
}

View file

@ -1,19 +0,0 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
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 bookstore exists to allow this repo to work with recursive go get.
// It will be filled in with auto generated code.
package bookstore

Some files were not shown because too many files have changed in this diff Show more