Merge pull request #1 from Microsoft/yqwang/dev
Initial FrameworkController: General-Purpose Kubernetes Pod Controller
This commit is contained in:
Коммит
3fcb72beae
|
@ -1,330 +1,2 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
dist/
|
||||
|
|
|
@ -0,0 +1,529 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/PuerkitoBio/purell"
|
||||
packages = ["."]
|
||||
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/PuerkitoBio/urlesc"
|
||||
packages = ["."]
|
||||
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/emicklei/go-restful"
|
||||
packages = [
|
||||
".",
|
||||
"log"
|
||||
]
|
||||
revision = "3eb9738c1697594ea6e71a7156a9bb32ed216cf0"
|
||||
version = "v2.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/ghodss/yaml"
|
||||
packages = ["."]
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-openapi/jsonpointer"
|
||||
packages = ["."]
|
||||
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
|
||||
version = "0.15.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-openapi/jsonreference"
|
||||
packages = ["."]
|
||||
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
|
||||
version = "0.15.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-openapi/spec"
|
||||
packages = ["."]
|
||||
revision = "bce47c9386f9ecd6b86f450478a80103c3fe1402"
|
||||
version = "0.15.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-openapi/swag"
|
||||
packages = ["."]
|
||||
revision = "2b0bd4f193d011c203529df626a65d63cb8a79e8"
|
||||
version = "0.15.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"sortkeys"
|
||||
]
|
||||
revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/glog"
|
||||
packages = ["."]
|
||||
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/google/gofuzz"
|
||||
packages = ["."]
|
||||
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/googleapis/gnostic"
|
||||
packages = [
|
||||
"OpenAPIv2",
|
||||
"compiler",
|
||||
"extensions"
|
||||
]
|
||||
revision = "7c663266750e7d82587642f65e60bc4083f1f84e"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
packages = [
|
||||
".",
|
||||
"simplelru"
|
||||
]
|
||||
revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/howeyc/gopass"
|
||||
packages = ["."]
|
||||
revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/imdario/mergo"
|
||||
packages = ["."]
|
||||
revision = "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
|
||||
version = "v0.3.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
revision = "ab8a2e0c74be9d3be70b3184d9acc634935ded82"
|
||||
version = "1.1.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mailru/easyjson"
|
||||
packages = [
|
||||
"buffer",
|
||||
"jlexer",
|
||||
"jwriter"
|
||||
]
|
||||
revision = "d5012789d6659eeed305f54c1b1542e7b65829e6"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||
version = "1.0.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
|
||||
version = "1.0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
revision = "3e01752db0189b9157070a0e1668a620f9a85da2"
|
||||
version = "v1.0.6"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ssh/terminal"]
|
||||
revision = "c126467f60eb25f8f27e5a981f32a87e3965053f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna"
|
||||
]
|
||||
revision = "a680a1efc54dd51c040b3b5ce4939ea3cf2ea0d1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
"width"
|
||||
]
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/time"
|
||||
packages = ["rate"]
|
||||
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"imports",
|
||||
"internal/fastwalk"
|
||||
]
|
||||
revision = "ded554d0681e0cba3cf074977cdc12e2c0906fe6"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/inf.v0"
|
||||
packages = ["."]
|
||||
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
|
||||
version = "v0.9.1"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[[projects]]
|
||||
name = "k8s.io/api"
|
||||
packages = [
|
||||
"admissionregistration/v1alpha1",
|
||||
"admissionregistration/v1beta1",
|
||||
"apps/v1",
|
||||
"apps/v1beta1",
|
||||
"apps/v1beta2",
|
||||
"authentication/v1",
|
||||
"authentication/v1beta1",
|
||||
"authorization/v1",
|
||||
"authorization/v1beta1",
|
||||
"autoscaling/v1",
|
||||
"autoscaling/v2beta1",
|
||||
"batch/v1",
|
||||
"batch/v1beta1",
|
||||
"batch/v2alpha1",
|
||||
"certificates/v1beta1",
|
||||
"core/v1",
|
||||
"events/v1beta1",
|
||||
"extensions/v1beta1",
|
||||
"networking/v1",
|
||||
"policy/v1beta1",
|
||||
"rbac/v1",
|
||||
"rbac/v1alpha1",
|
||||
"rbac/v1beta1",
|
||||
"scheduling/v1alpha1",
|
||||
"settings/v1alpha1",
|
||||
"storage/v1",
|
||||
"storage/v1alpha1",
|
||||
"storage/v1beta1"
|
||||
]
|
||||
revision = "73d903622b7391f3312dcbac6483fed484e185f8"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[projects]]
|
||||
name = "k8s.io/apiextensions-apiserver"
|
||||
packages = [
|
||||
"pkg/apis/apiextensions",
|
||||
"pkg/apis/apiextensions/v1beta1",
|
||||
"pkg/client/clientset/clientset",
|
||||
"pkg/client/clientset/clientset/scheme",
|
||||
"pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
|
||||
]
|
||||
revision = "750feebe2038bad00fdf68ba37c31f62c4f1f891"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[projects]]
|
||||
name = "k8s.io/apimachinery"
|
||||
packages = [
|
||||
"pkg/api/errors",
|
||||
"pkg/api/meta",
|
||||
"pkg/api/resource",
|
||||
"pkg/apimachinery",
|
||||
"pkg/apimachinery/registered",
|
||||
"pkg/apis/meta/internalversion",
|
||||
"pkg/apis/meta/v1",
|
||||
"pkg/apis/meta/v1/unstructured",
|
||||
"pkg/apis/meta/v1beta1",
|
||||
"pkg/conversion",
|
||||
"pkg/conversion/queryparams",
|
||||
"pkg/fields",
|
||||
"pkg/labels",
|
||||
"pkg/runtime",
|
||||
"pkg/runtime/schema",
|
||||
"pkg/runtime/serializer",
|
||||
"pkg/runtime/serializer/json",
|
||||
"pkg/runtime/serializer/protobuf",
|
||||
"pkg/runtime/serializer/recognizer",
|
||||
"pkg/runtime/serializer/streaming",
|
||||
"pkg/runtime/serializer/versioning",
|
||||
"pkg/selection",
|
||||
"pkg/types",
|
||||
"pkg/util/cache",
|
||||
"pkg/util/clock",
|
||||
"pkg/util/diff",
|
||||
"pkg/util/errors",
|
||||
"pkg/util/framer",
|
||||
"pkg/util/intstr",
|
||||
"pkg/util/json",
|
||||
"pkg/util/net",
|
||||
"pkg/util/runtime",
|
||||
"pkg/util/sets",
|
||||
"pkg/util/validation",
|
||||
"pkg/util/validation/field",
|
||||
"pkg/util/wait",
|
||||
"pkg/util/yaml",
|
||||
"pkg/version",
|
||||
"pkg/watch",
|
||||
"third_party/forked/golang/reflect"
|
||||
]
|
||||
revision = "302974c03f7e50f16561ba237db776ab93594ef6"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[projects]]
|
||||
name = "k8s.io/client-go"
|
||||
packages = [
|
||||
"discovery",
|
||||
"discovery/fake",
|
||||
"informers",
|
||||
"informers/admissionregistration",
|
||||
"informers/admissionregistration/v1alpha1",
|
||||
"informers/admissionregistration/v1beta1",
|
||||
"informers/apps",
|
||||
"informers/apps/v1",
|
||||
"informers/apps/v1beta1",
|
||||
"informers/apps/v1beta2",
|
||||
"informers/autoscaling",
|
||||
"informers/autoscaling/v1",
|
||||
"informers/autoscaling/v2beta1",
|
||||
"informers/batch",
|
||||
"informers/batch/v1",
|
||||
"informers/batch/v1beta1",
|
||||
"informers/batch/v2alpha1",
|
||||
"informers/certificates",
|
||||
"informers/certificates/v1beta1",
|
||||
"informers/core",
|
||||
"informers/core/v1",
|
||||
"informers/events",
|
||||
"informers/events/v1beta1",
|
||||
"informers/extensions",
|
||||
"informers/extensions/v1beta1",
|
||||
"informers/internalinterfaces",
|
||||
"informers/networking",
|
||||
"informers/networking/v1",
|
||||
"informers/policy",
|
||||
"informers/policy/v1beta1",
|
||||
"informers/rbac",
|
||||
"informers/rbac/v1",
|
||||
"informers/rbac/v1alpha1",
|
||||
"informers/rbac/v1beta1",
|
||||
"informers/scheduling",
|
||||
"informers/scheduling/v1alpha1",
|
||||
"informers/settings",
|
||||
"informers/settings/v1alpha1",
|
||||
"informers/storage",
|
||||
"informers/storage/v1",
|
||||
"informers/storage/v1alpha1",
|
||||
"informers/storage/v1beta1",
|
||||
"kubernetes",
|
||||
"kubernetes/scheme",
|
||||
"kubernetes/typed/admissionregistration/v1alpha1",
|
||||
"kubernetes/typed/admissionregistration/v1beta1",
|
||||
"kubernetes/typed/apps/v1",
|
||||
"kubernetes/typed/apps/v1beta1",
|
||||
"kubernetes/typed/apps/v1beta2",
|
||||
"kubernetes/typed/authentication/v1",
|
||||
"kubernetes/typed/authentication/v1beta1",
|
||||
"kubernetes/typed/authorization/v1",
|
||||
"kubernetes/typed/authorization/v1beta1",
|
||||
"kubernetes/typed/autoscaling/v1",
|
||||
"kubernetes/typed/autoscaling/v2beta1",
|
||||
"kubernetes/typed/batch/v1",
|
||||
"kubernetes/typed/batch/v1beta1",
|
||||
"kubernetes/typed/batch/v2alpha1",
|
||||
"kubernetes/typed/certificates/v1beta1",
|
||||
"kubernetes/typed/core/v1",
|
||||
"kubernetes/typed/events/v1beta1",
|
||||
"kubernetes/typed/extensions/v1beta1",
|
||||
"kubernetes/typed/networking/v1",
|
||||
"kubernetes/typed/policy/v1beta1",
|
||||
"kubernetes/typed/rbac/v1",
|
||||
"kubernetes/typed/rbac/v1alpha1",
|
||||
"kubernetes/typed/rbac/v1beta1",
|
||||
"kubernetes/typed/scheduling/v1alpha1",
|
||||
"kubernetes/typed/settings/v1alpha1",
|
||||
"kubernetes/typed/storage/v1",
|
||||
"kubernetes/typed/storage/v1alpha1",
|
||||
"kubernetes/typed/storage/v1beta1",
|
||||
"listers/admissionregistration/v1alpha1",
|
||||
"listers/admissionregistration/v1beta1",
|
||||
"listers/apps/v1",
|
||||
"listers/apps/v1beta1",
|
||||
"listers/apps/v1beta2",
|
||||
"listers/autoscaling/v1",
|
||||
"listers/autoscaling/v2beta1",
|
||||
"listers/batch/v1",
|
||||
"listers/batch/v1beta1",
|
||||
"listers/batch/v2alpha1",
|
||||
"listers/certificates/v1beta1",
|
||||
"listers/core/v1",
|
||||
"listers/events/v1beta1",
|
||||
"listers/extensions/v1beta1",
|
||||
"listers/networking/v1",
|
||||
"listers/policy/v1beta1",
|
||||
"listers/rbac/v1",
|
||||
"listers/rbac/v1alpha1",
|
||||
"listers/rbac/v1beta1",
|
||||
"listers/scheduling/v1alpha1",
|
||||
"listers/settings/v1alpha1",
|
||||
"listers/storage/v1",
|
||||
"listers/storage/v1alpha1",
|
||||
"listers/storage/v1beta1",
|
||||
"pkg/apis/clientauthentication",
|
||||
"pkg/apis/clientauthentication/v1alpha1",
|
||||
"pkg/version",
|
||||
"plugin/pkg/client/auth/exec",
|
||||
"rest",
|
||||
"rest/watch",
|
||||
"testing",
|
||||
"tools/auth",
|
||||
"tools/cache",
|
||||
"tools/clientcmd",
|
||||
"tools/clientcmd/api",
|
||||
"tools/clientcmd/api/latest",
|
||||
"tools/clientcmd/api/v1",
|
||||
"tools/metrics",
|
||||
"tools/pager",
|
||||
"tools/reference",
|
||||
"transport",
|
||||
"util/buffer",
|
||||
"util/cert",
|
||||
"util/flowcontrol",
|
||||
"util/homedir",
|
||||
"util/integer",
|
||||
"util/retry",
|
||||
"util/workqueue"
|
||||
]
|
||||
revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
|
||||
version = "v7.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "k8s.io/code-generator"
|
||||
packages = [
|
||||
"cmd/client-gen",
|
||||
"cmd/client-gen/args",
|
||||
"cmd/client-gen/generators",
|
||||
"cmd/client-gen/generators/fake",
|
||||
"cmd/client-gen/generators/scheme",
|
||||
"cmd/client-gen/generators/util",
|
||||
"cmd/client-gen/path",
|
||||
"cmd/client-gen/types",
|
||||
"cmd/deepcopy-gen",
|
||||
"cmd/deepcopy-gen/args",
|
||||
"cmd/defaulter-gen",
|
||||
"cmd/defaulter-gen/args",
|
||||
"cmd/informer-gen",
|
||||
"cmd/informer-gen/args",
|
||||
"cmd/informer-gen/generators",
|
||||
"cmd/lister-gen",
|
||||
"cmd/lister-gen/args",
|
||||
"cmd/lister-gen/generators",
|
||||
"cmd/openapi-gen",
|
||||
"cmd/openapi-gen/args",
|
||||
"pkg/util"
|
||||
]
|
||||
revision = "7ead8f38b01cf8653249f5af80ce7b2c8aba12e2"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "k8s.io/gengo"
|
||||
packages = [
|
||||
"args",
|
||||
"examples/deepcopy-gen/generators",
|
||||
"examples/defaulter-gen/generators",
|
||||
"examples/set-gen/sets",
|
||||
"generator",
|
||||
"namer",
|
||||
"parser",
|
||||
"types"
|
||||
]
|
||||
revision = "906d99f89cd644eecf75ab547b29bf9f876f0b59"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "k8s.io/kube-openapi"
|
||||
packages = [
|
||||
"cmd/openapi-gen/args",
|
||||
"pkg/common",
|
||||
"pkg/generators",
|
||||
"pkg/generators/rules",
|
||||
"pkg/util/sets"
|
||||
]
|
||||
revision = "d8ea2fe547a448256204cfc68dfee7b26c720acb"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "cb5bee9e5ec1e65f463ce5bdd3d489484ddd3c564f13bcf3d681ef9c9da8d724"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
|
@ -0,0 +1,52 @@
|
|||
ignored = ["github.com/microsoft/frameworkcontroller"]
|
||||
|
||||
required = [
|
||||
"k8s.io/code-generator/cmd/client-gen",
|
||||
"k8s.io/code-generator/cmd/informer-gen",
|
||||
"k8s.io/code-generator/cmd/lister-gen",
|
||||
"k8s.io/code-generator/cmd/deepcopy-gen",
|
||||
"k8s.io/code-generator/cmd/defaulter-gen",
|
||||
"k8s.io/code-generator/cmd/openapi-gen",
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered",
|
||||
]
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
version = "1.0.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/api"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/apimachinery"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/code-generator"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "k8s.io/apiextensions-apiserver"
|
||||
version = "kubernetes-1.10.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
non-go = true
|
||||
[[prune.project]]
|
||||
name = "k8s.io/code-generator"
|
||||
unused-packages = false
|
||||
non-go = false
|
||||
[[prune.project]]
|
||||
name = "k8s.io/client-go"
|
||||
unused-packages = false
|
||||
non-go = false
|
83
README.md
83
README.md
|
@ -1,6 +1,87 @@
|
|||
# FrameworkController
|
||||
FrameworkController is built to orchestrate all kinds of applications on [Kubernetes](https://kubernetes.io) by a single controller.
|
||||
|
||||
# Contributing
|
||||
These kinds of applications include but not limited to:
|
||||
- __Stateless and Stateful Service__ (Nginx, TensorFlow Serving, HBase, Kafka, etc)
|
||||
- __Stateless and Stateful Batch__ (KD-Tree Building, Batch Data Processing, etc)
|
||||
- __Any combination of above applications__ (Distributed TensorFlow Training, Stream Data Processing, etc)
|
||||
|
||||
## Why Need It
|
||||
### Problem
|
||||
In the open source community, there are so many specialized Kubernetes Pod controllers which are built for a specific kind of application, such as [Kubernetes StatefulSet Controller](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset), [Kubernetes Job Controller](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion), [KubeFlow TFJob Operator](https://www.kubeflow.org/docs/guides/components/tftraining). However, no one is built for all kinds of applications and combination of the existing ones still cannot support some kinds of applications. So, we have to learn, use, develop, deploy and maintain so many Pod controllers.
|
||||
|
||||
### Solution
|
||||
Build a General-Purpose Kubernetes Pod Controller: FrameworkController.
|
||||
|
||||
And then we can get below benefits from it:
|
||||
- __Support Kubernetes official unsupported applications__
|
||||
|
||||
Such as the [Stateful Batch with Service](example/framework/basic/batchwithservicesucceeded.yaml) application, like [Distributed TensorFlow
|
||||
Training](https://www.tensorflow.org/deploy/distributed).
|
||||
|
||||
- __Only need to learn, use, develop, deploy and maintain a single controller__
|
||||
|
||||
- __All kinds of applications can be orchestrated through the same interface with a unified experience__
|
||||
|
||||
- __If really required, only need to build specialized controllers on top of it, instead of building from scratch__
|
||||
|
||||
The similar practice is also adopted by Kubernetes official controllers, such as the [Kubernetes Deployment Controller](https://kubernetes.io/docs/concepts/workloads/controllers/deployment) is built on top of the [Kubernetes ReplicaSet Controller](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset).
|
||||
|
||||
## <a name="FrameworkInterop">Architecture</a>
|
||||
<p style="text-align: left;">
|
||||
<img src="doc/architecture.svg" title="Architecture" alt="Architecture" width="150%"/>
|
||||
</p>
|
||||
|
||||
## Feature
|
||||
### Framework Feature
|
||||
A Framework represents an application with a set of Tasks:
|
||||
1. Executed by Kubernetes Pod
|
||||
2. Partitioned to different heterogeneous TaskRoles which share the same lifecycle
|
||||
3. Ordered in the same homogeneous TaskRole by TaskIndex
|
||||
4. With consistent identity {FrameworkName}-{TaskRoleName}-{TaskIndex} as PodName
|
||||
5. With fine grained [RetryPolicy](doc/user-manual.md#RetryPolicy) for each Task and the whole Framework
|
||||
6. With fine grained [FrameworkAttemptCompletionPolicy](doc/user-manual.md#FrameworkAttemptCompletionPolicy) for each TaskRole
|
||||
7. Guarantees at most one instance of a specific Task is running at any point in time
|
||||
8. Guarantees at most one instance of a specific Framework is running at any point in time
|
||||
|
||||
### Controller Feature
|
||||
1. Highly generalized as it is built for all kinds of applications
|
||||
2. Light-weight as it is only responsible for Pod orchestration
|
||||
3. Tolerate Pod/ConfigMap unexpected deletion, Node/Network/FrameworkController/Kubernetes failure
|
||||
4. Well-defined Framework consistency, state machine and failure model
|
||||
5. Idiomatic with Kubernetes official controllers, such as [Pod Spec](https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/#pod-templates)
|
||||
6. Compatible with other Kubernetes features, such as Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service), [Gpu Scheduling](https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus), [Volume](https://kubernetes.io/docs/concepts/storage/volumes/), [Logging](https://kubernetes.io/docs/concepts/cluster-administration/logging)
|
||||
7. Aligned with Kubernetes [Controller Design Guidelines](https://github.com/kubernetes/community/blob/master/contributors/devel/controllers.md) and [API Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md)
|
||||
|
||||
## Prerequisite
|
||||
1. A Kubernetes cluster, v1.10 or above, on-cloud or on-premise.
|
||||
|
||||
## Quick Start
|
||||
1. [Build](build/frameworkcontroller)
|
||||
2. [Run Example](example/run/frameworkcontroller)
|
||||
3. [Config Usage](pkg/apis/frameworkcontroller/v1/config.go)
|
||||
4. [Config Example](example/config)
|
||||
5. [Framework Usage](pkg/apis/frameworkcontroller/v1/types.go)
|
||||
6. [Framework Example](example/framework)
|
||||
|
||||
## Doc
|
||||
1. [User Manual](doc/user-manual.md)
|
||||
2. [Known Issue and Upcoming Feature](doc/known-issue-and-upcoming-feature.md)
|
||||
3. FAQ
|
||||
4. Release Note
|
||||
|
||||
## Third Party Controller Wrapper
|
||||
A specialized wrapper can be built on top of FrameworkController to optimize for a specific kind of application:
|
||||
* [OpenPAI Controller Wrapper](https://github.com/Microsoft/pai)(Developing): A wrapper client optimized for AI applications
|
||||
* [NNI Controller Wrapper](https://github.com/Microsoft/nni)(Developing): A wrapper client optimized for AutoML applications
|
||||
|
||||
## Official Image
|
||||
[FrameworkController DockerHub](https://hub.docker.com/u/frameworkcontroller)
|
||||
|
||||
## Related Project
|
||||
* [YARN FrameworkLauncher](https://github.com/Microsoft/pai/blob/master/subprojects/frameworklauncher/yarn): Similar offering natively supports [Apache YARN](http://hadoop.apache.org)
|
||||
|
||||
## Contributing
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
BASH_DIR=$(cd $(dirname ${BASH_SOURCE}) && pwd)
|
||||
|
||||
cd ${BASH_DIR}
|
||||
|
||||
./frameworkcontroller
|
|
@ -0,0 +1,34 @@
|
|||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
FROM golang:alpine
|
||||
|
||||
ENV PROJECT_DIR=${GOPATH}/src/github.com/microsoft/frameworkcontroller
|
||||
|
||||
RUN apk update && apk add bash && mkdir -p ${PROJECT_DIR}
|
||||
COPY . ${PROJECT_DIR}
|
||||
WORKDIR ${PROJECT_DIR}
|
||||
|
||||
RUN ./build/frameworkcontroller/go-build.sh
|
||||
WORKDIR ./dist/frameworkcontroller
|
||||
|
||||
ENTRYPOINT ["./start.sh"]
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
BASH_DIR=$(cd $(dirname ${BASH_SOURCE}) && pwd)
|
||||
PROJECT_DIR=${BASH_DIR}/../..
|
||||
IMAGE_NAME=frameworkcontroller
|
||||
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
docker build -t ${IMAGE_NAME} -f ${BASH_DIR}/Dockerfile .
|
||||
|
||||
echo Succeeded to build docker image ${IMAGE_NAME}
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/bash
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
BASH_DIR=$(cd $(dirname ${BASH_SOURCE}) && pwd)
|
||||
# Ensure ${PROJECT_DIR} is ${GOPATH}/src/github.com/microsoft/frameworkcontroller
|
||||
PROJECT_DIR=${BASH_DIR}/../..
|
||||
DIST_DIR=${PROJECT_DIR}/dist/frameworkcontroller
|
||||
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
rm -rf ${DIST_DIR}
|
||||
mkdir -p ${DIST_DIR}
|
||||
|
||||
go build -o ${DIST_DIR}/frameworkcontroller cmd/frameworkcontroller/*
|
||||
chmod a+x ${DIST_DIR}/frameworkcontroller
|
||||
cp -r bin/frameworkcontroller/* ${DIST_DIR}
|
||||
cp -r example/config/default/* ${DIST_DIR}
|
||||
|
||||
echo Succeeded to build binary distribution into ${DIST_DIR}:
|
||||
cd ${DIST_DIR} && ls -lR .
|
|
@ -0,0 +1,47 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"github.com/microsoft/frameworkcontroller/pkg/common"
|
||||
"github.com/microsoft/frameworkcontroller/pkg/controller"
|
||||
)
|
||||
|
||||
func init() {
|
||||
common.InitAll()
|
||||
}
|
||||
|
||||
func main() {
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
|
||||
go controller.NewQueueFrameworkController().Run(stopCh)
|
||||
|
||||
sigTerm := make(chan os.Signal, 1)
|
||||
signal.Notify(sigTerm, syscall.SIGTERM)
|
||||
signal.Notify(sigTerm, syscall.SIGINT)
|
||||
<-sigTerm
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
|
||||
width="6.16528in" height="4.36635in" viewBox="0 0 443.9 314.377" xml:space="preserve" color-interpolation-filters="sRGB"
|
||||
class="st23">
|
||||
<title>FrameworkController Architecture</title>
|
||||
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.st1 {marker-end:url(#mrkr1-6);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
|
||||
.st2 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.28409090909091}
|
||||
.st3 {fill:#ffffff;stroke:none;stroke-linecap:butt;stroke-width:7.2}
|
||||
.st4 {fill:#000000;font-family:Calibri;font-size:0.666664em}
|
||||
.st5 {marker-end:url(#mrkr1-6);stroke:#000000;stroke-dasharray:3,3;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
|
||||
.st6 {visibility:visible}
|
||||
.st7 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-opacity:0.22}
|
||||
.st8 {fill:#bdedff;stroke:#000000;stroke-width:1}
|
||||
.st9 {fill:#000000;font-family:Calibri;font-size:0.666664em;font-weight:bold}
|
||||
.st10 {fill:#dae2f3;stroke:#000000;stroke-width:1}
|
||||
.st11 {fill:#000000;font-family:Calibri;font-size:0.666664em;font-style:italic;font-weight:bold}
|
||||
.st12 {font-size:1em;font-style:normal}
|
||||
.st13 {fill:#ededed;stroke:#000000;stroke-width:1}
|
||||
.st14 {fill:#ffffff;stroke:#000000;stroke-width:1}
|
||||
.st15 {fill:#5b9bd5;fill-opacity:0.22;filter:url(#filter_2);stroke:#5b9bd5;stroke-dasharray:3,3;stroke-opacity:0.22}
|
||||
.st16 {fill:#ffffff;stroke:#000000;stroke-dasharray:3,3;stroke-width:1}
|
||||
.st17 {fill:#ffffff;stroke:none;stroke-linecap:butt}
|
||||
.st18 {fill:#fad3b8;stroke:#000000;stroke-width:1}
|
||||
.st19 {font-size:1em}
|
||||
.st20 {fill:#fbe5d5;stroke:#000000;stroke-width:1}
|
||||
.st21 {fill:#cde3bf;stroke:#000000;stroke-width:1}
|
||||
.st22 {fill:#e2efd9;stroke:#000000;stroke-width:1}
|
||||
.st23 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]>
|
||||
</style>
|
||||
|
||||
<defs id="Markers">
|
||||
<g id="lend1">
|
||||
<path d="M 1 -1 L 0 0 L 1 1 " style="stroke-linecap:round;stroke-linejoin:round;fill:none"/>
|
||||
</g>
|
||||
<marker id="mrkr1-6" class="st2" orient="auto" markerUnits="strokeWidth" overflow="visible">
|
||||
<use xlink:href="#lend1" transform="scale(-3.52,-3.52) "/>
|
||||
</marker>
|
||||
</defs>
|
||||
<defs id="Filters">
|
||||
<filter id="filter_2">
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g>
|
||||
<title>Page-1</title>
|
||||
<g id="shape64-1" transform="translate(321.077,-143.975)">
|
||||
<title>Dynamic connector</title>
|
||||
<desc>3/6</desc>
|
||||
<path d="M0 314.38 L23.85 314.38 L23.85 256.75" class="st1"/>
|
||||
<rect x="18.2459" y="292.688" width="11.1995" height="9.59985" class="st3"/>
|
||||
<text x="18.25" y="299.89" class="st4">3/6</text> </g>
|
||||
<g id="shape93-9" transform="translate(290.923,-213.475)">
|
||||
<title>Dynamic connector.99</title>
|
||||
<desc>2/5/8</desc>
|
||||
<path d="M0 314.38 L-23.85 314.38 L-23.85 372" class="st1"/>
|
||||
<rect x="-33.0178" y="326.467" width="18.3441" height="9.59985" class="st3"/>
|
||||
<text x="-33.02" y="333.67" class="st4">2/5/8</text> </g>
|
||||
<g id="shape107-16" transform="translate(353.923,-271.1)">
|
||||
<title>Dynamic connector.107</title>
|
||||
<desc>0</desc>
|
||||
<path d="M-9 314.38 L-9 360.13" class="st1"/>
|
||||
<rect x="-11.0275" y="332.452" width="4.05478" height="9.59985" class="st3"/>
|
||||
<text x="-11.03" y="339.65" class="st4">0</text> </g>
|
||||
<g id="shape111-23" transform="translate(66.1544,-86.35)">
|
||||
<title>Dynamic connector.111</title>
|
||||
<path d="M9 314.38 L9 268.63" class="st5"/>
|
||||
</g>
|
||||
<g id="shape112-28" transform="translate(153,-86.35)">
|
||||
<title>Dynamic connector.112</title>
|
||||
<path d="M0 314.38 L0 292.73 L-77.85 292.73 L-77.85 268.63" class="st5"/>
|
||||
</g>
|
||||
<g id="shape113-33" transform="translate(398.923,-213.475)">
|
||||
<title>Dynamic connector.113</title>
|
||||
<path d="M0 314.38 L6.98 314.38 L6.98 501.05 L-323.77 501.05 L-323.77 483.05" class="st1"/>
|
||||
</g>
|
||||
<g id="shape114-38" transform="translate(398.923,-213.475)">
|
||||
<title>Dynamic connector.114</title>
|
||||
<path d="M0 314.38 L6.98 314.38 L6.98 501.05 L-245.92 501.05 L-245.92 483.05" class="st1"/>
|
||||
</g>
|
||||
<g id="shape128-43" transform="translate(213.077,-132.1)">
|
||||
<title>External interactor.128</title>
|
||||
<desc>FrameworkController</desc>
|
||||
<g id="shadow128-44" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="290.627" width="108" height="23.75" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="290.627" width="108" height="23.75" class="st8"/>
|
||||
<text x="18.25" y="304.9" class="st9">FrameworkController</text> </g>
|
||||
<g id="shape144-49" transform="translate(290.923,-213.475)">
|
||||
<title>Dynamic connector.144</title>
|
||||
<desc>4</desc>
|
||||
<path d="M0 314.38 L-102.37 314.38 L-102.37 346.88 L-204.97 346.88 L-204.97 372" class="st1"/>
|
||||
<rect x="-104.396" y="338.506" width="4.05478" height="9.59985" class="st3"/>
|
||||
<text x="-104.4" y="345.71" class="st4">4</text> </g>
|
||||
<g id="shape147-56" transform="translate(21.1544,-132.1)">
|
||||
<title>External interactor.147</title>
|
||||
<desc>[ConfigMapObject] FrameworkAttemptInstance</desc>
|
||||
<g id="shadow147-57" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="290.627" width="108" height="23.75" rx="11.875" ry="11.875" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="290.627" width="108" height="23.75" rx="11.875" ry="11.875" class="st10"/>
|
||||
<text x="22.23" y="300.1" class="st11">[ConfigMapObject] <tspan x="7.03" dy="1.2em" class="st12">FrameworkAttemptInstance</tspan></text> </g>
|
||||
<g id="shape149-63" transform="translate(21.1544,-201.6)">
|
||||
<title>External interactor.149</title>
|
||||
<desc>[FrameworkObject] FrameworkInstance</desc>
|
||||
<g id="shadow149-64" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="290.627" width="108" height="23.75" rx="11.875" ry="11.875" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="290.627" width="108" height="23.75" rx="11.875" ry="11.875" class="st13"/>
|
||||
<text x="21.64" y="300.1" class="st11">[FrameworkObject] <tspan x="21.02" dy="1.2em" class="st12">FrameworkInstance</tspan></text> </g>
|
||||
<g id="shape151-70" transform="translate(66.1544,-155.85)">
|
||||
<title>Dynamic connector.151</title>
|
||||
<path d="M9 314.38 L9 268.63" class="st5"/>
|
||||
</g>
|
||||
<g id="shape152-75" transform="translate(290.923,-201.6)">
|
||||
<title>External interactor.152</title>
|
||||
<desc>KubernetesApiServer</desc>
|
||||
<g id="shadow152-76" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="290.627" width="108" height="23.75" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="290.627" width="108" height="23.75" class="st14"/>
|
||||
<text x="18.72" y="304.9" class="st9">KubernetesApiServer</text> </g>
|
||||
<g id="shape154-81" transform="translate(290.923,-271.1)">
|
||||
<title>External interactor.154</title>
|
||||
<desc>KubernetesClient</desc>
|
||||
<g id="shadow154-82" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<path d="M0 314.38 L108 314.38 L108 290.63 L0 290.63 L0 314.38 Z" class="st15"/>
|
||||
</g>
|
||||
<path d="M0 314.38 L108 314.38 L108 290.63 L0 290.63 L0 314.38 Z" class="st16"/>
|
||||
<text x="25.3" y="304.9" class="st9">KubernetesClient</text> </g>
|
||||
<g id="shape137-87" transform="translate(290.923,-204.475)">
|
||||
<title>Dynamic connector.137</title>
|
||||
<desc>1</desc>
|
||||
<path d="M0 305.38 L-161.77 305.38" class="st1"/>
|
||||
<rect x="-82.9117" y="300.577" width="4.05478" height="9.59985" class="st3"/>
|
||||
<text x="-82.91" y="307.78" class="st4">1</text> </g>
|
||||
<g id="shape166-94" transform="translate(398.923,-213.475)">
|
||||
<title>Dynamic connector.166</title>
|
||||
<path d="M0 314.38 L6.98 314.38 L6.98 501.05 L-131.85 501.05 L-131.85 483.05" class="st1"/>
|
||||
</g>
|
||||
<g id="shape160-99" transform="translate(398.923,-213.475)">
|
||||
<title>Dynamic connector.160</title>
|
||||
<desc>7</desc>
|
||||
<path d="M0 314.38 L6.98 314.38 L6.98 501.05 L-54 501.05 L-54 483.05" class="st1"/>
|
||||
<rect x="4.94973" y="438.915" width="4.05478" height="9.59985" class="st17"/>
|
||||
<text x="4.95" y="446.11" class="st4">7</text> </g>
|
||||
<g id="shape169-106" transform="translate(267.077,-86.35)">
|
||||
<title>Dynamic connector.169</title>
|
||||
<path d="M0 314.38 L0 292.73 L-191.92 292.73 L-191.92 268.63" class="st5"/>
|
||||
</g>
|
||||
<g id="shape170-111" transform="translate(344.923,-86.35)">
|
||||
<title>Dynamic connector.170</title>
|
||||
<path d="M0 314.38 L0 292.73 L-269.77 292.73 L-269.77 268.63" class="st5"/>
|
||||
</g>
|
||||
<g id="shape162-116" transform="translate(290.923,-44.8)">
|
||||
<title>External interactor.162</title>
|
||||
<desc><TaskRole-B> <TaskIndex-1> [PodObject] TaskAttemptInstance</desc>
|
||||
<g id="shadow162-117" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st18"/>
|
||||
<text x="31.65" y="281.6" class="st11"><TaskRole-B> <tspan x="30.09" dy="1.2em" class="st19"><</tspan>TaskIndex-1> <tspan
|
||||
x="34.25" dy="1.2em" class="st19">[</tspan>PodObject] <tspan x="18.53" dy="1.2em" class="st12">TaskAttemptInstance</tspan></text> </g>
|
||||
<g id="shape163-125" transform="translate(213.077,-44.8)">
|
||||
<title>External interactor.163</title>
|
||||
<desc><TaskRole-B> <TaskIndex-0> [PodObject] TaskAttemptInstance</desc>
|
||||
<g id="shadow163-126" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st20"/>
|
||||
<text x="31.65" y="281.6" class="st11"><TaskRole-B> <tspan x="30.09" dy="1.2em" class="st19"><</tspan>TaskIndex-0> <tspan
|
||||
x="34.25" dy="1.2em" class="st19">[</tspan>PodObject] <tspan x="18.53" dy="1.2em" class="st12">TaskAttemptInstance</tspan></text> </g>
|
||||
<g id="shape130-134" transform="translate(99,-44.8)">
|
||||
<title>External interactor.130</title>
|
||||
<desc><TaskRole-A> <TaskIndex-1> [PodObject] TaskAttemptInstance</desc>
|
||||
<g id="shadow130-135" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st21"/>
|
||||
<text x="31.47" y="281.6" class="st11"><TaskRole-A> <tspan x="30.09" dy="1.2em" class="st19"><</tspan>TaskIndex-1> <tspan
|
||||
x="34.25" dy="1.2em" class="st19">[</tspan>PodObject] <tspan x="18.53" dy="1.2em" class="st12">TaskAttemptInstance</tspan></text> </g>
|
||||
<g id="shape133-143" transform="translate(21.1544,-44.8)">
|
||||
<title>External interactor.133</title>
|
||||
<desc><TaskRole-A> <TaskIndex-0> [PodObject] TaskAttemptInstance</desc>
|
||||
<g id="shadow133-144" transform="matrix(1,0,0,1,0.345598,1.97279)" class="st6">
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st7"/>
|
||||
</g>
|
||||
<rect x="0" y="272.827" width="108" height="41.55" rx="20.775" ry="20.775" class="st22"/>
|
||||
<text x="31.47" y="281.6" class="st11"><TaskRole-A> <tspan x="30.09" dy="1.2em" class="st19"><</tspan>TaskIndex-0> <tspan
|
||||
x="34.25" dy="1.2em" class="st19">[</tspan>PodObject] <tspan x="18.53" dy="1.2em" class="st12">TaskAttemptInstance</tspan></text> </g>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 11 KiB |
|
@ -0,0 +1,22 @@
|
|||
# <a name="KnownIssueAndUpcomingFeature">Known Issue and Upcoming Feature</a>
|
||||
|
||||
## <a name="Index">Index</a>
|
||||
- [Internal Known Issue](#InternalKnownIssue)
|
||||
- [External Known Issue](#ExternalKnownIssue)
|
||||
- [Upcoming Feature](#UpcomingFeature)
|
||||
|
||||
## <a name="InternalKnownIssue">Internal Known Issue</a>
|
||||
|
||||
## <a name="ExternalKnownIssue">External Known Issue</a>
|
||||
- [ ] Kubernetes Dashboard: Pod Detail Page: "Unknown reference kind ConfigMap".
|
||||
|
||||
Tracked in [Dashboard errors if pod's owner reference is not supported](https://github.com/kubernetes/dashboard/issues/3251)
|
||||
|
||||
## <a name="UpcomingFeature">Upcoming Feature</a>
|
||||
- [ ] Add Distributed TensorFlow Training Example
|
||||
- [ ] Support Framework Spec Update
|
||||
- [ ] Support Framework Spec Validation and Defaulting
|
||||
- [ ] Support Framework Status Subresource
|
||||
- [ ] Support Framework CompletedRetainSec
|
||||
- [ ] Add AttemptCreating state to move the object initialization time out of the ObjectLocalCacheCreationTimeoutSec
|
||||
- [ ] Support Framework History Server
|
|
@ -0,0 +1,305 @@
|
|||
# <a name="UserManual">User Manual</a>
|
||||
|
||||
## <a name="Index">Index</a>
|
||||
- [Framework Interop](#FrameworkInterop)
|
||||
- [Container EnvironmentVariable](#ContainerEnvironmentVariable)
|
||||
- [CompletionCode Convention](#CompletionCodeConvention)
|
||||
- [RetryPolicy](#RetryPolicy)
|
||||
- [FrameworkAttemptCompletionPolicy](#FrameworkAttemptCompletionPolicy)
|
||||
- [Best Practice](#BestPractice)
|
||||
|
||||
## <a name="FrameworkInterop">Framework Interop</a>
|
||||
**Supported interoperations with a Framework**
|
||||
|
||||
| API Kind | Operations |
|
||||
|:---- |:---- |
|
||||
| Framework | [CREATE](#CREATE_Framework) [DELETE](#DELETE_Framework) [GET](#GET_Framework) [LIST](#LIST_Frameworks) [WATCH](#WATCH_Framework) [WATCH_LIST](#WATCH_LIST_Frameworks) |
|
||||
| [ConfigMap](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#configmap-v1-core) | All operations except for [CREATE](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#create-193) [PUT](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#replace-195) [PATCH](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#patch-194) |
|
||||
| [Pod](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#pod-v1-core) | All operations except for [CREATE](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#create-55) [PUT](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#replace-57) [PATCH](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#patch-56) |
|
||||
|
||||
**Supported clients to execute the interoperations with a Framework**
|
||||
|
||||
As Framework is actually a Kubernetes [CRD](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions), all CRD clients can be used to execute the interoperations with a Framework, see them in [Accessing a custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#accessing-a-custom-resource).
|
||||
|
||||
### <a name="CREATE_Framework">CREATE Framework</a>
|
||||
**Request**
|
||||
|
||||
POST /apis/frameworkcontroller.microsoft.com/v1/namespaces/{FrameworkNamespace}/frameworks
|
||||
|
||||
Body: [Framework](../pkg/apis/frameworkcontroller/v1/types.go)
|
||||
|
||||
Type: application/json
|
||||
|
||||
**Description**
|
||||
|
||||
Create the specified Framework.
|
||||
|
||||
**Response**
|
||||
|
||||
| Code | Body | Description |
|
||||
|:---- |:---- |:---- |
|
||||
| OK(200) | [Framework](../pkg/apis/frameworkcontroller/v1/types.go) | Return current Framework. |
|
||||
| Created(201) | [Framework](../pkg/apis/frameworkcontroller/v1/types.go) | Return current Framework. |
|
||||
| Accepted(202) | [Framework](../pkg/apis/frameworkcontroller/v1/types.go) | Return current Framework. |
|
||||
| Conflict(409) | [Status](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#status-v1-meta) | The specified Framework already exists. |
|
||||
|
||||
### <a name="DELETE_Framework">DELETE Framework</a>
|
||||
**Request**
|
||||
|
||||
DELETE /apis/frameworkcontroller.microsoft.com/v1/namespaces/{FrameworkNamespace}/frameworks/{FrameworkName}
|
||||
|
||||
Body:
|
||||
```json
|
||||
{
|
||||
"propagationPolicy": "Foreground"
|
||||
}
|
||||
```
|
||||
|
||||
Type: application/json
|
||||
|
||||
**Description**
|
||||
|
||||
Delete the specified Framework.
|
||||
|
||||
Notes:
|
||||
* Should always use and only use the provided body, see [Framework Notes](../pkg/apis/frameworkcontroller/v1/types.go).
|
||||
|
||||
**Response**
|
||||
|
||||
| Code | Body | Description |
|
||||
|:---- |:---- |:---- |
|
||||
| OK(200) | [Framework](../pkg/apis/frameworkcontroller/v1/types.go) | The specified Framework is deleting.<br>Return current Framework. |
|
||||
| OK(200) | [Status](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#status-v1-meta) | The specified Framework is deleted. |
|
||||
| NotFound(200) | [Status](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#status-v1-meta) | The specified Framework is not found. |
|
||||
|
||||
### <a name="GET_Framework">GET Framework</a>
|
||||
**Request**
|
||||
|
||||
GET /apis/frameworkcontroller.microsoft.com/v1/namespaces/{FrameworkNamespace}/frameworks/{FrameworkName}
|
||||
|
||||
**Description**
|
||||
|
||||
Get the specified Framework.
|
||||
|
||||
**Response**
|
||||
|
||||
| Code | Body | Description |
|
||||
|:---- |:---- |:---- |
|
||||
| OK(200) | [Framework](../pkg/apis/frameworkcontroller/v1/types.go) | Return current Framework. |
|
||||
| NotFound(200) | [Status](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#status-v1-meta) | The specified Framework is not found. |
|
||||
|
||||
### <a name="LIST_Frameworks">LIST Frameworks</a>
|
||||
**Request**
|
||||
|
||||
GET /apis/frameworkcontroller.microsoft.com/v1/namespaces/{FrameworkNamespace}/frameworks
|
||||
GET /apis/frameworkcontroller.microsoft.com/v1/frameworks
|
||||
|
||||
QueryParameters: Same as [StatefulSet QueryParameters](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#list-122)
|
||||
|
||||
**Description**
|
||||
|
||||
Get all Frameworks (in the specified FrameworkNamespace).
|
||||
|
||||
**Response**
|
||||
|
||||
| Code | Body | Description |
|
||||
|:---- |:---- |:---- |
|
||||
| OK(200) | [FrameworkList](../pkg/apis/frameworkcontroller/v1/types.go) | Return all Frameworks (in the specified FrameworkNamespace). |
|
||||
|
||||
### <a name="WATCH_Framework">WATCH Framework</a>
|
||||
**Request**
|
||||
|
||||
GET /apis/frameworkcontroller.microsoft.com/v1/watch/namespaces/{FrameworkNamespace}/frameworks/{FrameworkName}
|
||||
|
||||
QueryParameters: Same as [StatefulSet QueryParameters](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#watch-124)
|
||||
|
||||
**Description**
|
||||
|
||||
Watch the change events of the specified Framework.
|
||||
|
||||
**Response**
|
||||
|
||||
| Code | Body | Description |
|
||||
|:---- |:---- |:---- |
|
||||
| OK(200) | [WatchEvent](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#watchevent-v1-meta) | Streaming the change events of the specified Framework. |
|
||||
| NotFound(200) | [Status](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#status-v1-meta) | The specified Framework is not found. |
|
||||
|
||||
### <a name="WATCH_LIST_Frameworks">WATCH_LIST Frameworks</a>
|
||||
**Request**
|
||||
|
||||
GET /apis/frameworkcontroller.microsoft.com/v1/watch/namespaces/{FrameworkNamespace}/frameworks
|
||||
GET /apis/frameworkcontroller.microsoft.com/v1/watch/frameworks
|
||||
|
||||
QueryParameters: Same as [StatefulSet QueryParameters](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#watch-list-125)
|
||||
|
||||
**Description**
|
||||
|
||||
Watch the change events of all Frameworks (in the specified FrameworkNamespace).
|
||||
|
||||
**Response**
|
||||
|
||||
| Code | Body | Description |
|
||||
|:---- |:---- |:---- |
|
||||
| OK(200) | [WatchEvent](https://v1-10.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#watchevent-v1-meta) | Streaming the change events of all Frameworks (in the specified FrameworkNamespace). |
|
||||
|
||||
## <a name="ContainerEnvironmentVariable">Container EnvironmentVariable</a>
|
||||
[Container EnvironmentVariable](../pkg/apis/frameworkcontroller/v1/constants.go)
|
||||
|
||||
## <a name="CompletionCodeConvention">CompletionCode Convention</a>
|
||||
[CompletionCode Convention](../pkg/apis/frameworkcontroller/v1/types.go)
|
||||
|
||||
## <a name="RetryPolicy">RetryPolicy</a>
|
||||
### <a name="RetryPolicy_Spec">Spec</a>
|
||||
[RetryPolicySpec](../pkg/apis/frameworkcontroller/v1/types.go)
|
||||
|
||||
### <a name="RetryPolicy_Usage">Usage</a>
|
||||
[RetryPolicySpec](../pkg/apis/frameworkcontroller/v1/types.go)
|
||||
|
||||
### <a name="RetryPolicy_Example">Example</a>
|
||||
Notes:
|
||||
1. *Italic Conditions* can be inherited from the **DEFAULT** RetryPolicy, so no need to specify them explicitly.
|
||||
|
||||
*You still need to specify them explicitly, as we have not supported the Framework Spec Defaulting yet.*
|
||||
|
||||
2. For the definition of each CompletionType, such as Transient Failed, see [CompletionCode Convention](#CompletionCodeConvention).
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>FrameworkType</th>
|
||||
<th>Framework RetryPolicy</th>
|
||||
<th>TaskRole</th>
|
||||
<th>Task RetryPolicy</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><b>DEFAULT</td>
|
||||
<td rowspan="2"><i>FancyRetryPolicy = false<br>MaxRetryCount = 0</i></td>
|
||||
<td>TaskRole-A</td>
|
||||
<td><i>FancyRetryPolicy = false<br>MaxRetryCount = 0</i></td>
|
||||
<td rowspan="2">The default RetryPolicy:<br>Never Retry for any Failed or Succeeded.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TaskRole-B</td>
|
||||
<td><i>FancyRetryPolicy = false<br>MaxRetryCount = 0</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><b>Service</td>
|
||||
<td rowspan="1"><i>FancyRetryPolicy = false</i><br>MaxRetryCount = -2</td>
|
||||
<td>TaskRole-A</td>
|
||||
<td><i>FancyRetryPolicy = false</i><br>MaxRetryCount = -2</td>
|
||||
<td rowspan="1">Always Retry for any Failed or Succeeded.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><b>Blind Batch</td>
|
||||
<td rowspan="1"><i>FancyRetryPolicy = false</i><br>MaxRetryCount = -1</td>
|
||||
<td>TaskRole-A</td>
|
||||
<td><i>FancyRetryPolicy = false</i><br>MaxRetryCount = -1</td>
|
||||
<td rowspan="1">Always Retry for any Failed.<br>Never Retry for Succeeded.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><b>Batch with Task Fault Tolerance</td>
|
||||
<td rowspan="1">FancyRetryPolicy = true<br>MaxRetryCount = 3</td>
|
||||
<td>TaskRole-A</td>
|
||||
<td>FancyRetryPolicy = true<br>MaxRetryCount = 3</td>
|
||||
<td rowspan="1">Always Retry for Transient Failed.<br>Never Retry for Permanent Failed or Succeeded.<br>Retry up to 3 times for Unknown Failed.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><b>Batch without Task Fault Tolerance</td>
|
||||
<td rowspan="1">FancyRetryPolicy = true<br>MaxRetryCount = 3</td>
|
||||
<td>TaskRole-A</td>
|
||||
<td><i>FancyRetryPolicy = false<br>MaxRetryCount = 0</i></td>
|
||||
<td rowspan="1">For Framework RetryPolicy, same as "Batch with Task Fault Tolerance".<br>For Task RetryPolicy, because the Task cannot tolerate any failed TaskAttempt, such as it cannot recover from previous failed TaskAttempt, so Never Retry Task for any Failed or Succeeded.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><b>Debug Mode</td>
|
||||
<td rowspan="1">FancyRetryPolicy = true<br><i>MaxRetryCount = 0</i></td>
|
||||
<td>TaskRole-A</td>
|
||||
<td>FancyRetryPolicy = true<br><i>MaxRetryCount = 0</i></td>
|
||||
<td rowspan="1">Always Retry for Transient Failed.<br>Never Retry for Permanent Failed or Unknown Failed or Succeeded.<br>This can help to capture the unexpected exit of user application itself.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## <a name="FrameworkAttemptCompletionPolicy">FrameworkAttemptCompletionPolicy</a>
|
||||
### <a name="FrameworkAttemptCompletionPolicy_Spec">Spec</a>
|
||||
[CompletionPolicySpec](../pkg/apis/frameworkcontroller/v1/types.go)
|
||||
|
||||
### <a name="FrameworkAttemptCompletionPolicy_Usage">Usage</a>
|
||||
[CompletionPolicySpec](../pkg/apis/frameworkcontroller/v1/types.go)
|
||||
|
||||
### <a name="FrameworkAttemptCompletionPolicy_Example">Example</a>
|
||||
Notes:
|
||||
1. *Italic Conditions* can be inherited from the **DEFAULT** FrameworkAttemptCompletionPolicy, so no need to specify them explicitly.
|
||||
|
||||
*You still need to specify them explicitly, as we have not supported the Framework Spec Defaulting yet.*
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>FrameworkType</th>
|
||||
<th>TaskRole</th>
|
||||
<th>FrameworkAttemptCompletionPolicy</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><b>DEFAULT</td>
|
||||
<td>TaskRole-A</td>
|
||||
<td><i>MinFailedTaskCount = 1<br>MinSucceededTaskCount = -1</i></td>
|
||||
<td rowspan="2">The default FrameworkAttemptCompletionPolicy:<br>Fail the FrameworkAttempt immediately if any Task failed.<br>Succeed the FrameworkAttempt until all Tasks succeeded.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TaskRole-B</td>
|
||||
<td><i>MinFailedTaskCount = 1<br>MinSucceededTaskCount = -1</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><b>Service</td>
|
||||
<td>TaskRole-A</td>
|
||||
<td><i>MinFailedTaskCount = 1<br>MinSucceededTaskCount = -1</i></td>
|
||||
<td rowspan="1">Actually, any FrameworkAttemptCompletionPolicy is fine, since Service's Task will never complete, i.e. its Task's MaxRetryCount is -2, see <a href="#RetryPolicy_Example">RetryPolicy Example</a>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><b>MapReduce</td>
|
||||
<td>Map</td>
|
||||
<td>MinFailedTaskCount = {Map.TaskNumber} * {mapreduce.map.failures.maxpercent} + 1<br><i>MinSucceededTaskCount = -1</i></td>
|
||||
<td rowspan="2">A few failed Tasks is acceptable, but always want to wait all Tasks to succeed:<br>Fail the FrameworkAttempt immediately if the failed Tasks exceeded the limit.<br>Succeed the FrameworkAttempt until all Tasks completed and the failed Tasks is within the limit.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reduce</td>
|
||||
<td>MinFailedTaskCount = {Reduce.TaskNumber} * {mapreduce.reduce.failures.maxpercent} + 1<br><i>MinSucceededTaskCount = -1</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2"><b>TensorFlow</td>
|
||||
<td>ParameterServer</td>
|
||||
<td><i>MinFailedTaskCount = 1<br>MinSucceededTaskCount = -1</i></td>
|
||||
<td rowspan="2">Succeed a certain TaskRole is enough, and do not want to wait all Tasks to succeed:<br>Fail the FrameworkAttempt immediately if any Task failed.<br>Succeed the FrameworkAttempt immediately if Worker's all Tasks succeeded.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Worker</td>
|
||||
<td><i>MinFailedTaskCount = 1</i><br>MinSucceededTaskCount = {Worker.TaskNumber}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3"><b>Arbitrator Dominated</td>
|
||||
<td>Arbitrator</td>
|
||||
<td><i>MinFailedTaskCount = 1</i><br>MinSucceededTaskCount = 1</td>
|
||||
<td rowspan="3">The FrameworkAttemptCompletionPolicy is fully delegated to the single instance arbitrator of the user application:<br>Fail the FrameworkAttempt immediately if the arbitrator failed.<br>Succeed the FrameworkAttempt immediately if the arbitrator succeeded.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TaskRole-A</td>
|
||||
<td>MinFailedTaskCount = -1<br><i>MinSucceededTaskCount = -1</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TaskRole-B</td>
|
||||
<td>MinFailedTaskCount = -1<br><i>MinSucceededTaskCount = -1</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="1"><b>First Completed Task Dominated</td>
|
||||
<td>TaskRole-A</td>
|
||||
<td><i>MinFailedTaskCount = 1</i><br>MinSucceededTaskCount = 1</td>
|
||||
<td rowspan="1">The FrameworkAttemptCompletionPolicy is fully delegated to the first completed Task of the user application:<br>Fail the FrameworkAttempt immediately if any Task failed.<br>Succeed the FrameworkAttempt immediately if any Task succeeded.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## <a name="BestPractice">Best Practice</a>
|
||||
[Best Practice](../pkg/apis/frameworkcontroller/v1/types.go)
|
|
@ -0,0 +1,18 @@
|
|||
# Put it directly under frameworkcontroller's current working directory.
|
||||
# For the full config setting and usage, see ./pkg/apis/frameworkcontroller/v1/config.go
|
||||
|
||||
# This is the default config for frameworkcontroller, so all settings are commented out.
|
||||
|
||||
# Setup k8s config:
|
||||
# kubeApiServerAddress is default to ${KUBE_APISERVER_ADDRESS} and kubeConfigFilePath
|
||||
# is default to ${KUBECONFIG} then falls back to ${HOME}/.kube/config.
|
||||
# If both kubeApiServerAddress and kubeConfigFilePath after defaulting are still empty,
|
||||
# falls back to k8s inClusterConfig.
|
||||
#
|
||||
# Address should be in format http[s]://host:port
|
||||
#kubeApiServerAddress: http://10.10.10.10:8080
|
||||
#
|
||||
# See https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#config
|
||||
#kubeConfigFilePath: ""
|
||||
|
||||
#workerNumber: 20
|
|
@ -0,0 +1,34 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: batchfailedpermanent
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
taskRoles:
|
||||
- name: worker
|
||||
taskNumber: 1
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
# See CompletionCode Convention in
|
||||
# ./pkg/apis/frameworkcontroller/v1/constants.go
|
||||
command: [
|
||||
"sh", "-c",
|
||||
"sleep 10 &&
|
||||
echo exit with permanent failure to tell controller not to retry &&
|
||||
exit 210"]
|
|
@ -0,0 +1,34 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: batchfailedtransient
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
taskRoles:
|
||||
- name: worker
|
||||
taskNumber: 1
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
# See CompletionCode Convention in
|
||||
# ./pkg/apis/frameworkcontroller/v1/constants.go
|
||||
command: [
|
||||
"sh", "-c",
|
||||
"sleep 10 &&
|
||||
echo exit with transient failure to tell controller to retry &&
|
||||
exit 200"]
|
|
@ -0,0 +1,34 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: batchfailedtransientconflict
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
taskRoles:
|
||||
- name: worker
|
||||
taskNumber: 1
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: false
|
||||
maxRetryCount: 0
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
# See CompletionCode Convention in
|
||||
# ./pkg/apis/frameworkcontroller/v1/constants.go
|
||||
command: [
|
||||
"sh", "-c",
|
||||
"sleep 10 &&
|
||||
echo exit with transient conflict failure tell controller to back off retry &&
|
||||
exit 201"]
|
|
@ -0,0 +1,34 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: batchfailedunknown
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
taskRoles:
|
||||
- name: worker
|
||||
taskNumber: 1
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
# See CompletionCode Convention in
|
||||
# ./pkg/apis/frameworkcontroller/v1/constants.go
|
||||
command: [
|
||||
"sh", "-c",
|
||||
"sleep 10 &&
|
||||
echo exit with unknown failure to tell controller to retry within maxRetryCount &&
|
||||
exit 1"]
|
|
@ -0,0 +1,45 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: batchstatefulfailed
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
taskRoles:
|
||||
- name: worker
|
||||
taskNumber: 3
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
# To locate a specific Task during its whole lifecycle regardless of
|
||||
# any retry:
|
||||
# Consistent Identity:
|
||||
# PodName = {FrameworkName}-{TaskRoleName}-{TaskIndex}
|
||||
# PodNamespace = {FrameworkNamespace}
|
||||
# Consistent Environment Variable Value:
|
||||
# ${FRAMEWORK_NAME}, ${TASKROLE_NAME}, ${TASK_INDEX}
|
||||
# ${CONFIGMAP_NAME}, ${POD_NAME}, ${POD_NAMESPACE}
|
||||
#
|
||||
# To locate a specific execution attempt of a specific Task:
|
||||
# Attempt Specific Environment Variable Value:
|
||||
# ${FRAMEWORK_ATTEMPT_ID}, ${TASK_ATTEMPT_ID}
|
||||
#
|
||||
# To locate a specific execution attempt instance of a specific Task:
|
||||
# Attempt Instance Specific Environment Variable Value:
|
||||
# ${FRAMEWORK_ATTEMPT_INSTANCE_UID}, ${CONFIGMAP_UID}
|
||||
# ${TASK_ATTEMPT_INSTANCE_UID}, ${POD_UID}
|
||||
command: ["sh", "-c", "printenv && sleep 60 && exit 1"]
|
|
@ -0,0 +1,28 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: batchsucceeded
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
taskRoles:
|
||||
- name: worker
|
||||
taskNumber: 1
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
command: ["sh", "-c", "sleep 10 && printenv"]
|
|
@ -0,0 +1,48 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: batchwithservicesucceeded
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: true
|
||||
maxRetryCount: 1
|
||||
taskRoles:
|
||||
- name: server
|
||||
taskNumber: 2
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: false
|
||||
maxRetryCount: 0
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:stable
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- name: worker
|
||||
taskNumber: 3
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: 3
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: false
|
||||
maxRetryCount: 0
|
||||
pod:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
# The communication from worker to server is ignored in this
|
||||
# example, however, in a real case, worker usually needs the
|
||||
# help from server to make progress.
|
||||
command: ["sh", "-c", "sleep 10 && printenv"]
|
|
@ -0,0 +1,48 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: service
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: false
|
||||
maxRetryCount: -1
|
||||
taskRoles:
|
||||
- name: server
|
||||
taskNumber: 1
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: false
|
||||
maxRetryCount: -1
|
||||
pod:
|
||||
#metadata:
|
||||
# labels:
|
||||
# app: server
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:stable
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
# Post to {kubeApiServerAddress}/api/v1/namespaces/default/services/
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: server
|
||||
spec:
|
||||
selector:
|
||||
# Using predefined labels
|
||||
FRAMEWORK_NAME: service
|
||||
TASKROLE_NAME: server
|
||||
# Also can use customized labels
|
||||
#app: server
|
||||
ports:
|
||||
- port: 80
|
||||
type: NodePort
|
|
@ -0,0 +1,51 @@
|
|||
# Post to {kubeApiServerAddress}/apis/frameworkcontroller.microsoft.com/v1/namespaces/default/frameworks
|
||||
# For the full spec setting and usage, see ./pkg/apis/frameworkcontroller/v1/types.go
|
||||
apiVersion: frameworkcontroller.microsoft.com/v1
|
||||
kind: Framework
|
||||
metadata:
|
||||
name: servicestateful
|
||||
spec:
|
||||
executionType: Start
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: false
|
||||
maxRetryCount: -1
|
||||
taskRoles:
|
||||
- name: serverstateful
|
||||
taskNumber: 3
|
||||
frameworkAttemptCompletionPolicy:
|
||||
minFailedTaskCount: 1
|
||||
minSucceededTaskCount: -1
|
||||
task:
|
||||
retryPolicy:
|
||||
fancyRetryPolicy: false
|
||||
maxRetryCount: -1
|
||||
pod:
|
||||
spec:
|
||||
# Using Never restartPolicy, ActiveDeadlineSeconds and initContainers
|
||||
# just to demonstrate consistent identities in this example.
|
||||
restartPolicy: Never
|
||||
ActiveDeadlineSeconds: 60
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:stable
|
||||
ports:
|
||||
- containerPort: 80
|
||||
initContainers:
|
||||
- name: ubuntu
|
||||
image: ubuntu:trusty
|
||||
# See comments in batchstatefulfailed.yaml
|
||||
command: ["sh", "-c", "printenv"]
|
||||
---
|
||||
# Post to {kubeApiServerAddress}/api/v1/namespaces/default/services/
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: serverstateful
|
||||
spec:
|
||||
selector:
|
||||
# See comments in service.yaml
|
||||
FRAMEWORK_NAME: servicestateful
|
||||
TASKROLE_NAME: serverstateful
|
||||
ports:
|
||||
- port: 80
|
||||
type: NodePort
|
|
@ -0,0 +1,7 @@
|
|||
### Run FrameworkController by a Docker Container
|
||||
|
||||
Note at most one instance of FrameworkController can be run for a single k8s cluster.
|
||||
|
||||
```shell
|
||||
docker run -e KUBE_APISERVER_ADDRESS={http[s]://host:port} frameworkcontroller
|
||||
```
|
|
@ -0,0 +1,36 @@
|
|||
### Run FrameworkController by a Kubernetes StatefulSet
|
||||
|
||||
Note at most one instance of FrameworkController can be run for a single Kubernetes cluster.
|
||||
|
||||
```shell
|
||||
kubectl create -f frameworkcontroller.yaml
|
||||
```
|
||||
|
||||
frameworkcontroller.yaml:
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: frameworkcontroller
|
||||
spec:
|
||||
serviceName: frameworkcontroller
|
||||
selector:
|
||||
matchLabels:
|
||||
app: frameworkcontroller
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: frameworkcontroller
|
||||
spec:
|
||||
containers:
|
||||
- name: frameworkcontroller
|
||||
# Using local image just to demonstrate this example.
|
||||
imagePullPolicy: Never
|
||||
image: frameworkcontroller
|
||||
env:
|
||||
# May not need to specify KUBE_APISERVER_ADDRESS if the target
|
||||
# cluster to control is the cluster running the StatefulSet.
|
||||
- name: KUBE_APISERVER_ADDRESS
|
||||
value: {http[s]://host:port}
|
||||
```
|
|
@ -0,0 +1,7 @@
|
|||
### Run FrameworkController by a OS Process
|
||||
|
||||
Note at most one instance of FrameworkController can be run for a single k8s cluster.
|
||||
|
||||
```shell
|
||||
export KUBE_APISERVER_ADDRESS={http[s]://host:port} && ./dist/frameworkcontroller/start.sh
|
||||
```
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
BASH_DIR=$(cd $(dirname ${BASH_SOURCE}) && pwd)
|
||||
PROJECT_DIR=${BASH_DIR}/..
|
||||
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
./vendor/k8s.io/code-generator/generate-groups.sh \
|
||||
"all" \
|
||||
github.com/microsoft/frameworkcontroller/pkg/client \
|
||||
github.com/microsoft/frameworkcontroller/pkg/apis \
|
||||
frameworkcontroller:v1
|
||||
|
||||
echo Succeeded to generate k8s CRD client code
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/bash
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
BASH_DIR=$(cd $(dirname ${BASH_SOURCE}) && pwd)
|
||||
PROJECT_DIR=${BASH_DIR}/..
|
||||
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
dep ensure
|
||||
|
||||
echo Succeeded to update dependent package
|
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
BASH_DIR=$(cd $(dirname ${BASH_SOURCE}) && pwd)
|
||||
PROJECT_DIR=${BASH_DIR}/..
|
||||
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
git pull
|
||||
${BASH_DIR}/update-codegen.sh
|
||||
git add --all
|
||||
git status
|
||||
read -rsp $'Press enter to continue...\n'
|
||||
|
||||
git commit -m "Update codegen"
|
||||
git push
|
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
BASH_DIR=$(cd $(dirname ${BASH_SOURCE}) && pwd)
|
||||
PROJECT_DIR=${BASH_DIR}/..
|
||||
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
git pull
|
||||
${BASH_DIR}/update-dep.sh
|
||||
git add --all
|
||||
git status
|
||||
read -rsp $'Press enter to continue...\n'
|
||||
|
||||
git commit -m "Update dep"
|
||||
git push
|
|
@ -0,0 +1,160 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"github.com/microsoft/frameworkcontroller/pkg/common"
|
||||
)
|
||||
|
||||
type ControllerConfig struct {
|
||||
// If both kubeApiServerAddress and kubeConfigFilePath after defaulting are still
|
||||
// empty, falls back to k8s inClusterConfig.
|
||||
// Address should be in format http[s]://host:port
|
||||
KubeApiServerAddress *string `yaml:"kubeApiServerAddress"`
|
||||
// See https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#config
|
||||
KubeConfigFilePath *string `yaml:"kubeConfigFilePath"`
|
||||
|
||||
// Number of concurrent workers to process each different Frameworks
|
||||
WorkerNumber *int32 `yaml:"workerNumber"`
|
||||
|
||||
// Check interval and timeout to expect the created CRD to be in Established condition.
|
||||
CRDEstablishedCheckIntervalSec *int64 `yaml:"crdEstablishedCheckIntervalSec"`
|
||||
CRDEstablishedCheckTimeoutSec *int64 `yaml:"crdEstablishedCheckTimeoutSec"`
|
||||
|
||||
// Timeout to expect the created object in ApiServer also appears in the local
|
||||
// cache of the Controller's Informer.
|
||||
// If the created object does not appear in the local cache within the timeout,
|
||||
// it is considered as deleted.
|
||||
ObjectLocalCacheCreationTimeoutSec *int64 `yaml:"objectLocalCacheCreationTimeoutSec"`
|
||||
|
||||
// If the Framework FancyRetryPolicy is enabled and its FrameworkAttempt is
|
||||
// completed with Transient Conflict Failed CompletionType, it will be retried
|
||||
// after a random delay within this range.
|
||||
// This helps to avoid the resource deadlock for Framework which needs
|
||||
// Gang Scheduling (Gang Allocation), i.e. all Tasks in the Framework should
|
||||
// be run in an all-or-nothing fashion in order to perform any useful work.
|
||||
FrameworkMinRetryDelaySecForTransientConflictFailed *int64 `yaml:"frameworkMinRetryDelaySecForTransientConflictFailed"`
|
||||
FrameworkMaxRetryDelaySecForTransientConflictFailed *int64 `yaml:"frameworkMaxRetryDelaySecForTransientConflictFailed"`
|
||||
}
|
||||
|
||||
func NewControllerConfig() *ControllerConfig {
|
||||
c := initControllerConfig()
|
||||
|
||||
// Defaulting
|
||||
if c.KubeApiServerAddress == nil {
|
||||
c.KubeApiServerAddress = common.PtrString(os.Getenv("KUBE_APISERVER_ADDRESS"))
|
||||
}
|
||||
if c.KubeConfigFilePath == nil {
|
||||
c.KubeConfigFilePath = defaultKubeConfigFilePath()
|
||||
}
|
||||
if c.WorkerNumber == nil {
|
||||
c.WorkerNumber = common.PtrInt32(10)
|
||||
}
|
||||
if c.CRDEstablishedCheckIntervalSec == nil {
|
||||
c.CRDEstablishedCheckIntervalSec = common.PtrInt64(1)
|
||||
}
|
||||
if c.CRDEstablishedCheckTimeoutSec == nil {
|
||||
c.CRDEstablishedCheckTimeoutSec = common.PtrInt64(60)
|
||||
}
|
||||
if c.ObjectLocalCacheCreationTimeoutSec == nil {
|
||||
// Default to k8s.io/kubernetes/pkg/controller.ExpectationsTimeout
|
||||
c.ObjectLocalCacheCreationTimeoutSec = common.PtrInt64(5 * 60)
|
||||
}
|
||||
if c.FrameworkMinRetryDelaySecForTransientConflictFailed == nil {
|
||||
c.FrameworkMinRetryDelaySecForTransientConflictFailed = common.PtrInt64(60)
|
||||
}
|
||||
if c.FrameworkMaxRetryDelaySecForTransientConflictFailed == nil {
|
||||
c.FrameworkMaxRetryDelaySecForTransientConflictFailed = common.PtrInt64(15 * 60)
|
||||
}
|
||||
|
||||
// Validation
|
||||
errPrefix := "ControllerConfig Validation Failed: "
|
||||
if *c.WorkerNumber <= 0 {
|
||||
panic(fmt.Errorf(errPrefix+
|
||||
"WorkerNumber %v should be positive",
|
||||
*c.WorkerNumber))
|
||||
}
|
||||
if *c.CRDEstablishedCheckIntervalSec < 1 {
|
||||
panic(fmt.Errorf(errPrefix+
|
||||
"CRDEstablishedCheckIntervalSec %v should not be less than 1",
|
||||
*c.CRDEstablishedCheckIntervalSec))
|
||||
}
|
||||
if *c.CRDEstablishedCheckTimeoutSec < 10 {
|
||||
panic(fmt.Errorf(errPrefix+
|
||||
"CRDEstablishedCheckTimeoutSec %v should not be less than 10",
|
||||
*c.CRDEstablishedCheckTimeoutSec))
|
||||
}
|
||||
if *c.ObjectLocalCacheCreationTimeoutSec < 60 {
|
||||
panic(fmt.Errorf(errPrefix+
|
||||
"ObjectLocalCacheCreationTimeoutSec %v should not be less than 60",
|
||||
*c.ObjectLocalCacheCreationTimeoutSec))
|
||||
}
|
||||
if *c.FrameworkMinRetryDelaySecForTransientConflictFailed < 0 {
|
||||
panic(fmt.Errorf(errPrefix+
|
||||
"FrameworkMinRetryDelaySecForTransientConflictFailed %v should not be negative",
|
||||
*c.FrameworkMinRetryDelaySecForTransientConflictFailed))
|
||||
}
|
||||
if *c.FrameworkMaxRetryDelaySecForTransientConflictFailed <
|
||||
*c.FrameworkMinRetryDelaySecForTransientConflictFailed {
|
||||
panic(fmt.Errorf(errPrefix+
|
||||
"FrameworkMaxRetryDelaySecForTransientConflictFailed %v should not be less than "+
|
||||
"FrameworkMinRetryDelaySecForTransientConflictFailed %v",
|
||||
*c.FrameworkMaxRetryDelaySecForTransientConflictFailed,
|
||||
*c.FrameworkMinRetryDelaySecForTransientConflictFailed))
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func defaultKubeConfigFilePath() *string {
|
||||
configPath := os.Getenv("KUBECONFIG")
|
||||
_, err := os.Stat(configPath)
|
||||
if err == nil {
|
||||
return &configPath
|
||||
}
|
||||
|
||||
configPath = os.Getenv("HOME") + "/.kube/config"
|
||||
_, err = os.Stat(configPath)
|
||||
if err == nil {
|
||||
return &configPath
|
||||
}
|
||||
|
||||
configPath = ""
|
||||
return &configPath
|
||||
}
|
||||
|
||||
func initControllerConfig() *ControllerConfig {
|
||||
c := ControllerConfig{}
|
||||
|
||||
yamlBytes, err := ioutil.ReadFile(ControllerConfigFile)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf(
|
||||
"Failed to Read ControllerConfigFile: %v, %v", ControllerConfigFile, err))
|
||||
}
|
||||
|
||||
common.FromYaml(string(yamlBytes), &c)
|
||||
return &c
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// General Constants
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
const (
|
||||
GroupName = "frameworkcontroller.microsoft.com"
|
||||
Version = "v1"
|
||||
FrameworkPlural = "frameworks"
|
||||
FrameworkCRDName = FrameworkPlural + "." + GroupName
|
||||
FrameworkKind = "Framework"
|
||||
ConfigMapKind = "ConfigMap"
|
||||
ControllerName = "frameworkcontroller"
|
||||
ObjectUIDFieldPath = "metadata.uid"
|
||||
|
||||
ControllerConfigFile = "./frameworkcontroller.yaml"
|
||||
UnlimitedValue = -1
|
||||
ExtendedUnlimitedValue = -2
|
||||
|
||||
// For all managed objects
|
||||
AnnotationKeyFrameworkName = "FRAMEWORK_NAME"
|
||||
AnnotationKeyTaskRoleName = "TASKROLE_NAME"
|
||||
AnnotationKeyTaskIndex = "TASK_INDEX"
|
||||
AnnotationKeyConfigMapName = "CONFIGMAP_NAME"
|
||||
AnnotationKeyPodName = "POD_NAME"
|
||||
AnnotationKeyPodNamespace = "POD_NAMESPACE"
|
||||
|
||||
AnnotationKeyFrameworkAttemptID = "FRAMEWORK_ATTEMPT_ID"
|
||||
AnnotationKeyFrameworkAttemptInstanceUID = "FRAMEWORK_ATTEMPT_INSTANCE_UID"
|
||||
AnnotationKeyConfigMapUID = "CONFIGMAP_UID"
|
||||
AnnotationKeyTaskAttemptID = "TASK_ATTEMPT_ID"
|
||||
|
||||
LabelKeyFrameworkName = AnnotationKeyFrameworkName
|
||||
LabelKeyTaskRoleName = AnnotationKeyTaskRoleName
|
||||
|
||||
// For all managed containers
|
||||
EnvNameFrameworkName = AnnotationKeyFrameworkName
|
||||
EnvNameTaskRoleName = AnnotationKeyTaskRoleName
|
||||
EnvNameTaskIndex = AnnotationKeyTaskIndex
|
||||
EnvNameConfigMapName = AnnotationKeyConfigMapName
|
||||
EnvNamePodName = AnnotationKeyPodName
|
||||
EnvNamePodNamespace = AnnotationKeyPodNamespace
|
||||
|
||||
EnvNameFrameworkAttemptID = AnnotationKeyFrameworkAttemptID
|
||||
EnvNameFrameworkAttemptInstanceUID = AnnotationKeyFrameworkAttemptInstanceUID
|
||||
EnvNameConfigMapUID = AnnotationKeyConfigMapUID
|
||||
EnvNameTaskAttemptID = AnnotationKeyTaskAttemptID
|
||||
EnvNameTaskAttemptInstanceUID = "TASK_ATTEMPT_INSTANCE_UID"
|
||||
EnvNamePodUID = "POD_UID"
|
||||
)
|
||||
|
||||
var FrameworkGroupVersionKind = SchemeGroupVersion.WithKind(FrameworkKind)
|
||||
var ConfigMapGroupVersionKind = core.SchemeGroupVersion.WithKind(ConfigMapKind)
|
||||
var ObjectUIDEnvVarSource = &core.EnvVarSource{
|
||||
FieldRef: &core.ObjectFieldSelector{FieldPath: ObjectUIDFieldPath},
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// CompletionCodeInfos
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
type CompletionCodeInfo struct {
|
||||
Phrase CompletionPhrase
|
||||
Type CompletionType
|
||||
}
|
||||
|
||||
type ContainerStateTerminatedReason string
|
||||
|
||||
const (
|
||||
ReasonOOMKilled ContainerStateTerminatedReason = "OOMKilled"
|
||||
)
|
||||
|
||||
// Defined according to the CompletionCode Convention.
|
||||
// See CompletionStatus.
|
||||
const (
|
||||
// NonNegative:
|
||||
// ExitCode of the Framework's Container
|
||||
// [129, 165]: Container Received Fatal Error Signal: ExitCode - 128
|
||||
CompletionCodeContainerSigTermReceived CompletionCode = 143
|
||||
CompletionCodeContainerSigKillReceived CompletionCode = 137
|
||||
CompletionCodeContainerSigIntReceived CompletionCode = 130
|
||||
// [200, 219]: Container ExitCode Contract
|
||||
CompletionCodeContainerTransientFailed CompletionCode = 200
|
||||
CompletionCodeContainerTransientConflictFailed CompletionCode = 201
|
||||
CompletionCodeContainerPermanentFailed CompletionCode = 210
|
||||
// 0: Succeeded
|
||||
CompletionCodeSucceeded CompletionCode = 0
|
||||
|
||||
// Negative:
|
||||
// ExitCode of the Framework's Predefined Error
|
||||
// -1XX: Framework Predefined Transient Error
|
||||
CompletionCodeConfigMapExternalDeleted CompletionCode = -100
|
||||
CompletionCodePodExternalDeleted CompletionCode = -101
|
||||
CompletionCodeConfigMapCreationTimeout CompletionCode = -110
|
||||
CompletionCodePodCreationTimeout CompletionCode = -111
|
||||
CompletionCodePodFailedWithoutFailedContainer CompletionCode = -120
|
||||
// -2XX: Framework Predefined Permanent Error
|
||||
CompletionCodePodSpecInvalid CompletionCode = -200
|
||||
// -3XX: Framework Predefined Unknown Error
|
||||
CompletionCodeContainerOOMKilled CompletionCode = -300
|
||||
)
|
||||
|
||||
var CompletionCodeInfos = map[CompletionCode]CompletionCodeInfo{
|
||||
CompletionCodeContainerSigTermReceived: {
|
||||
"ContainerSigTermReceived", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodeContainerSigKillReceived: {
|
||||
"ContainerSigKillReceived", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodeContainerSigIntReceived: {
|
||||
"ContainerSigIntReceived", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodeContainerTransientFailed: {
|
||||
"ContainerTransientFailed", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeInternal}}},
|
||||
CompletionCodeContainerTransientConflictFailed: {
|
||||
"ContainerTransientConflictFailed", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeInternal,
|
||||
CompletionTypeAttributeConflict}}},
|
||||
CompletionCodeContainerPermanentFailed: {
|
||||
"ContainerPermanentFailed", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributePermanent, CompletionTypeAttributeInternal}}},
|
||||
CompletionCodeSucceeded: {
|
||||
"Succeeded", CompletionType{CompletionTypeNameSucceeded,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeInternal}}},
|
||||
CompletionCodeConfigMapExternalDeleted: {
|
||||
"ConfigMapExternalDeleted", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodePodExternalDeleted: {
|
||||
// Possibly be due to Pod Eviction.
|
||||
"PodExternalDeleted", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodeConfigMapCreationTimeout: {
|
||||
"ConfigMapCreationTimeout", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodePodCreationTimeout: {
|
||||
"PodCreationTimeout", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodePodFailedWithoutFailedContainer: {
|
||||
"PodFailedWithoutFailedContainer", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributeTransient, CompletionTypeAttributeExternal}}},
|
||||
CompletionCodePodSpecInvalid: {
|
||||
"PodSpecInvalid", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{CompletionTypeAttributePermanent, CompletionTypeAttributeInternal}}},
|
||||
CompletionCodeContainerOOMKilled: {
|
||||
// May be due to exceed the Container memory limit or the Container workload spike or
|
||||
// OS memory pressure, so it may be Permanent or Transient, Internal or External.
|
||||
"ContainerOOMKilled", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{}}},
|
||||
}
|
||||
|
||||
var CompletionCodeInfoContainerFailedWithUnknownExitCode = CompletionCodeInfo{
|
||||
"ContainerFailedWithUnknownExitCode", CompletionType{CompletionTypeNameFailed,
|
||||
[]CompletionTypeAttribute{}},
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
apiExtensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"github.com/microsoft/frameworkcontroller/pkg/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// Names in CRD should be up to 63 lower case alphanumeric characters.
|
||||
NamingConvention = "^[a-z0-9]{1,63}$"
|
||||
)
|
||||
|
||||
func BuildFrameworkCRD() *apiExtensions.CustomResourceDefinition {
|
||||
crd := &apiExtensions.CustomResourceDefinition{
|
||||
ObjectMeta: meta.ObjectMeta{
|
||||
Name: FrameworkCRDName,
|
||||
},
|
||||
Spec: apiExtensions.CustomResourceDefinitionSpec{
|
||||
Group: GroupName,
|
||||
Version: SchemeGroupVersion.Version,
|
||||
Scope: apiExtensions.NamespaceScoped,
|
||||
Names: apiExtensions.CustomResourceDefinitionNames{
|
||||
Plural: FrameworkPlural,
|
||||
Kind: FrameworkKind,
|
||||
},
|
||||
Validation: buildFrameworkValidation(),
|
||||
// TODO: Enable CRD Subresources after ApiServer has supported it.
|
||||
//Subresources: &apiExtensions.CustomResourceSubresources{
|
||||
// Status: &apiExtensions.CustomResourceSubresourceStatus{
|
||||
// },
|
||||
//},
|
||||
},
|
||||
}
|
||||
|
||||
return crd
|
||||
}
|
||||
|
||||
func buildFrameworkValidation() *apiExtensions.CustomResourceValidation {
|
||||
return &apiExtensions.CustomResourceValidation{
|
||||
OpenAPIV3Schema: &apiExtensions.JSONSchemaProps{
|
||||
Properties: map[string]apiExtensions.JSONSchemaProps{
|
||||
"metadata": {
|
||||
Properties: map[string]apiExtensions.JSONSchemaProps{
|
||||
"name": {
|
||||
Type: "string",
|
||||
Pattern: NamingConvention,
|
||||
},
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
Properties: map[string]apiExtensions.JSONSchemaProps{
|
||||
"executionType": {
|
||||
Enum: []apiExtensions.JSON{
|
||||
{Raw: []byte(common.Quote(string(ExecutionStart)))},
|
||||
{Raw: []byte(common.Quote(string(ExecutionStop)))},
|
||||
},
|
||||
},
|
||||
"retryPolicy": {
|
||||
Properties: map[string]apiExtensions.JSONSchemaProps{
|
||||
"maxRetryCount": {
|
||||
Type: "integer",
|
||||
Minimum: common.PtrFloat64(ExtendedUnlimitedValue),
|
||||
},
|
||||
},
|
||||
},
|
||||
"taskRoles": {
|
||||
// TODO: names in array should not duplicate
|
||||
Type: "array",
|
||||
Items: &apiExtensions.JSONSchemaPropsOrArray{
|
||||
Schema: &apiExtensions.JSONSchemaProps{
|
||||
Properties: map[string]apiExtensions.JSONSchemaProps{
|
||||
"name": {
|
||||
Type: "string",
|
||||
Pattern: NamingConvention,
|
||||
},
|
||||
"taskNumber": {
|
||||
Type: "integer",
|
||||
Minimum: common.PtrFloat64(0),
|
||||
Maximum: common.PtrFloat64(10000),
|
||||
},
|
||||
"frameworkAttemptCompletionPolicy": {
|
||||
Properties: map[string]apiExtensions.JSONSchemaProps{
|
||||
"minFailedTaskCount": {
|
||||
Type: "integer",
|
||||
// TODO: should not allow 0
|
||||
Minimum: common.PtrFloat64(UnlimitedValue),
|
||||
},
|
||||
"minSucceededTaskCount": {
|
||||
Type: "integer",
|
||||
// TODO: should not allow 0
|
||||
Minimum: common.PtrFloat64(UnlimitedValue),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +groupName=frameworkcontroller.microsoft.com
|
||||
package v1
|
|
@ -0,0 +1,551 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"strconv"
|
||||
log "github.com/sirupsen/logrus"
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"github.com/microsoft/frameworkcontroller/pkg/common"
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
func GetConfigMapName(frameworkName string) string {
|
||||
return strings.Join([]string{frameworkName, "attempt"}, "-")
|
||||
}
|
||||
|
||||
func SplitConfigMapName(configMapName string) (frameworkName string) {
|
||||
parts := strings.Split(configMapName, "-")
|
||||
return parts[0]
|
||||
}
|
||||
|
||||
func GetPodName(frameworkName string, taskRoleName string, taskIndex int32) string {
|
||||
return strings.Join([]string{frameworkName, taskRoleName, fmt.Sprint(taskIndex)}, "-")
|
||||
}
|
||||
|
||||
func SplitPodName(podName string) (frameworkName string, taskRoleName string, taskIndex int32) {
|
||||
parts := strings.Split(podName, "-")
|
||||
i, err := strconv.ParseInt(parts[2], 10, 32)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to SplitPodName %v: %v", podName, err))
|
||||
}
|
||||
return parts[0], parts[1], int32(i)
|
||||
}
|
||||
|
||||
func GetFrameworkAttemptInstanceUID(frameworkAttemptID int32, configMapUID *types.UID) *types.UID {
|
||||
return common.PtrUIDStr(fmt.Sprintf("%v_%v", frameworkAttemptID, *configMapUID))
|
||||
}
|
||||
|
||||
func SplitFrameworkAttemptInstanceUID(frameworkAttemptInstanceUID *types.UID) (
|
||||
frameworkAttemptID int32, configMapUID *types.UID) {
|
||||
parts := strings.Split(string(*frameworkAttemptInstanceUID), "_")
|
||||
i, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf(
|
||||
"Failed to SplitFrameworkAttemptInstanceUID %v: %v",
|
||||
*frameworkAttemptInstanceUID, err))
|
||||
}
|
||||
return int32(i), common.PtrUIDStr(parts[1])
|
||||
}
|
||||
|
||||
func GetTaskAttemptInstanceUID(taskAttemptID int32, podUID *types.UID) *types.UID {
|
||||
return common.PtrUIDStr(fmt.Sprintf("%v_%v", taskAttemptID, *podUID))
|
||||
}
|
||||
|
||||
func SplitTaskAttemptInstanceUID(taskAttemptInstanceUID *types.UID) (
|
||||
taskAttemptID int32, podUID *types.UID) {
|
||||
parts := strings.Split(string(*taskAttemptInstanceUID), "_")
|
||||
i, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf(
|
||||
"Failed to SplitTaskAttemptInstanceUID %v: %v",
|
||||
*taskAttemptInstanceUID, err))
|
||||
}
|
||||
return int32(i), common.PtrUIDStr(parts[1])
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Interfaces
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
type TaskStatusSelector func(*TaskStatus) bool
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Spec Read Methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
func (f *Framework) Key() string {
|
||||
return f.Namespace + "/" + f.Name
|
||||
}
|
||||
|
||||
func (f *Framework) TaskRoleSpec(taskRoleName string) *TaskRoleSpec {
|
||||
for _, taskRole := range f.Spec.TaskRoles {
|
||||
if taskRole.Name == taskRoleName {
|
||||
return &taskRole
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("[%v]: TaskRole is not found in Spec", taskRoleName))
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Status Read Methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
func (f *Framework) FrameworkAttemptID() int32 {
|
||||
return f.Status.AttemptStatus.ID
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) TaskAttemptID() int32 {
|
||||
return ts.AttemptStatus.ID
|
||||
}
|
||||
|
||||
func (f *Framework) FrameworkAttemptInstanceUID() *types.UID {
|
||||
return f.Status.AttemptStatus.InstanceUID
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) TaskAttemptInstanceUID() *types.UID {
|
||||
return ts.AttemptStatus.InstanceUID
|
||||
}
|
||||
|
||||
func (f *Framework) ConfigMapName() string {
|
||||
return f.Status.AttemptStatus.ConfigMapName
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) PodName() string {
|
||||
return ts.AttemptStatus.PodName
|
||||
}
|
||||
|
||||
func (f *Framework) ConfigMapUID() *types.UID {
|
||||
return f.Status.AttemptStatus.ConfigMapUID
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) PodUID() *types.UID {
|
||||
return ts.AttemptStatus.PodUID
|
||||
}
|
||||
|
||||
func (f *Framework) CompletionType() CompletionType {
|
||||
return f.Status.AttemptStatus.CompletionStatus.Type
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) CompletionType() CompletionType {
|
||||
return ts.AttemptStatus.CompletionStatus.Type
|
||||
}
|
||||
|
||||
func (f *Framework) TaskRoleStatuses() []TaskRoleStatus {
|
||||
return f.Status.AttemptStatus.TaskRoleStatuses
|
||||
}
|
||||
|
||||
func (f *Framework) TaskRoleStatus(taskRoleName string) *TaskRoleStatus {
|
||||
for _, taskRoleStatus := range f.TaskRoleStatuses() {
|
||||
if taskRoleStatus.Name == taskRoleName {
|
||||
return &taskRoleStatus
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("[%v]: TaskRole is not found in Status", taskRoleName))
|
||||
}
|
||||
|
||||
func (f *Framework) TaskStatus(taskRoleName string, taskIndex int32) *TaskStatus {
|
||||
taskRoleStatus := f.TaskRoleStatus(taskRoleName)
|
||||
if 0 <= taskIndex && taskIndex < int32(len(taskRoleStatus.TaskStatuses)) {
|
||||
return &taskRoleStatus.TaskStatuses[taskIndex]
|
||||
}
|
||||
panic(fmt.Errorf("[%v][%v]: Task is not found in Status", taskRoleName, taskIndex))
|
||||
}
|
||||
|
||||
func (f *Framework) IsCompleted() bool {
|
||||
return f.Status.State == FrameworkCompleted
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) IsCompleted() bool {
|
||||
return ts.State == TaskCompleted
|
||||
}
|
||||
|
||||
func (ct CompletionType) IsSucceeded() bool {
|
||||
return ct.Name == CompletionTypeNameSucceeded
|
||||
}
|
||||
|
||||
func (f *Framework) IsSucceeded() bool {
|
||||
return f.IsCompleted() && f.CompletionType().IsSucceeded()
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) IsSucceeded() bool {
|
||||
return ts.IsCompleted() && ts.CompletionType().IsSucceeded()
|
||||
}
|
||||
|
||||
func (ct CompletionType) IsFailed() bool {
|
||||
return ct.Name == CompletionTypeNameFailed
|
||||
}
|
||||
|
||||
func (f *Framework) IsFailed() bool {
|
||||
return f.IsCompleted() && f.CompletionType().IsFailed()
|
||||
}
|
||||
|
||||
func (ts *TaskStatus) IsFailed() bool {
|
||||
return ts.IsCompleted() && ts.CompletionType().IsFailed()
|
||||
}
|
||||
|
||||
func (ct CompletionType) ContainsAttribute(attribute CompletionTypeAttribute) bool {
|
||||
for i := range ct.Attributes {
|
||||
if ct.Attributes[i] == attribute {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (trs *TaskRoleStatus) GetTaskStatuses(selector TaskStatusSelector) []TaskStatus {
|
||||
if selector == nil {
|
||||
return trs.TaskStatuses
|
||||
}
|
||||
|
||||
taskStatuses := []TaskStatus{}
|
||||
for _, taskStatus := range trs.TaskStatuses {
|
||||
if selector(&taskStatus) {
|
||||
taskStatuses = append(taskStatuses, taskStatus)
|
||||
}
|
||||
}
|
||||
return taskStatuses
|
||||
}
|
||||
|
||||
func (trs *TaskRoleStatus) GetTaskCount(selector TaskStatusSelector) int32 {
|
||||
return int32(len(trs.GetTaskStatuses(selector)))
|
||||
}
|
||||
|
||||
func (f *Framework) GetTaskCount(selector TaskStatusSelector) int32 {
|
||||
taskCount := int32(0)
|
||||
for _, taskRoleStatus := range f.TaskRoleStatuses() {
|
||||
taskCount += taskRoleStatus.GetTaskCount(selector)
|
||||
}
|
||||
return taskCount
|
||||
}
|
||||
|
||||
func (f *Framework) AreAllTasksCompleted() bool {
|
||||
return f.GetTaskCount(nil) == f.GetTaskCount((*TaskStatus).IsCompleted)
|
||||
}
|
||||
|
||||
func (f *Framework) NewConfigMap() *core.ConfigMap {
|
||||
frameworkAttemptIDStr := fmt.Sprint(f.FrameworkAttemptID())
|
||||
|
||||
cm := &core.ConfigMap{
|
||||
ObjectMeta: meta.ObjectMeta{},
|
||||
}
|
||||
|
||||
// Init ConfigMap
|
||||
cm.Name = f.ConfigMapName()
|
||||
cm.Namespace = f.Namespace
|
||||
cm.OwnerReferences = []meta.OwnerReference{*meta.NewControllerRef(f, FrameworkGroupVersionKind)}
|
||||
// By default, ensure there is no timeline overlap among different FrameworkAttemptInstances.
|
||||
cm.Finalizers = []string{meta.FinalizerDeleteDependents}
|
||||
|
||||
cm.Annotations = map[string]string{}
|
||||
cm.Annotations[AnnotationKeyFrameworkName] = f.Name
|
||||
cm.Annotations[AnnotationKeyConfigMapName] = cm.Name
|
||||
cm.Annotations[AnnotationKeyFrameworkAttemptID] = frameworkAttemptIDStr
|
||||
|
||||
cm.Labels = map[string]string{}
|
||||
cm.Labels[LabelKeyFrameworkName] = f.Name
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
func (f *Framework) NewPod(cm *core.ConfigMap, taskRoleName string, taskIndex int32) *core.Pod {
|
||||
taskPod := f.TaskRoleSpec(taskRoleName).Task.Pod.DeepCopy()
|
||||
taskStatus := f.TaskStatus(taskRoleName, taskIndex)
|
||||
taskIndexStr := fmt.Sprint(taskIndex)
|
||||
frameworkAttemptIDStr := fmt.Sprint(f.FrameworkAttemptID())
|
||||
frameworkAttemptInstanceUIDStr := string(*f.FrameworkAttemptInstanceUID())
|
||||
configMapUIDStr := string(*f.ConfigMapUID())
|
||||
taskAttemptIDStr := fmt.Sprint(taskStatus.TaskAttemptID())
|
||||
taskAttemptInstanceUIDReferStr := string(*GetTaskAttemptInstanceUID(
|
||||
taskStatus.TaskAttemptID(),
|
||||
common.PtrUIDStr(common.ReferEnvVar(EnvNamePodUID))))
|
||||
|
||||
pod := &core.Pod{
|
||||
ObjectMeta: taskPod.ObjectMeta,
|
||||
Spec: taskPod.Spec,
|
||||
}
|
||||
|
||||
// Rewrite Task.Pod
|
||||
pod.Name = taskStatus.PodName()
|
||||
pod.Namespace = f.Namespace
|
||||
|
||||
// Augment Task.Pod
|
||||
// By setting the owner is also a controller, the Pod functions normally, except for
|
||||
// its detail cannot be viewed from k8s dashboard, and the dashboard shows error:
|
||||
// "Unknown reference kind ConfigMap".
|
||||
// See https://github.com/kubernetes/dashboard/issues/3158
|
||||
if pod.OwnerReferences == nil {
|
||||
pod.OwnerReferences = []meta.OwnerReference{}
|
||||
}
|
||||
pod.OwnerReferences = append(pod.OwnerReferences, *meta.NewControllerRef(cm, ConfigMapGroupVersionKind))
|
||||
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = map[string]string{}
|
||||
}
|
||||
pod.Annotations[AnnotationKeyFrameworkName] = f.Name
|
||||
pod.Annotations[AnnotationKeyTaskRoleName] = taskRoleName
|
||||
pod.Annotations[AnnotationKeyTaskIndex] = taskIndexStr
|
||||
pod.Annotations[AnnotationKeyConfigMapName] = f.ConfigMapName()
|
||||
pod.Annotations[AnnotationKeyPodName] = pod.Name
|
||||
pod.Annotations[AnnotationKeyPodNamespace] = pod.Namespace
|
||||
pod.Annotations[AnnotationKeyFrameworkAttemptID] = frameworkAttemptIDStr
|
||||
pod.Annotations[AnnotationKeyFrameworkAttemptInstanceUID] = frameworkAttemptInstanceUIDStr
|
||||
pod.Annotations[AnnotationKeyConfigMapUID] = configMapUIDStr
|
||||
pod.Annotations[AnnotationKeyTaskAttemptID] = taskAttemptIDStr
|
||||
|
||||
if pod.Labels == nil {
|
||||
pod.Labels = map[string]string{}
|
||||
}
|
||||
pod.Labels[LabelKeyFrameworkName] = f.Name
|
||||
pod.Labels[LabelKeyTaskRoleName] = taskRoleName
|
||||
|
||||
exEnvs := []core.EnvVar{
|
||||
{Name: EnvNameFrameworkName, Value: f.Name},
|
||||
{Name: EnvNameTaskRoleName, Value: taskRoleName},
|
||||
{Name: EnvNameTaskIndex, Value: taskIndexStr},
|
||||
{Name: EnvNameConfigMapName, Value: f.ConfigMapName()},
|
||||
{Name: EnvNamePodName, Value: pod.Name},
|
||||
{Name: EnvNamePodNamespace, Value: pod.Namespace},
|
||||
{Name: EnvNameFrameworkAttemptID, Value: frameworkAttemptIDStr},
|
||||
{Name: EnvNameFrameworkAttemptInstanceUID, Value: frameworkAttemptInstanceUIDStr},
|
||||
{Name: EnvNameConfigMapUID, Value: configMapUIDStr},
|
||||
{Name: EnvNameTaskAttemptID, Value: taskAttemptIDStr},
|
||||
{Name: EnvNamePodUID, ValueFrom: ObjectUIDEnvVarSource},
|
||||
{Name: EnvNameTaskAttemptInstanceUID, Value: taskAttemptInstanceUIDReferStr},
|
||||
}
|
||||
|
||||
// Change the default TerminationMessagePolicy to TerminationMessageFallbackToLogsOnError
|
||||
// in case the cluster-level logging has not been setup for the cluster.
|
||||
// See https://kubernetes.io/docs/concepts/cluster-administration/logging
|
||||
// It is safe to do so, since it will only fall back to the tail log if the container
|
||||
// is failed and the termination message file specified by the terminationMessagePath
|
||||
// is not found or empty.
|
||||
for i := range pod.Spec.Containers {
|
||||
for _, exEnv := range exEnvs {
|
||||
pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, exEnv)
|
||||
}
|
||||
if len(pod.Spec.Containers[i].TerminationMessagePolicy) == 0 {
|
||||
pod.Spec.Containers[i].TerminationMessagePolicy = core.TerminationMessageFallbackToLogsOnError
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.InitContainers {
|
||||
for _, exEnv := range exEnvs {
|
||||
pod.Spec.InitContainers[i].Env = append(pod.Spec.InitContainers[i].Env, exEnv)
|
||||
}
|
||||
if len(pod.Spec.InitContainers[i].TerminationMessagePolicy) == 0 {
|
||||
pod.Spec.InitContainers[i].TerminationMessagePolicy = core.TerminationMessageFallbackToLogsOnError
|
||||
}
|
||||
}
|
||||
|
||||
return pod
|
||||
}
|
||||
|
||||
func (f *Framework) NewFrameworkStatus() *FrameworkStatus {
|
||||
return &FrameworkStatus{
|
||||
StartTime: meta.Now(),
|
||||
CompletionTime: nil,
|
||||
State: FrameworkAttemptCreationPending,
|
||||
TransitionTime: meta.Now(),
|
||||
RetryPolicyStatus: RetryPolicyStatus{
|
||||
TotalRetriedCount: 0,
|
||||
AccountableRetriedCount: 0,
|
||||
RetryDelaySec: nil,
|
||||
},
|
||||
AttemptStatus: f.NewFrameworkAttemptStatus(0),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Framework) NewFrameworkAttemptStatus(
|
||||
frameworkAttemptID int32) FrameworkAttemptStatus {
|
||||
return FrameworkAttemptStatus{
|
||||
ID: frameworkAttemptID,
|
||||
StartTime: meta.Now(),
|
||||
CompletionTime: nil,
|
||||
InstanceUID: nil,
|
||||
ConfigMapName: GetConfigMapName(f.Name),
|
||||
ConfigMapUID: nil,
|
||||
CompletionStatus: nil,
|
||||
TaskRoleStatuses: f.NewTaskRoleStatuses(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Framework) NewTaskRoleStatuses() []TaskRoleStatus {
|
||||
trss := []TaskRoleStatus{}
|
||||
for _, taskRole := range f.Spec.TaskRoles {
|
||||
trs := TaskRoleStatus{Name: taskRole.Name, TaskStatuses: []TaskStatus{}}
|
||||
for taskIndex := int32(0); taskIndex < taskRole.TaskNumber; taskIndex++ {
|
||||
trs.TaskStatuses = append(trs.TaskStatuses, f.NewTaskStatus(taskRole.Name, taskIndex))
|
||||
}
|
||||
trss = append(trss, trs)
|
||||
}
|
||||
return trss
|
||||
}
|
||||
|
||||
func (f *Framework) NewTaskStatus(taskRoleName string, taskIndex int32) TaskStatus {
|
||||
return TaskStatus{
|
||||
Index: taskIndex,
|
||||
StartTime: meta.Now(),
|
||||
CompletionTime: nil,
|
||||
State: TaskAttemptCreationPending,
|
||||
TransitionTime: meta.Now(),
|
||||
RetryPolicyStatus: RetryPolicyStatus{
|
||||
TotalRetriedCount: 0,
|
||||
AccountableRetriedCount: 0,
|
||||
RetryDelaySec: nil,
|
||||
},
|
||||
AttemptStatus: f.NewTaskAttemptStatus(taskRoleName, taskIndex, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Framework) NewTaskAttemptStatus(
|
||||
taskRoleName string, taskIndex int32, taskAttemptID int32) TaskAttemptStatus {
|
||||
return TaskAttemptStatus{
|
||||
ID: taskAttemptID,
|
||||
StartTime: meta.Now(),
|
||||
CompletionTime: nil,
|
||||
InstanceUID: nil,
|
||||
PodName: GetPodName(f.Name, taskRoleName, taskIndex),
|
||||
PodUID: nil,
|
||||
PodIP: nil,
|
||||
PodHostIP: nil,
|
||||
CompletionStatus: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (cc CompletionCode) NewCompletionStatus(diagnostics string) *CompletionStatus {
|
||||
cci, exists := CompletionCodeInfos[cc]
|
||||
if !exists {
|
||||
cci = CompletionCodeInfoContainerFailedWithUnknownExitCode
|
||||
}
|
||||
return &CompletionStatus{
|
||||
Code: cc,
|
||||
Phrase: cci.Phrase,
|
||||
Type: cci.Type,
|
||||
Diagnostics: diagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *CompletionStatus) String() string {
|
||||
return fmt.Sprintf(
|
||||
"[Code: %v, Phrase: %v, Type: %v, Diagnostics: %v]",
|
||||
cs.Code, cs.Phrase, cs.Type, cs.Diagnostics)
|
||||
}
|
||||
|
||||
type RetryDecision struct {
|
||||
ShouldRetry bool
|
||||
// Whether the retry should be counted into AccountableRetriedCount
|
||||
IsAccountable bool
|
||||
// The retry should be executed after DelaySec.
|
||||
DelaySec int64
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (rd RetryDecision) String() string {
|
||||
return fmt.Sprintf(
|
||||
"[ShouldRetry: %v, IsAccountable: %v, DelaySec: %v, Reason: %v]",
|
||||
rd.ShouldRetry, rd.IsAccountable, rd.DelaySec, rd.Reason)
|
||||
}
|
||||
|
||||
func (rp RetryPolicySpec) ShouldRetry(
|
||||
rps RetryPolicyStatus,
|
||||
ct CompletionType,
|
||||
minDelaySecForTransientConflictFailed int64,
|
||||
maxDelaySecForTransientConflictFailed int64) RetryDecision {
|
||||
// 1. FancyRetryPolicy
|
||||
if rp.FancyRetryPolicy {
|
||||
reason := fmt.Sprintf(
|
||||
"FancyRetryPolicy is %v and CompletionType is %v",
|
||||
rp.FancyRetryPolicy, ct)
|
||||
if ct.IsFailed() {
|
||||
if ct.ContainsAttribute(CompletionTypeAttributeTransient) {
|
||||
if ct.ContainsAttribute(CompletionTypeAttributeConflict) {
|
||||
return RetryDecision{true, false,
|
||||
common.RandInt64(
|
||||
minDelaySecForTransientConflictFailed,
|
||||
maxDelaySecForTransientConflictFailed),
|
||||
reason}
|
||||
} else {
|
||||
return RetryDecision{true, false, 0, reason}
|
||||
}
|
||||
} else if ct.ContainsAttribute(CompletionTypeAttributePermanent) {
|
||||
return RetryDecision{false, true, 0, reason}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. NormalRetryPolicy
|
||||
if (rp.MaxRetryCount == ExtendedUnlimitedValue) ||
|
||||
(ct.IsFailed() && rp.MaxRetryCount == UnlimitedValue) ||
|
||||
(ct.IsFailed() && rps.AccountableRetriedCount < rp.MaxRetryCount) {
|
||||
return RetryDecision{true, true, 0, fmt.Sprintf(
|
||||
"AccountableRetriedCount %v has not reached MaxRetryCount %v",
|
||||
rps.AccountableRetriedCount, rp.MaxRetryCount)}
|
||||
} else {
|
||||
if ct.IsSucceeded() {
|
||||
return RetryDecision{false, true, 0, fmt.Sprintf(
|
||||
"CompletionType is %v", ct)}
|
||||
} else {
|
||||
return RetryDecision{false, true, 0, fmt.Sprintf(
|
||||
"AccountableRetriedCount %v has reached MaxRetryCount %v",
|
||||
rps.AccountableRetriedCount, rp.MaxRetryCount)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// Status Write Methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// This is the only interface to modify FrameworkState
|
||||
func (f *Framework) TransitionFrameworkState(dstState FrameworkState) {
|
||||
srcState := f.Status.State
|
||||
if srcState == dstState {
|
||||
return
|
||||
}
|
||||
|
||||
f.Status.State = dstState
|
||||
f.Status.TransitionTime = meta.Now()
|
||||
|
||||
log.Infof(
|
||||
"[%v]: Transitioned Framework from [%v] to [%v]",
|
||||
f.Key(), srcState, dstState)
|
||||
}
|
||||
|
||||
// This is the only interface to modify TaskState
|
||||
func (f *Framework) TransitionTaskState(
|
||||
taskRoleName string, taskIndex int32, dstState TaskState) {
|
||||
taskStatus := f.TaskStatus(taskRoleName, taskIndex)
|
||||
srcState := taskStatus.State
|
||||
if srcState == dstState {
|
||||
return
|
||||
}
|
||||
|
||||
taskStatus.State = dstState
|
||||
taskStatus.TransitionTime = meta.Now()
|
||||
|
||||
log.Infof(
|
||||
"[%v][%v][%v]: Transitioned Task from [%v] to [%v]",
|
||||
f.Key(), taskRoleName, taskIndex, srcState, dstState)
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var SchemeGroupVersion = schema.GroupVersion{
|
||||
Group: GroupName,
|
||||
Version: Version,
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(
|
||||
SchemeGroupVersion,
|
||||
&Framework{},
|
||||
&FrameworkList{},
|
||||
)
|
||||
|
||||
// register the type in the scheme
|
||||
meta.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,531 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type FrameworkList struct {
|
||||
meta.TypeMeta `json:",inline"`
|
||||
meta.ListMeta `json:"metadata"`
|
||||
Items []Framework `json:"items"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:noStatus
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// A Framework represents an application with a set of Tasks:
|
||||
// 1. Executed by Kubernetes Pod
|
||||
// 2. Partitioned to different heterogeneous TaskRoles which share the same lifecycle
|
||||
// 3. Ordered in the same homogeneous TaskRole by TaskIndex
|
||||
// 4. With consistent identity {FrameworkName}-{TaskRoleName}-{TaskIndex} as PodName
|
||||
// 5. With fine grained RetryPolicy for each Task and the whole Framework
|
||||
// 6. With fine grained FrameworkAttemptCompletionPolicy for each TaskRole
|
||||
// 7. Guarantees at most one instance of a specific Task is running at any point in time
|
||||
// 8. Guarantees at most one instance of a specific Framework is running at any point in time
|
||||
//
|
||||
// Notes:
|
||||
// 1. Status field should only be modified by FrameworkController, and
|
||||
// other fields should not be modified by FrameworkController.
|
||||
// TODO: Remove +genclient:noStatus after ApiServer has supported CRD Subresources.
|
||||
// Leverage CRD status subresource to isolate Status field modification with other fields.
|
||||
// This can help to avoid unintended modification, such as users may unintendedly modify
|
||||
// the status when updating the spec.
|
||||
// 2. To ensure at most one instance of a specific Task is running at any point in time:
|
||||
// 1. Do not delete the managed Pod with 0 gracePeriodSeconds.
|
||||
// For example, the default Pod deletion is acceptable.
|
||||
// 2. Do not delete the Node which runs the managed Pod.
|
||||
// For example, drain before delete the Node is acceptable.
|
||||
// The instance can be universally located by its TaskAttemptInstanceUID or PodUID.
|
||||
// See RetryPolicySpec and TaskAttemptStatus.
|
||||
// 3. To ensure at most one instance of a specific Framework is running at any point in time:
|
||||
// 1. Ensure ensure at most one instance of a specific Task is running at any point in time.
|
||||
// 2. Do not delete the managed ConfigMap with Background propagationPolicy.
|
||||
// For example, the default ConfigMap deletion is acceptable.
|
||||
// 3. Must delete the Framework with Foreground propagationPolicy.
|
||||
// For example, the default Framework deletion may not be acceptable, since the default
|
||||
// propagationPolicy for Framework object may be Background.
|
||||
// The instance can be universally located by its FrameworkAttemptInstanceUID or ConfigMapUID.
|
||||
// See RetryPolicySpec and FrameworkAttemptStatus.
|
||||
// 4. To ensure there is no orphan object previously managed by FrameworkController:
|
||||
// 1. Do not delete the Framework or the managed ConfigMap with Orphan propagationPolicy.
|
||||
// For example, the default Framework and ConfigMap deletion is acceptable.
|
||||
// 2. Do not change the OwnerReferences of the managed ConfigMap and Pods.
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
type Framework struct {
|
||||
meta.TypeMeta `json:",inline"`
|
||||
meta.ObjectMeta `json:"metadata"`
|
||||
Spec FrameworkSpec `json:"spec"`
|
||||
Status *FrameworkStatus `json:"status"`
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Spec
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
type FrameworkSpec struct {
|
||||
Description string `json:"description"`
|
||||
ExecutionType ExecutionType `json:"executionType"`
|
||||
RetryPolicy RetryPolicySpec `json:"retryPolicy"`
|
||||
TaskRoles []TaskRoleSpec `json:"taskRoles"`
|
||||
}
|
||||
|
||||
type TaskRoleSpec struct {
|
||||
// TaskRoleName
|
||||
Name string `json:"name"`
|
||||
|
||||
// Tasks with TaskIndex in range [0, TaskNumber)
|
||||
TaskNumber int32 `json:"taskNumber"`
|
||||
FrameworkAttemptCompletionPolicy CompletionPolicySpec `json:"frameworkAttemptCompletionPolicy"`
|
||||
Task TaskSpec `json:"task"`
|
||||
}
|
||||
|
||||
type TaskSpec struct {
|
||||
RetryPolicy RetryPolicySpec `json:"retryPolicy"`
|
||||
Pod core.PodTemplateSpec `json:"pod"`
|
||||
}
|
||||
|
||||
type ExecutionType string
|
||||
|
||||
const (
|
||||
ExecutionStart ExecutionType = "Start"
|
||||
ExecutionStop ExecutionType = "Stop"
|
||||
)
|
||||
|
||||
// RetryPolicySpec can be configured for the whole Framework and each TaskRole
|
||||
// to control:
|
||||
// 1. Framework RetryPolicy:
|
||||
// The conditions to retry the whole Framework after the Framework's current
|
||||
// FrameworkAttempt completed.
|
||||
// It can also be considered as Framework CompletionPolicy, i.e. the conditions
|
||||
// to complete the whole Framework.
|
||||
// 2. Task RetryPolicy:
|
||||
// The conditions to retry a single Task in the TaskRole after the Task's
|
||||
// current TaskAttempt completed.
|
||||
// It can also be considered as Task CompletionPolicy, i.e. the conditions to
|
||||
// complete a single Task in the TaskRole.
|
||||
//
|
||||
// Usage:
|
||||
// If the FancyRetryPolicy is enabled,
|
||||
// will retry if the completion is due to Transient Failed CompletionType,
|
||||
// will not retry if the completion is due to Permanent Failed CompletionType,
|
||||
// will apply the NormalRetryPolicy defined below if all above conditions are
|
||||
// not satisfied.
|
||||
//
|
||||
// If the FancyRetryPolicy is not enabled,
|
||||
// will directly apply the NormalRetryPolicy for all kinds of completions.
|
||||
//
|
||||
// The NormalRetryPolicy is defined as,
|
||||
// will retry and AccountableRetriedCount++ if MaxRetryCount == -2,
|
||||
// will retry and AccountableRetriedCount++ if the completion is due to any
|
||||
// failure and MaxRetryCount == -1,
|
||||
// will retry and AccountableRetriedCount++ if the completion is due to any
|
||||
// failure and AccountableRetriedCount < MaxRetryCount,
|
||||
// will not retry if all above conditions are not satisfied.
|
||||
//
|
||||
// After the retry is exhausted, the final CompletionStatus is defined as,
|
||||
// the CompletionStatus of the last attempt.
|
||||
//
|
||||
// Notes:
|
||||
// 1. The existence of an attempt instance may not always be observed, such as
|
||||
// create fails but succeeds on remote and then followed by an external delete.
|
||||
// So, an attempt identified by its attempt id may be associated with multiple
|
||||
// attempt instances over time, i.e. multiple instances may be run for the
|
||||
// attempt over time, however, at most one instance is exposed into ApiServer
|
||||
// over time and at most one instance is running at any point in time.
|
||||
// So, the actual retried attempt instances maybe exceed the RetryPolicySpec
|
||||
// in rare cases, however, the RetryPolicyStatus will never exceed the
|
||||
// RetryPolicySpec.
|
||||
type RetryPolicySpec struct {
|
||||
FancyRetryPolicy bool `json:"fancyRetryPolicy"`
|
||||
MaxRetryCount int32 `json:"maxRetryCount"`
|
||||
}
|
||||
|
||||
// CompletionPolicySpec can be configured for each TaskRole to control:
|
||||
// 1. FrameworkAttempt CompletionPolicy:
|
||||
// 1. The conditions to complete a FrameworkAttempt.
|
||||
// 2. The CompletionStatus of the completed FrameworkAttempt.
|
||||
//
|
||||
// Usage:
|
||||
// 1. If MinFailedTaskCount != -1 and MinFailedTaskCount <= failed Task count of
|
||||
// current TaskRole, immediately complete the FrameworkAttempt, regardless of
|
||||
// any uncompleted Task, and the CompletionStatus is failed which is generated
|
||||
// from the Task which triggers the completion.
|
||||
// 2. If MinSucceededTaskCount != -1 and MinSucceededTaskCount <= succeeded Task
|
||||
// count of current TaskRole, immediately complete the FrameworkAttempt, regardless
|
||||
// of any uncompleted Task, and the CompletionStatus is succeeded which is
|
||||
// generated from the Task which triggers the completion.
|
||||
// 3. If multiple above 1. and 2. conditions of all TaskRoles are satisfied at the
|
||||
// same time, the behavior can be any one of these satisfied conditions.
|
||||
// 4. If none of above 1. and 2. conditions of all TaskRoles are satisfied until all
|
||||
// Tasks of the Framework are completed, immediately complete the FrameworkAttempt
|
||||
// and the CompletionStatus is succeeded which is not generated from any Task.
|
||||
//
|
||||
// Notes:
|
||||
// 1. When the FrameworkAttempt is completed, the FrameworkState is transitioned to
|
||||
// FrameworkAttemptCompleted, so the Framework may still be retried with another
|
||||
// new FrameworkAttempt according to the Framework RetryPolicySpec.
|
||||
type CompletionPolicySpec struct {
|
||||
MinFailedTaskCount int32 `json:"minFailedTaskCount"`
|
||||
MinSucceededTaskCount int32 `json:"minSucceededTaskCount"`
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Status
|
||||
// It is used to:
|
||||
// 1. Aggregate the ground truth from other related objects, such as Pod.Status.
|
||||
// 2. Maintain the Framework owned ground truth, such as PodUID.
|
||||
// 3. Retain the ground truth even if other related objects are deleted.
|
||||
//
|
||||
// Notes:
|
||||
// 1. It should only contain current status, history status should be a different type
|
||||
// and stored in a history database.
|
||||
// 2. For field which is not the ground truth, such as the TaskState, it should be
|
||||
// totally reconstructable from its ground truth, in case the Status is failed to
|
||||
// persist due to FrameworkController restart.
|
||||
// The ground truth may be other fields in Framework.Status or the fields in other
|
||||
// related objects, such as the PodUID and Pod.Status.
|
||||
// 3. For field which is the ground truth, such as the PodUID, it should be
|
||||
// Monotonically Exposed which means it should only be changed to a future state in
|
||||
// ApiServer. However, it does not mean other related objects are also Monotonically
|
||||
// Exposed.
|
||||
// For example, from the view of any ApiServer client, the PodUID should be changed
|
||||
// from a not nil value to a different not nil value, if and only if its TaskAttemptID
|
||||
// is also increased.
|
||||
// 4. It is better to keep the ground truth in other related objects instead of in the
|
||||
// Status here, so that the Framework can be more compatible with other k8s features,
|
||||
// such as labels and selectors.
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
type FrameworkStatus struct {
|
||||
StartTime meta.Time `json:"startTime"`
|
||||
CompletionTime *meta.Time `json:"completionTime"`
|
||||
State FrameworkState `json:"state"`
|
||||
TransitionTime meta.Time `json:"transitionTime"`
|
||||
RetryPolicyStatus RetryPolicyStatus `json:"retryPolicyStatus"`
|
||||
AttemptStatus FrameworkAttemptStatus `json:"attemptStatus"`
|
||||
}
|
||||
|
||||
type FrameworkAttemptStatus struct {
|
||||
// FrameworkAttemptID = {FrameworkStatus.RetryPolicyStatus.TotalRetriedCount}
|
||||
// It can only locate the FrameworkAttempt within a specific Framework, i.e.
|
||||
// it cannot universally locate the FrameworkAttempt and cannot locate the
|
||||
// FrameworkAttemptInstance even within a specific Framework.
|
||||
ID int32 `json:"id"`
|
||||
|
||||
StartTime meta.Time `json:"startTime"`
|
||||
CompletionTime *meta.Time `json:"completionTime"`
|
||||
|
||||
// Current associated FrameworkAttemptInstance:
|
||||
// FrameworkAttemptInstanceUID = {FrameworkAttemptID}_{ConfigMapUID}
|
||||
// It is ordered by FrameworkAttemptID and can universally locate the
|
||||
// FrameworkAttemptInstance.
|
||||
InstanceUID *types.UID `json:"instanceUID"`
|
||||
// A FrameworkAttemptInstance is represented by a ConfigMap object:
|
||||
// ConfigMapName = {FrameworkName}-attempt
|
||||
// It will never be changed during the whole lifetime of a specific Framework.
|
||||
ConfigMapName string `json:"configMapName"`
|
||||
// ConfigMapUID can also universally locate the FrameworkAttemptInstance.
|
||||
ConfigMapUID *types.UID `json:"configMapUID"`
|
||||
CompletionStatus *CompletionStatus `json:"completionStatus"`
|
||||
TaskRoleStatuses []TaskRoleStatus `json:"taskRoleStatuses"`
|
||||
}
|
||||
|
||||
type TaskRoleStatus struct {
|
||||
// TaskRoleName
|
||||
Name string `json:"name"`
|
||||
|
||||
// Tasks with TaskIndex in range [0, TaskNumber)
|
||||
TaskStatuses []TaskStatus `json:"taskStatuses"`
|
||||
}
|
||||
|
||||
type TaskStatus struct {
|
||||
// TaskIndex
|
||||
Index int32 `json:"index"`
|
||||
|
||||
StartTime meta.Time `json:"startTime"`
|
||||
CompletionTime *meta.Time `json:"completionTime"`
|
||||
State TaskState `json:"state"`
|
||||
TransitionTime meta.Time `json:"transitionTime"`
|
||||
RetryPolicyStatus RetryPolicyStatus `json:"retryPolicyStatus"`
|
||||
AttemptStatus TaskAttemptStatus `json:"attemptStatus"`
|
||||
}
|
||||
|
||||
type TaskAttemptStatus struct {
|
||||
// TaskAttemptID = {TaskStatus.RetryPolicyStatus.TotalRetriedCount}
|
||||
// It can only locate the TaskAttempt within a specific Task, i.e. it cannot
|
||||
// universally locate the TaskAttempt and cannot locate the TaskAttemptInstance
|
||||
// even within a specific Task.
|
||||
ID int32 `json:"id"`
|
||||
|
||||
StartTime meta.Time `json:"startTime"`
|
||||
CompletionTime *meta.Time `json:"completionTime"`
|
||||
|
||||
// Current associated TaskAttemptInstance:
|
||||
// TaskAttemptInstanceUID = {TaskAttemptID}_{PodUID}
|
||||
// It is ordered by TaskAttemptID and can universally locate the
|
||||
// TaskAttemptInstance.
|
||||
InstanceUID *types.UID `json:"instanceUID"`
|
||||
// A TaskAttemptInstance is represented by a Pod object:
|
||||
// PodName = {FrameworkName}-{TaskRoleName}-{TaskIndex}
|
||||
// It will never be changed during the whole lifetime of a specific Task.
|
||||
PodName string `json:"podName"`
|
||||
// PodUID can also universally locate the TaskAttemptInstance.
|
||||
PodUID *types.UID `json:"podUID"`
|
||||
PodIP *string `json:"podIP"`
|
||||
PodHostIP *string `json:"podHostIP"`
|
||||
CompletionStatus *CompletionStatus `json:"completionStatus"`
|
||||
}
|
||||
|
||||
type RetryPolicyStatus struct {
|
||||
// Used as the ground truth of current attempt id.
|
||||
// If it is for Framework, TotalRetriedCount = FrameworkAttemptID
|
||||
// If it is for Task, TotalRetriedCount = TaskAttemptID
|
||||
TotalRetriedCount int32 `json:"totalRetriedCount"`
|
||||
|
||||
// Used to compare against MaxRetryCount.
|
||||
// If the FancyRetryPolicy is not enabled,
|
||||
// it is the same as the TotalRetriedCount.
|
||||
// If the FancyRetryPolicy is enabled,
|
||||
// it does not count into the retries for the completion which is due to
|
||||
// Transient CompletionType, so only in this case, it may be less than the
|
||||
// TotalRetriedCount.
|
||||
AccountableRetriedCount int32 `json:"accountableRetriedCount"`
|
||||
|
||||
// Used to expose the ScheduledRetryTime after which current retry can be
|
||||
// executed.
|
||||
// ScheduledRetryTime = AttemptStatus.CompletionTime + RetryDelaySec
|
||||
// It is available and meaningful if and only if current attempt is in
|
||||
// AttemptCompleted state.
|
||||
RetryDelaySec *int64 `json:"retryDelaySec"`
|
||||
}
|
||||
|
||||
type CompletionStatus struct {
|
||||
// CompletionCode Convention:
|
||||
// 1. NonNegative:
|
||||
// The CompletionCode is the ExitCode of the Framework's Container which
|
||||
// triggers the completion.
|
||||
// 2. Negative:
|
||||
// -1XX: Framework Predefined Transient Error
|
||||
// -2XX: Framework Predefined Permanent Error
|
||||
// -3XX: Framework Predefined Unknown Error
|
||||
// The CompletionCode is the ExitCode of the Framework's Predefined Error
|
||||
// which triggers the completion.
|
||||
Code CompletionCode `json:"code"`
|
||||
// The textual phrase representation of the CompletionCode.
|
||||
Phrase CompletionPhrase `json:"phrase"`
|
||||
|
||||
// CompletionType is determined by the CompletionCode and the Predefined
|
||||
// CompletionCodeInfos.
|
||||
// See CompletionCodeInfos.
|
||||
Type CompletionType `json:"type"`
|
||||
|
||||
// The detailed diagnostic information of the completion.
|
||||
Diagnostics string `json:"diagnostics"`
|
||||
}
|
||||
|
||||
type CompletionCode int32
|
||||
|
||||
type CompletionPhrase string
|
||||
|
||||
type CompletionType struct {
|
||||
Name CompletionTypeName `json:"name"`
|
||||
Attributes []CompletionTypeAttribute `json:"attributes"`
|
||||
}
|
||||
|
||||
type CompletionTypeName string
|
||||
|
||||
const (
|
||||
CompletionTypeNameSucceeded CompletionTypeName = "Succeeded"
|
||||
CompletionTypeNameFailed CompletionTypeName = "Failed"
|
||||
)
|
||||
|
||||
type CompletionTypeAttribute string
|
||||
|
||||
const (
|
||||
// CompletionTypeName must be different within a finite retry times:
|
||||
// such as failed due to dependent components shutdown, machine error,
|
||||
// network error, environment error, workload spike, etc.
|
||||
CompletionTypeAttributeTransient CompletionTypeAttribute = "Transient"
|
||||
// CompletionTypeName must be the same in every retry times:
|
||||
// such as failed due to incorrect usage, incorrect configuration, etc.
|
||||
CompletionTypeAttributePermanent CompletionTypeAttribute = "Permanent"
|
||||
|
||||
// The completion must be caused by External, i.e. the Platform.
|
||||
CompletionTypeAttributeExternal CompletionTypeAttribute = "External"
|
||||
// The completion must be caused by Internal, i.e. the Framework itself.
|
||||
CompletionTypeAttributeInternal CompletionTypeAttribute = "Internal"
|
||||
|
||||
// The completion must be caused by Resource Conflict (Resource Contention):
|
||||
// such as failed due to Gang Allocation timeout.
|
||||
CompletionTypeAttributeConflict CompletionTypeAttribute = "Conflict"
|
||||
)
|
||||
|
||||
// The ground truth of FrameworkState is the current associated FrameworkAttemptInstance
|
||||
// which is represented by the ConfigMapUID and the corresponding ConfigMap object in
|
||||
// the local cache.
|
||||
//
|
||||
// [AssociatedState]: ConfigMapUID is not nil
|
||||
type FrameworkState string
|
||||
|
||||
const (
|
||||
// ConfigMap does not exist and
|
||||
// has not been creation requested.
|
||||
// [StartState]
|
||||
// [AttemptStartState]
|
||||
// -> FrameworkAttemptCreationRequested
|
||||
FrameworkAttemptCreationPending FrameworkState = "AttemptCreationPending"
|
||||
|
||||
// ConfigMap does not exist and
|
||||
// has been creation requested and is expected to exist.
|
||||
// [AssociatedState]
|
||||
// -> FrameworkAttemptRunning
|
||||
// -> FrameworkAttemptDeleting
|
||||
// -> FrameworkAttemptCompleted
|
||||
FrameworkAttemptCreationRequested FrameworkState = "AttemptCreationRequested"
|
||||
|
||||
// ConfigMap exists and is not deleting and
|
||||
// has not been deletion requested and
|
||||
// is not pending to be deletion requested.
|
||||
// [AssociatedState]
|
||||
// -> FrameworkAttemptDeletionPending
|
||||
// -> FrameworkAttemptDeleting
|
||||
// -> FrameworkAttemptCompleted
|
||||
FrameworkAttemptRunning FrameworkState = "AttemptRunning"
|
||||
|
||||
// ConfigMap exists and is not deleting and
|
||||
// has not been deletion requested and
|
||||
// is pending to be deletion requested.
|
||||
// [AssociatedState]
|
||||
// -> FrameworkAttemptDeletionRequested
|
||||
// -> FrameworkAttemptDeleting
|
||||
// -> FrameworkAttemptCompleted
|
||||
FrameworkAttemptDeletionPending FrameworkState = "AttemptDeletionPending"
|
||||
|
||||
// ConfigMap exists and is not deleting and
|
||||
// has been deletion requested.
|
||||
// [AssociatedState]
|
||||
// -> FrameworkAttemptDeleting
|
||||
// -> FrameworkAttemptCompleted
|
||||
FrameworkAttemptDeletionRequested FrameworkState = "AttemptDeletionRequested"
|
||||
|
||||
// ConfigMap exists and is deleting.
|
||||
// [AssociatedState]
|
||||
// -> FrameworkAttemptCompleted
|
||||
FrameworkAttemptDeleting FrameworkState = "AttemptDeleting"
|
||||
|
||||
// ConfigMap does not exist and
|
||||
// has been creation requested and is not expected to exist.
|
||||
// [AttemptFinalState]
|
||||
// [AssociatedState]
|
||||
// -> FrameworkAttemptCreationPending
|
||||
// -> FrameworkCompleted
|
||||
FrameworkAttemptCompleted FrameworkState = "AttemptCompleted"
|
||||
|
||||
// Last FrameworkAttempt is completed.
|
||||
// [FinalState]
|
||||
// [AssociatedState]
|
||||
FrameworkCompleted FrameworkState = "Completed"
|
||||
)
|
||||
|
||||
// The ground truth of TaskState is the current associated TaskAttemptInstance
|
||||
// which is represented by the PodUID and the corresponding Pod object in the
|
||||
// local cache.
|
||||
//
|
||||
// [AssociatedState]: PodUID is not nil
|
||||
type TaskState string
|
||||
|
||||
const (
|
||||
// Pod does not exist and
|
||||
// has not been creation requested.
|
||||
// [StartState]
|
||||
// [AttemptStartState]
|
||||
// -> TaskAttemptCreationRequested
|
||||
TaskAttemptCreationPending TaskState = "AttemptCreationPending"
|
||||
|
||||
// Pod does not exist and
|
||||
// has been creation requested and is expected to exist.
|
||||
// [AssociatedState]
|
||||
// -> TaskAttemptPreparing
|
||||
// -> TaskAttemptRunning
|
||||
// -> TaskAttemptDeleting
|
||||
// -> TaskAttemptCompleted
|
||||
TaskAttemptCreationRequested TaskState = "AttemptCreationRequested"
|
||||
|
||||
// Pod exists and is not deleting and
|
||||
// has not been deletion requested and
|
||||
// is PodPending or PodUnknown afterwards.
|
||||
// [AssociatedState]
|
||||
// -> TaskAttemptDeletionPending
|
||||
// -> TaskAttemptRunning
|
||||
// -> TaskAttemptDeleting
|
||||
// -> TaskAttemptCompleted
|
||||
TaskAttemptPreparing TaskState = "AttemptPreparing"
|
||||
|
||||
// Pod exists and is not deleting and
|
||||
// has not been deletion requested and
|
||||
// is PodRunning or PodUnknown afterwards.
|
||||
// [AssociatedState]
|
||||
// -> TaskAttemptDeletionPending
|
||||
// -> TaskAttemptDeleting
|
||||
// -> TaskAttemptCompleted
|
||||
TaskAttemptRunning TaskState = "AttemptRunning"
|
||||
|
||||
// Pod exists and is not deleting and
|
||||
// has not been deletion requested and
|
||||
// is PodSucceeded or PodFailed.
|
||||
// [AssociatedState]
|
||||
// -> TaskAttemptDeletionRequested
|
||||
// -> TaskAttemptDeleting
|
||||
// -> TaskAttemptCompleted
|
||||
TaskAttemptDeletionPending TaskState = "AttemptDeletionPending"
|
||||
|
||||
// Pod exists and is not deleting and
|
||||
// has been deletion requested.
|
||||
// [AssociatedState]
|
||||
// -> TaskAttemptDeleting
|
||||
// -> TaskAttemptCompleted
|
||||
TaskAttemptDeletionRequested TaskState = "AttemptDeletionRequested"
|
||||
|
||||
// Pod exists and is deleting.
|
||||
// [AssociatedState]
|
||||
// -> TaskAttemptCompleted
|
||||
TaskAttemptDeleting TaskState = "AttemptDeleting"
|
||||
|
||||
// Pod does not exist and
|
||||
// has been creation requested and is not expected to exist.
|
||||
// [AttemptFinalState]
|
||||
// [AssociatedState]
|
||||
// -> TaskAttemptCreationPending
|
||||
// -> TaskCompleted
|
||||
TaskAttemptCompleted TaskState = "AttemptCompleted"
|
||||
|
||||
// Last TaskAttempt is completed.
|
||||
// [FinalState]
|
||||
// [AssociatedState]
|
||||
TaskCompleted TaskState = "Completed"
|
||||
)
|
|
@ -0,0 +1,491 @@
|
|||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CompletionCodeInfo) DeepCopyInto(out *CompletionCodeInfo) {
|
||||
*out = *in
|
||||
in.Type.DeepCopyInto(&out.Type)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompletionCodeInfo.
|
||||
func (in *CompletionCodeInfo) DeepCopy() *CompletionCodeInfo {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CompletionCodeInfo)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CompletionPolicySpec) DeepCopyInto(out *CompletionPolicySpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompletionPolicySpec.
|
||||
func (in *CompletionPolicySpec) DeepCopy() *CompletionPolicySpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CompletionPolicySpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CompletionStatus) DeepCopyInto(out *CompletionStatus) {
|
||||
*out = *in
|
||||
in.Type.DeepCopyInto(&out.Type)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompletionStatus.
|
||||
func (in *CompletionStatus) DeepCopy() *CompletionStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CompletionStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CompletionType) DeepCopyInto(out *CompletionType) {
|
||||
*out = *in
|
||||
if in.Attributes != nil {
|
||||
in, out := &in.Attributes, &out.Attributes
|
||||
*out = make([]CompletionTypeAttribute, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompletionType.
|
||||
func (in *CompletionType) DeepCopy() *CompletionType {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CompletionType)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ControllerConfig) DeepCopyInto(out *ControllerConfig) {
|
||||
*out = *in
|
||||
if in.KubeApiServerAddress != nil {
|
||||
in, out := &in.KubeApiServerAddress, &out.KubeApiServerAddress
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.KubeConfigFilePath != nil {
|
||||
in, out := &in.KubeConfigFilePath, &out.KubeConfigFilePath
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.WorkerNumber != nil {
|
||||
in, out := &in.WorkerNumber, &out.WorkerNumber
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.CRDEstablishedCheckIntervalSec != nil {
|
||||
in, out := &in.CRDEstablishedCheckIntervalSec, &out.CRDEstablishedCheckIntervalSec
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.CRDEstablishedCheckTimeoutSec != nil {
|
||||
in, out := &in.CRDEstablishedCheckTimeoutSec, &out.CRDEstablishedCheckTimeoutSec
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.ObjectLocalCacheCreationTimeoutSec != nil {
|
||||
in, out := &in.ObjectLocalCacheCreationTimeoutSec, &out.ObjectLocalCacheCreationTimeoutSec
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.FrameworkMinRetryDelaySecForTransientConflictFailed != nil {
|
||||
in, out := &in.FrameworkMinRetryDelaySecForTransientConflictFailed, &out.FrameworkMinRetryDelaySecForTransientConflictFailed
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.FrameworkMaxRetryDelaySecForTransientConflictFailed != nil {
|
||||
in, out := &in.FrameworkMaxRetryDelaySecForTransientConflictFailed, &out.FrameworkMaxRetryDelaySecForTransientConflictFailed
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerConfig.
|
||||
func (in *ControllerConfig) DeepCopy() *ControllerConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ControllerConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Framework) DeepCopyInto(out *Framework) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(FrameworkStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Framework.
|
||||
func (in *Framework) DeepCopy() *Framework {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Framework)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Framework) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FrameworkAttemptStatus) DeepCopyInto(out *FrameworkAttemptStatus) {
|
||||
*out = *in
|
||||
in.StartTime.DeepCopyInto(&out.StartTime)
|
||||
if in.CompletionTime != nil {
|
||||
in, out := &in.CompletionTime, &out.CompletionTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.InstanceUID != nil {
|
||||
in, out := &in.InstanceUID, &out.InstanceUID
|
||||
*out = new(types.UID)
|
||||
**out = **in
|
||||
}
|
||||
if in.ConfigMapUID != nil {
|
||||
in, out := &in.ConfigMapUID, &out.ConfigMapUID
|
||||
*out = new(types.UID)
|
||||
**out = **in
|
||||
}
|
||||
if in.CompletionStatus != nil {
|
||||
in, out := &in.CompletionStatus, &out.CompletionStatus
|
||||
*out = new(CompletionStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TaskRoleStatuses != nil {
|
||||
in, out := &in.TaskRoleStatuses, &out.TaskRoleStatuses
|
||||
*out = make([]TaskRoleStatus, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrameworkAttemptStatus.
|
||||
func (in *FrameworkAttemptStatus) DeepCopy() *FrameworkAttemptStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FrameworkAttemptStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FrameworkList) DeepCopyInto(out *FrameworkList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Framework, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrameworkList.
|
||||
func (in *FrameworkList) DeepCopy() *FrameworkList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FrameworkList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *FrameworkList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FrameworkSpec) DeepCopyInto(out *FrameworkSpec) {
|
||||
*out = *in
|
||||
out.RetryPolicy = in.RetryPolicy
|
||||
if in.TaskRoles != nil {
|
||||
in, out := &in.TaskRoles, &out.TaskRoles
|
||||
*out = make([]TaskRoleSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrameworkSpec.
|
||||
func (in *FrameworkSpec) DeepCopy() *FrameworkSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FrameworkSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FrameworkStatus) DeepCopyInto(out *FrameworkStatus) {
|
||||
*out = *in
|
||||
in.StartTime.DeepCopyInto(&out.StartTime)
|
||||
if in.CompletionTime != nil {
|
||||
in, out := &in.CompletionTime, &out.CompletionTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
in.TransitionTime.DeepCopyInto(&out.TransitionTime)
|
||||
in.RetryPolicyStatus.DeepCopyInto(&out.RetryPolicyStatus)
|
||||
in.AttemptStatus.DeepCopyInto(&out.AttemptStatus)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrameworkStatus.
|
||||
func (in *FrameworkStatus) DeepCopy() *FrameworkStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FrameworkStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RetryDecision) DeepCopyInto(out *RetryDecision) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryDecision.
|
||||
func (in *RetryDecision) DeepCopy() *RetryDecision {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RetryDecision)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RetryPolicySpec) DeepCopyInto(out *RetryPolicySpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryPolicySpec.
|
||||
func (in *RetryPolicySpec) DeepCopy() *RetryPolicySpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RetryPolicySpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RetryPolicyStatus) DeepCopyInto(out *RetryPolicyStatus) {
|
||||
*out = *in
|
||||
if in.RetryDelaySec != nil {
|
||||
in, out := &in.RetryDelaySec, &out.RetryDelaySec
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryPolicyStatus.
|
||||
func (in *RetryPolicyStatus) DeepCopy() *RetryPolicyStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RetryPolicyStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TaskAttemptStatus) DeepCopyInto(out *TaskAttemptStatus) {
|
||||
*out = *in
|
||||
in.StartTime.DeepCopyInto(&out.StartTime)
|
||||
if in.CompletionTime != nil {
|
||||
in, out := &in.CompletionTime, &out.CompletionTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.InstanceUID != nil {
|
||||
in, out := &in.InstanceUID, &out.InstanceUID
|
||||
*out = new(types.UID)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodUID != nil {
|
||||
in, out := &in.PodUID, &out.PodUID
|
||||
*out = new(types.UID)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodIP != nil {
|
||||
in, out := &in.PodIP, &out.PodIP
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.PodHostIP != nil {
|
||||
in, out := &in.PodHostIP, &out.PodHostIP
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.CompletionStatus != nil {
|
||||
in, out := &in.CompletionStatus, &out.CompletionStatus
|
||||
*out = new(CompletionStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskAttemptStatus.
|
||||
func (in *TaskAttemptStatus) DeepCopy() *TaskAttemptStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TaskAttemptStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TaskRoleSpec) DeepCopyInto(out *TaskRoleSpec) {
|
||||
*out = *in
|
||||
out.FrameworkAttemptCompletionPolicy = in.FrameworkAttemptCompletionPolicy
|
||||
in.Task.DeepCopyInto(&out.Task)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskRoleSpec.
|
||||
func (in *TaskRoleSpec) DeepCopy() *TaskRoleSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TaskRoleSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TaskRoleStatus) DeepCopyInto(out *TaskRoleStatus) {
|
||||
*out = *in
|
||||
if in.TaskStatuses != nil {
|
||||
in, out := &in.TaskStatuses, &out.TaskStatuses
|
||||
*out = make([]TaskStatus, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskRoleStatus.
|
||||
func (in *TaskRoleStatus) DeepCopy() *TaskRoleStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TaskRoleStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TaskSpec) DeepCopyInto(out *TaskSpec) {
|
||||
*out = *in
|
||||
out.RetryPolicy = in.RetryPolicy
|
||||
in.Pod.DeepCopyInto(&out.Pod)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskSpec.
|
||||
func (in *TaskSpec) DeepCopy() *TaskSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TaskSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TaskStatus) DeepCopyInto(out *TaskStatus) {
|
||||
*out = *in
|
||||
in.StartTime.DeepCopyInto(&out.StartTime)
|
||||
if in.CompletionTime != nil {
|
||||
in, out := &in.CompletionTime, &out.CompletionTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
in.TransitionTime.DeepCopyInto(&out.TransitionTime)
|
||||
in.RetryPolicyStatus.DeepCopyInto(&out.RetryPolicyStatus)
|
||||
in.AttemptStatus.DeepCopyInto(&out.AttemptStatus)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskStatus.
|
||||
func (in *TaskStatus) DeepCopy() *TaskStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TaskStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package versioned
|
||||
|
||||
import (
|
||||
glog "github.com/golang/glog"
|
||||
frameworkcontrollerv1 "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned/typed/frameworkcontroller/v1"
|
||||
discovery "k8s.io/client-go/discovery"
|
||||
rest "k8s.io/client-go/rest"
|
||||
flowcontrol "k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Discovery() discovery.DiscoveryInterface
|
||||
FrameworkcontrollerV1() frameworkcontrollerv1.FrameworkcontrollerV1Interface
|
||||
// Deprecated: please explicitly pick a version if possible.
|
||||
Frameworkcontroller() frameworkcontrollerv1.FrameworkcontrollerV1Interface
|
||||
}
|
||||
|
||||
// Clientset contains the clients for groups. Each group has exactly one
|
||||
// version included in a Clientset.
|
||||
type Clientset struct {
|
||||
*discovery.DiscoveryClient
|
||||
frameworkcontrollerV1 *frameworkcontrollerv1.FrameworkcontrollerV1Client
|
||||
}
|
||||
|
||||
// FrameworkcontrollerV1 retrieves the FrameworkcontrollerV1Client
|
||||
func (c *Clientset) FrameworkcontrollerV1() frameworkcontrollerv1.FrameworkcontrollerV1Interface {
|
||||
return c.frameworkcontrollerV1
|
||||
}
|
||||
|
||||
// Deprecated: Frameworkcontroller retrieves the default version of FrameworkcontrollerClient.
|
||||
// Please explicitly pick a version.
|
||||
func (c *Clientset) Frameworkcontroller() frameworkcontrollerv1.FrameworkcontrollerV1Interface {
|
||||
return c.frameworkcontrollerV1
|
||||
}
|
||||
|
||||
// Discovery retrieves the DiscoveryClient
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.DiscoveryClient
|
||||
}
|
||||
|
||||
// NewForConfig creates a new Clientset for the given config.
|
||||
func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
configShallowCopy := *c
|
||||
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
|
||||
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
|
||||
}
|
||||
var cs Clientset
|
||||
var err error
|
||||
cs.frameworkcontrollerV1, err = frameworkcontrollerv1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to create the DiscoveryClient: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return &cs, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new Clientset for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
var cs Clientset
|
||||
cs.frameworkcontrollerV1 = frameworkcontrollerv1.NewForConfigOrDie(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
||||
return &cs
|
||||
}
|
||||
|
||||
// New creates a new Clientset for the given RESTClient.
|
||||
func New(c rest.Interface) *Clientset {
|
||||
var cs Clientset
|
||||
cs.frameworkcontrollerV1 = frameworkcontrollerv1.New(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
||||
return &cs
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated clientset.
|
||||
package versioned
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
clientset "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned"
|
||||
frameworkcontrollerv1 "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned/typed/frameworkcontroller/v1"
|
||||
fakeframeworkcontrollerv1 "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned/typed/frameworkcontroller/v1/fake"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/discovery"
|
||||
fakediscovery "k8s.io/client-go/discovery/fake"
|
||||
"k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// NewSimpleClientset returns a clientset that will respond with the provided objects.
|
||||
// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
|
||||
// without applying any validations and/or defaults. It shouldn't be considered a replacement
|
||||
// for a real clientset and is mostly useful in simple unit tests.
|
||||
func NewSimpleClientset(objects ...runtime.Object) *Clientset {
|
||||
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
|
||||
for _, obj := range objects {
|
||||
if err := o.Add(obj); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
fakePtr := testing.Fake{}
|
||||
fakePtr.AddReactor("*", "*", testing.ObjectReaction(o))
|
||||
fakePtr.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
||||
gvr := action.GetResource()
|
||||
ns := action.GetNamespace()
|
||||
watch, err := o.Watch(gvr, ns)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return true, watch, nil
|
||||
})
|
||||
|
||||
return &Clientset{fakePtr, &fakediscovery.FakeDiscovery{Fake: &fakePtr}}
|
||||
}
|
||||
|
||||
// Clientset implements clientset.Interface. Meant to be embedded into a
|
||||
// struct to get a default implementation. This makes faking out just the method
|
||||
// you want to test easier.
|
||||
type Clientset struct {
|
||||
testing.Fake
|
||||
discovery *fakediscovery.FakeDiscovery
|
||||
}
|
||||
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
return c.discovery
|
||||
}
|
||||
|
||||
var _ clientset.Interface = &Clientset{}
|
||||
|
||||
// FrameworkcontrollerV1 retrieves the FrameworkcontrollerV1Client
|
||||
func (c *Clientset) FrameworkcontrollerV1() frameworkcontrollerv1.FrameworkcontrollerV1Interface {
|
||||
return &fakeframeworkcontrollerv1.FakeFrameworkcontrollerV1{Fake: &c.Fake}
|
||||
}
|
||||
|
||||
// Frameworkcontroller retrieves the FrameworkcontrollerV1Client
|
||||
func (c *Clientset) Frameworkcontroller() frameworkcontrollerv1.FrameworkcontrollerV1Interface {
|
||||
return &fakeframeworkcontrollerv1.FakeFrameworkcontrollerV1{Fake: &c.Fake}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated fake clientset.
|
||||
package fake
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
frameworkcontrollerv1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
var scheme = runtime.NewScheme()
|
||||
var codecs = serializer.NewCodecFactory(scheme)
|
||||
var parameterCodec = runtime.NewParameterCodec(scheme)
|
||||
|
||||
func init() {
|
||||
v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
|
||||
AddToScheme(scheme)
|
||||
}
|
||||
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
frameworkcontrollerv1.AddToScheme(scheme)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package contains the scheme of the automatically generated clientset.
|
||||
package scheme
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package scheme
|
||||
|
||||
import (
|
||||
frameworkcontrollerv1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
var Scheme = runtime.NewScheme()
|
||||
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||
var ParameterCodec = runtime.NewParameterCodec(Scheme)
|
||||
|
||||
func init() {
|
||||
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||
AddToScheme(Scheme)
|
||||
}
|
||||
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
frameworkcontrollerv1.AddToScheme(scheme)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated typed clients.
|
||||
package v1
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// Package fake has the automatically generated clients.
|
||||
package fake
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
frameworkcontrollerv1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeFrameworks implements FrameworkInterface
|
||||
type FakeFrameworks struct {
|
||||
Fake *FakeFrameworkcontrollerV1
|
||||
ns string
|
||||
}
|
||||
|
||||
var frameworksResource = schema.GroupVersionResource{Group: "frameworkcontroller.microsoft.com", Version: "v1", Resource: "frameworks"}
|
||||
|
||||
var frameworksKind = schema.GroupVersionKind{Group: "frameworkcontroller.microsoft.com", Version: "v1", Kind: "Framework"}
|
||||
|
||||
// Get takes name of the framework, and returns the corresponding framework object, and an error if there is any.
|
||||
func (c *FakeFrameworks) Get(name string, options v1.GetOptions) (result *frameworkcontrollerv1.Framework, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewGetAction(frameworksResource, c.ns, name), &frameworkcontrollerv1.Framework{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*frameworkcontrollerv1.Framework), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of Frameworks that match those selectors.
|
||||
func (c *FakeFrameworks) List(opts v1.ListOptions) (result *frameworkcontrollerv1.FrameworkList, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewListAction(frameworksResource, frameworksKind, c.ns, opts), &frameworkcontrollerv1.FrameworkList{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &frameworkcontrollerv1.FrameworkList{}
|
||||
for _, item := range obj.(*frameworkcontrollerv1.FrameworkList).Items {
|
||||
if label.Matches(labels.Set(item.Labels)) {
|
||||
list.Items = append(list.Items, item)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested frameworks.
|
||||
func (c *FakeFrameworks) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewWatchAction(frameworksResource, c.ns, opts))
|
||||
|
||||
}
|
||||
|
||||
// Create takes the representation of a framework and creates it. Returns the server's representation of the framework, and an error, if there is any.
|
||||
func (c *FakeFrameworks) Create(framework *frameworkcontrollerv1.Framework) (result *frameworkcontrollerv1.Framework, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewCreateAction(frameworksResource, c.ns, framework), &frameworkcontrollerv1.Framework{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*frameworkcontrollerv1.Framework), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a framework and updates it. Returns the server's representation of the framework, and an error, if there is any.
|
||||
func (c *FakeFrameworks) Update(framework *frameworkcontrollerv1.Framework) (result *frameworkcontrollerv1.Framework, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateAction(frameworksResource, c.ns, framework), &frameworkcontrollerv1.Framework{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*frameworkcontrollerv1.Framework), err
|
||||
}
|
||||
|
||||
// Delete takes name of the framework and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeFrameworks) Delete(name string, options *v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteAction(frameworksResource, c.ns, name), &frameworkcontrollerv1.Framework{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakeFrameworks) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
action := testing.NewDeleteCollectionAction(frameworksResource, c.ns, listOptions)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &frameworkcontrollerv1.FrameworkList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched framework.
|
||||
func (c *FakeFrameworks) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *frameworkcontrollerv1.Framework, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(frameworksResource, c.ns, name, data, subresources...), &frameworkcontrollerv1.Framework{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*frameworkcontrollerv1.Framework), err
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
v1 "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned/typed/frameworkcontroller/v1"
|
||||
rest "k8s.io/client-go/rest"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
type FakeFrameworkcontrollerV1 struct {
|
||||
*testing.Fake
|
||||
}
|
||||
|
||||
func (c *FakeFrameworkcontrollerV1) Frameworks(namespace string) v1.FrameworkInterface {
|
||||
return &FakeFrameworks{c, namespace}
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *FakeFrameworkcontrollerV1) RESTClient() rest.Interface {
|
||||
var ret *rest.RESTClient
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
scheme "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned/scheme"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// FrameworksGetter has a method to return a FrameworkInterface.
|
||||
// A group's client should implement this interface.
|
||||
type FrameworksGetter interface {
|
||||
Frameworks(namespace string) FrameworkInterface
|
||||
}
|
||||
|
||||
// FrameworkInterface has methods to work with Framework resources.
|
||||
type FrameworkInterface interface {
|
||||
Create(*v1.Framework) (*v1.Framework, error)
|
||||
Update(*v1.Framework) (*v1.Framework, error)
|
||||
Delete(name string, options *metav1.DeleteOptions) error
|
||||
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
|
||||
Get(name string, options metav1.GetOptions) (*v1.Framework, error)
|
||||
List(opts metav1.ListOptions) (*v1.FrameworkList, error)
|
||||
Watch(opts metav1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Framework, err error)
|
||||
FrameworkExpansion
|
||||
}
|
||||
|
||||
// frameworks implements FrameworkInterface
|
||||
type frameworks struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newFrameworks returns a Frameworks
|
||||
func newFrameworks(c *FrameworkcontrollerV1Client, namespace string) *frameworks {
|
||||
return &frameworks{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Get takes name of the framework, and returns the corresponding framework object, and an error if there is any.
|
||||
func (c *frameworks) Get(name string, options metav1.GetOptions) (result *v1.Framework, err error) {
|
||||
result = &v1.Framework{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of Frameworks that match those selectors.
|
||||
func (c *frameworks) List(opts metav1.ListOptions) (result *v1.FrameworkList, err error) {
|
||||
result = &v1.FrameworkList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested frameworks.
|
||||
func (c *frameworks) Watch(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Create takes the representation of a framework and creates it. Returns the server's representation of the framework, and an error, if there is any.
|
||||
func (c *frameworks) Create(framework *v1.Framework) (result *v1.Framework, err error) {
|
||||
result = &v1.Framework{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
Body(framework).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a framework and updates it. Returns the server's representation of the framework, and an error, if there is any.
|
||||
func (c *frameworks) Update(framework *v1.Framework) (result *v1.Framework, err error) {
|
||||
result = &v1.Framework{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
Name(framework.Name).
|
||||
Body(framework).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the framework and deletes it. Returns an error if one occurs.
|
||||
func (c *frameworks) Delete(name string, options *metav1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *frameworks) DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched framework.
|
||||
func (c *frameworks) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Framework, err error) {
|
||||
result = &v1.Framework{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("frameworks").
|
||||
SubResource(subresources...).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
"github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned/scheme"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type FrameworkcontrollerV1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
FrameworksGetter
|
||||
}
|
||||
|
||||
// FrameworkcontrollerV1Client is used to interact with features provided by the frameworkcontroller.microsoft.com group.
|
||||
type FrameworkcontrollerV1Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *FrameworkcontrollerV1Client) Frameworks(namespace string) FrameworkInterface {
|
||||
return newFrameworks(c, namespace)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new FrameworkcontrollerV1Client for the given config.
|
||||
func NewForConfig(c *rest.Config) (*FrameworkcontrollerV1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FrameworkcontrollerV1Client{client}, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new FrameworkcontrollerV1Client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *FrameworkcontrollerV1Client {
|
||||
client, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// New creates a new FrameworkcontrollerV1Client for the given RESTClient.
|
||||
func New(c rest.Interface) *FrameworkcontrollerV1Client {
|
||||
return &FrameworkcontrollerV1Client{c}
|
||||
}
|
||||
|
||||
func setConfigDefaults(config *rest.Config) error {
|
||||
gv := v1.SchemeGroupVersion
|
||||
config.GroupVersion = &gv
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *FrameworkcontrollerV1Client) RESTClient() rest.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
type FrameworkExpansion interface{}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package externalversions
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
time "time"
|
||||
|
||||
versioned "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned"
|
||||
frameworkcontroller "github.com/microsoft/frameworkcontroller/pkg/client/informers/externalversions/frameworkcontroller"
|
||||
internalinterfaces "github.com/microsoft/frameworkcontroller/pkg/client/informers/externalversions/internalinterfaces"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type sharedInformerFactory struct {
|
||||
client versioned.Interface
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
lock sync.Mutex
|
||||
defaultResync time.Duration
|
||||
|
||||
informers map[reflect.Type]cache.SharedIndexInformer
|
||||
// startedInformers is used for tracking which informers have been started.
|
||||
// This allows Start() to be called multiple times safely.
|
||||
startedInformers map[reflect.Type]bool
|
||||
}
|
||||
|
||||
// NewSharedInformerFactory constructs a new instance of sharedInformerFactory
|
||||
func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {
|
||||
return NewFilteredSharedInformerFactory(client, defaultResync, v1.NamespaceAll, nil)
|
||||
}
|
||||
|
||||
// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.
|
||||
// Listers obtained via this SharedInformerFactory will be subject to the same filters
|
||||
// as specified here.
|
||||
func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {
|
||||
return &sharedInformerFactory{
|
||||
client: client,
|
||||
namespace: namespace,
|
||||
tweakListOptions: tweakListOptions,
|
||||
defaultResync: defaultResync,
|
||||
informers: make(map[reflect.Type]cache.SharedIndexInformer),
|
||||
startedInformers: make(map[reflect.Type]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Start initializes all requested informers.
|
||||
func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
for informerType, informer := range f.informers {
|
||||
if !f.startedInformers[informerType] {
|
||||
go informer.Run(stopCh)
|
||||
f.startedInformers[informerType] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForCacheSync waits for all started informers' cache were synced.
|
||||
func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {
|
||||
informers := func() map[reflect.Type]cache.SharedIndexInformer {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
informers := map[reflect.Type]cache.SharedIndexInformer{}
|
||||
for informerType, informer := range f.informers {
|
||||
if f.startedInformers[informerType] {
|
||||
informers[informerType] = informer
|
||||
}
|
||||
}
|
||||
return informers
|
||||
}()
|
||||
|
||||
res := map[reflect.Type]bool{}
|
||||
for informType, informer := range informers {
|
||||
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// InternalInformerFor returns the SharedIndexInformer for obj using an internal
|
||||
// client.
|
||||
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
informerType := reflect.TypeOf(obj)
|
||||
informer, exists := f.informers[informerType]
|
||||
if exists {
|
||||
return informer
|
||||
}
|
||||
informer = newFunc(f.client, f.defaultResync)
|
||||
f.informers[informerType] = informer
|
||||
|
||||
return informer
|
||||
}
|
||||
|
||||
// SharedInformerFactory provides shared informers for resources in all known
|
||||
// API group versions.
|
||||
type SharedInformerFactory interface {
|
||||
internalinterfaces.SharedInformerFactory
|
||||
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
|
||||
WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
|
||||
|
||||
Frameworkcontroller() frameworkcontroller.Interface
|
||||
}
|
||||
|
||||
func (f *sharedInformerFactory) Frameworkcontroller() frameworkcontroller.Interface {
|
||||
return frameworkcontroller.New(f, f.namespace, f.tweakListOptions)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package frameworkcontroller
|
||||
|
||||
import (
|
||||
v1 "github.com/microsoft/frameworkcontroller/pkg/client/informers/externalversions/frameworkcontroller/v1"
|
||||
internalinterfaces "github.com/microsoft/frameworkcontroller/pkg/client/informers/externalversions/internalinterfaces"
|
||||
)
|
||||
|
||||
// Interface provides access to each of this group's versions.
|
||||
type Interface interface {
|
||||
// V1 provides access to shared informers for resources in V1.
|
||||
V1() v1.Interface
|
||||
}
|
||||
|
||||
type group struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// V1 returns a new v1.Interface.
|
||||
func (g *group) V1() v1.Interface {
|
||||
return v1.New(g.factory, g.namespace, g.tweakListOptions)
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
frameworkcontrollerv1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
versioned "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned"
|
||||
internalinterfaces "github.com/microsoft/frameworkcontroller/pkg/client/informers/externalversions/internalinterfaces"
|
||||
v1 "github.com/microsoft/frameworkcontroller/pkg/client/listers/frameworkcontroller/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// FrameworkInformer provides access to a shared informer and lister for
|
||||
// Frameworks.
|
||||
type FrameworkInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v1.FrameworkLister
|
||||
}
|
||||
|
||||
type frameworkInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewFrameworkInformer constructs a new informer for Framework type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFrameworkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredFrameworkInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredFrameworkInformer constructs a new informer for Framework type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredFrameworkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.FrameworkcontrollerV1().Frameworks(namespace).List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.FrameworkcontrollerV1().Frameworks(namespace).Watch(options)
|
||||
},
|
||||
},
|
||||
&frameworkcontrollerv1.Framework{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *frameworkInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredFrameworkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *frameworkInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&frameworkcontrollerv1.Framework{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *frameworkInformer) Lister() v1.FrameworkLister {
|
||||
return v1.NewFrameworkLister(f.Informer().GetIndexer())
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
internalinterfaces "github.com/microsoft/frameworkcontroller/pkg/client/informers/externalversions/internalinterfaces"
|
||||
)
|
||||
|
||||
// Interface provides access to all the informers in this group version.
|
||||
type Interface interface {
|
||||
// Frameworks returns a FrameworkInformer.
|
||||
Frameworks() FrameworkInformer
|
||||
}
|
||||
|
||||
type version struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// Frameworks returns a FrameworkInformer.
|
||||
func (v *version) Frameworks() FrameworkInformer {
|
||||
return &frameworkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package externalversions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// GenericInformer is type of SharedIndexInformer which will locate and delegate to other
|
||||
// sharedInformers based on type
|
||||
type GenericInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() cache.GenericLister
|
||||
}
|
||||
|
||||
type genericInformer struct {
|
||||
informer cache.SharedIndexInformer
|
||||
resource schema.GroupResource
|
||||
}
|
||||
|
||||
// Informer returns the SharedIndexInformer.
|
||||
func (f *genericInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.informer
|
||||
}
|
||||
|
||||
// Lister returns the GenericLister.
|
||||
func (f *genericInformer) Lister() cache.GenericLister {
|
||||
return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)
|
||||
}
|
||||
|
||||
// ForResource gives generic access to a shared informer of the matching type
|
||||
// TODO extend this to unknown resources with a client pool
|
||||
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
|
||||
switch resource {
|
||||
// Group=frameworkcontroller.microsoft.com, Version=v1
|
||||
case v1.SchemeGroupVersion.WithResource("frameworks"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Frameworkcontroller().V1().Frameworks().Informer()}, nil
|
||||
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no informer found for %v", resource)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package internalinterfaces
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
versioned "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer
|
||||
|
||||
// SharedInformerFactory a small interface to allow for adding an informer without an import cycle
|
||||
type SharedInformerFactory interface {
|
||||
Start(stopCh <-chan struct{})
|
||||
InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
type TweakListOptionsFunc func(*v1.ListOptions)
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
// FrameworkListerExpansion allows custom methods to be added to
|
||||
// FrameworkLister.
|
||||
type FrameworkListerExpansion interface{}
|
||||
|
||||
// FrameworkNamespaceListerExpansion allows custom methods to be added to
|
||||
// FrameworkNamespaceLister.
|
||||
type FrameworkNamespaceListerExpansion interface{}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// FrameworkLister helps list Frameworks.
|
||||
type FrameworkLister interface {
|
||||
// List lists all Frameworks in the indexer.
|
||||
List(selector labels.Selector) (ret []*v1.Framework, err error)
|
||||
// Frameworks returns an object that can list and get Frameworks.
|
||||
Frameworks(namespace string) FrameworkNamespaceLister
|
||||
FrameworkListerExpansion
|
||||
}
|
||||
|
||||
// frameworkLister implements the FrameworkLister interface.
|
||||
type frameworkLister struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
// NewFrameworkLister returns a new FrameworkLister.
|
||||
func NewFrameworkLister(indexer cache.Indexer) FrameworkLister {
|
||||
return &frameworkLister{indexer: indexer}
|
||||
}
|
||||
|
||||
// List lists all Frameworks in the indexer.
|
||||
func (s *frameworkLister) List(selector labels.Selector) (ret []*v1.Framework, err error) {
|
||||
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v1.Framework))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Frameworks returns an object that can list and get Frameworks.
|
||||
func (s *frameworkLister) Frameworks(namespace string) FrameworkNamespaceLister {
|
||||
return frameworkNamespaceLister{indexer: s.indexer, namespace: namespace}
|
||||
}
|
||||
|
||||
// FrameworkNamespaceLister helps list and get Frameworks.
|
||||
type FrameworkNamespaceLister interface {
|
||||
// List lists all Frameworks in the indexer for a given namespace.
|
||||
List(selector labels.Selector) (ret []*v1.Framework, err error)
|
||||
// Get retrieves the Framework from the indexer for a given namespace and name.
|
||||
Get(name string) (*v1.Framework, error)
|
||||
FrameworkNamespaceListerExpansion
|
||||
}
|
||||
|
||||
// frameworkNamespaceLister implements the FrameworkNamespaceLister
|
||||
// interface.
|
||||
type frameworkNamespaceLister struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
// List lists all Frameworks in the indexer for a given namespace.
|
||||
func (s frameworkNamespaceLister) List(selector labels.Selector) (ret []*v1.Framework, err error) {
|
||||
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v1.Framework))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves the Framework from the indexer for a given namespace and name.
|
||||
func (s frameworkNamespaceLister) Get(name string) (*v1.Framework, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(v1.Resource("framework"), name)
|
||||
}
|
||||
return obj.(*v1.Framework), nil
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Empty struct{}
|
||||
type T interface{}
|
||||
|
||||
type Set struct {
|
||||
items map[T]Empty
|
||||
}
|
||||
|
||||
func NewSet(items ...T) Set {
|
||||
s := Set{items: map[T]Empty{}}
|
||||
for _, item := range items {
|
||||
s.Add(item)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Set) Contains(item T) bool {
|
||||
_, exists := s.items[item]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s Set) Add(item T) Set {
|
||||
s.items[item] = Empty{}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Set) Delete(item T) Set {
|
||||
delete(s.items, item)
|
||||
return s
|
||||
}
|
||||
|
||||
type ImmutableSet struct {
|
||||
set Set
|
||||
}
|
||||
|
||||
func NewImmutableSet(items ...T) ImmutableSet {
|
||||
return ImmutableSet{set: NewSet(items...)}
|
||||
}
|
||||
|
||||
func (s ImmutableSet) Contains(item T) bool {
|
||||
return s.set.Contains(item)
|
||||
}
|
||||
|
||||
type LogHook struct {
|
||||
LogLevels []log.Level
|
||||
LogWriter io.Writer
|
||||
}
|
||||
|
||||
func (lh *LogHook) Levels() []log.Level {
|
||||
return lh.LogLevels
|
||||
}
|
||||
|
||||
func (lh *LogHook) Fire(entry *log.Entry) error {
|
||||
str, err := entry.String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = lh.LogWriter.Write([]byte(str))
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"os"
|
||||
"gopkg.in/yaml.v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func Quote(s string) string {
|
||||
return `"` + s + `"`
|
||||
}
|
||||
|
||||
func ReferEnvVar(name string) string {
|
||||
return "$(" + name + ")"
|
||||
}
|
||||
|
||||
func PtrString(o string) *string {
|
||||
return &o
|
||||
}
|
||||
|
||||
func PtrInt32(o int32) *int32 {
|
||||
return &o
|
||||
}
|
||||
|
||||
func NilInt32() *int32 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func PtrInt64(o int64) *int64 {
|
||||
return &o
|
||||
}
|
||||
|
||||
func PtrFloat64(o float64) *float64 {
|
||||
return &o
|
||||
}
|
||||
|
||||
func PtrBool(o bool) *bool {
|
||||
return &o
|
||||
}
|
||||
|
||||
func NilBool() *bool {
|
||||
return nil
|
||||
}
|
||||
|
||||
func PtrUID(o types.UID) *types.UID {
|
||||
return &o
|
||||
}
|
||||
|
||||
func PtrUIDStr(s string) *types.UID {
|
||||
return PtrUID(types.UID(s))
|
||||
}
|
||||
|
||||
func PtrNow() *meta.Time {
|
||||
now := meta.Now()
|
||||
return &now
|
||||
}
|
||||
|
||||
func SecToDuration(sec *int64) time.Duration {
|
||||
return time.Duration(*sec) * time.Second
|
||||
}
|
||||
|
||||
func IsTimeout(leftDuration time.Duration) bool {
|
||||
// Align with the AddAfter method of the workqueue
|
||||
return leftDuration <= 0
|
||||
}
|
||||
|
||||
func CurrentLeftDuration(startTime meta.Time, timeoutSec *int64) time.Duration {
|
||||
currentDuration := time.Since(startTime.Time)
|
||||
timeoutDuration := SecToDuration(timeoutSec)
|
||||
leftDuration := timeoutDuration - currentDuration
|
||||
return leftDuration
|
||||
}
|
||||
|
||||
func InitAll() {
|
||||
InitLogger()
|
||||
InitRandSeed()
|
||||
}
|
||||
|
||||
func InitLogger() {
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: true,
|
||||
// Always log with full timestamp, regardless of whether TTY is attached
|
||||
DisableTimestamp: false,
|
||||
FullTimestamp: true,
|
||||
// Align with k8s.io/apimachinery/pkg/apis/meta/v1.Time
|
||||
TimestampFormat: time.RFC3339,
|
||||
})
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetOutput(os.Stdout)
|
||||
}
|
||||
|
||||
func LogLines(format string, args ...interface{}) {
|
||||
lines := strings.Split(fmt.Sprintf(format, args), "\n")
|
||||
for _, line := range lines {
|
||||
log.Infof(line)
|
||||
}
|
||||
}
|
||||
|
||||
func InitRandSeed() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
|
||||
// Rand in range [min, max]
|
||||
func RandInt64(min int64, max int64) int64 {
|
||||
return min + rand.Int63n(max-min+1)
|
||||
}
|
||||
|
||||
func ToYaml(obj interface{}) string {
|
||||
yamlBytes, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to marshal Object %#v to YAML: %v", obj, err))
|
||||
}
|
||||
return string(yamlBytes)
|
||||
}
|
||||
|
||||
func FromYaml(yamlStr string, objAddr interface{}) {
|
||||
err := yaml.Unmarshal([]byte(yamlStr), objAddr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to unmarshal YAML %#v to Object: %v", yamlStr, err))
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,119 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
log "github.com/sirupsen/logrus"
|
||||
apiExtensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiClient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
apiErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/rest"
|
||||
"github.com/microsoft/frameworkcontroller/pkg/common"
|
||||
)
|
||||
|
||||
func PutCRD(
|
||||
config *rest.Config, crd *apiExtensions.CustomResourceDefinition,
|
||||
establishedCheckIntervalSec *int64, establishedCheckTimeoutSec *int64) {
|
||||
client := createCRDClient(config)
|
||||
|
||||
err := putCRDInternal(client, crd, establishedCheckIntervalSec, establishedCheckTimeoutSec)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to put CRD: %v", err))
|
||||
} else {
|
||||
log.Infof("Succeeded to put CRD")
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteCRD(config *rest.Config, name string) {
|
||||
client := createCRDClient(config)
|
||||
|
||||
err := client.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(name, meta.NewDeleteOptions(0))
|
||||
if err != nil && !apiErrors.IsNotFound(err) {
|
||||
panic(fmt.Errorf("Failed to delete CRD: %v", err))
|
||||
} else {
|
||||
log.Infof("Succeeded to delete CRD")
|
||||
}
|
||||
}
|
||||
|
||||
func createCRDClient(config *rest.Config) apiClient.Interface {
|
||||
client, err := apiClient.NewForConfig(config)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to create CRDClient: %v", err))
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func putCRDInternal(
|
||||
client apiClient.Interface, newCRD *apiExtensions.CustomResourceDefinition,
|
||||
establishedCheckIntervalSec *int64, establishedCheckTimeoutSec *int64) error {
|
||||
|
||||
remoteCRD, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().Get(newCRD.Name, meta.GetOptions{})
|
||||
if err == nil {
|
||||
log.Infof("Update CRD %v", newCRD.Name)
|
||||
if !reflect.DeepEqual(remoteCRD.Spec, newCRD.Spec) {
|
||||
updateCRD := remoteCRD
|
||||
updateCRD.Spec = newCRD.Spec
|
||||
remoteCRD, err = client.ApiextensionsV1beta1().CustomResourceDefinitions().Update(updateCRD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if apiErrors.IsNotFound(err) {
|
||||
log.Infof("Create CRD %v", newCRD.Name)
|
||||
remoteCRD, err = client.ApiextensionsV1beta1().CustomResourceDefinitions().Create(newCRD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
if isCRDEstablished(remoteCRD) {
|
||||
return nil
|
||||
}
|
||||
return wait.Poll(
|
||||
common.SecToDuration(establishedCheckIntervalSec),
|
||||
common.SecToDuration(establishedCheckTimeoutSec),
|
||||
func() (bool, error) {
|
||||
remoteCRD, err = client.ApiextensionsV1beta1().CustomResourceDefinitions().Get(newCRD.Name, meta.GetOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return isCRDEstablished(remoteCRD), nil
|
||||
})
|
||||
}
|
||||
|
||||
func isCRDEstablished(crd *apiExtensions.CustomResourceDefinition) bool {
|
||||
for _, cond := range crd.Status.Conditions {
|
||||
if cond.Status == apiExtensions.ConditionTrue &&
|
||||
cond.Type == apiExtensions.Established {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// MIT License
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/rest"
|
||||
core "k8s.io/api/core/v1"
|
||||
kubeClient "k8s.io/client-go/kubernetes"
|
||||
frameworkClient "github.com/microsoft/frameworkcontroller/pkg/client/clientset/versioned"
|
||||
ci "github.com/microsoft/frameworkcontroller/pkg/apis/frameworkcontroller/v1"
|
||||
)
|
||||
|
||||
func BuildKubeConfig(cConfig *ci.ControllerConfig) (*rest.Config) {
|
||||
kConfig, err := clientcmd.BuildConfigFromFlags(
|
||||
*cConfig.KubeApiServerAddress, *cConfig.KubeConfigFilePath)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to build KubeConfig, please ensure "+
|
||||
"config kubeApiServerAddress or config kubeConfigFilePath or "+
|
||||
"${KUBE_APISERVER_ADDRESS} or ${KUBECONFIG} or ${HOME}/.kube/config or "+
|
||||
"${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT} is valid: "+
|
||||
"Error: %v",
|
||||
err))
|
||||
}
|
||||
return kConfig
|
||||
}
|
||||
|
||||
func CreateClients(kConfig *rest.Config) (
|
||||
kubeClient.Interface, frameworkClient.Interface) {
|
||||
kClient, err := kubeClient.NewForConfig(kConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to create KubeClient: %v", err))
|
||||
}
|
||||
|
||||
fClient, err := frameworkClient.NewForConfig(kConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to create FrameworkClient: %v", err))
|
||||
}
|
||||
|
||||
return kClient, fClient
|
||||
}
|
||||
|
||||
func GetKey(obj interface{}) (string, error) {
|
||||
return cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||
}
|
||||
|
||||
func SplitKey(key string) (namespace, name string, err error) {
|
||||
return cache.SplitMetaNamespaceKey(key)
|
||||
}
|
||||
|
||||
// obj could be *core.ConfigMap or cache.DeletedFinalStateUnknown.
|
||||
func ToConfigMap(obj interface{}) *core.ConfigMap {
|
||||
cm, ok := obj.(*core.ConfigMap)
|
||||
|
||||
if !ok {
|
||||
deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
log.Errorf(
|
||||
"Failed to convert obj to ConfigMap or DeletedFinalStateUnknown: %#v",
|
||||
obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
cm, ok = deletedFinalStateUnknown.Obj.(*core.ConfigMap)
|
||||
if !ok {
|
||||
log.Errorf(
|
||||
"Failed to convert DeletedFinalStateUnknown.Obj to ConfigMap: %#v",
|
||||
deletedFinalStateUnknown)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
// obj could be *core.Pod or cache.DeletedFinalStateUnknown.
|
||||
func ToPod(obj interface{}) *core.Pod {
|
||||
pod, ok := obj.(*core.Pod)
|
||||
|
||||
if !ok {
|
||||
deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
log.Errorf(
|
||||
"Failed to convert obj to Pod or DeletedFinalStateUnknown: %#v",
|
||||
obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok = deletedFinalStateUnknown.Obj.(*core.Pod)
|
||||
if !ok {
|
||||
log.Errorf(
|
||||
"Failed to convert DeletedFinalStateUnknown.Obj to Pod: %#v",
|
||||
deletedFinalStateUnknown)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return pod
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
Copyright (c) 2012, Martin Angers
|
||||
All rights reserved.
|
||||
|
||||
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 the author 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 HOLDER 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.
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
Package purell offers URL normalization as described on the wikipedia page:
|
||||
http://en.wikipedia.org/wiki/URL_normalization
|
||||
*/
|
||||
package purell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/urlesc"
|
||||
"golang.org/x/net/idna"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
"golang.org/x/text/width"
|
||||
)
|
||||
|
||||
// A set of normalization flags determines how a URL will
|
||||
// be normalized.
|
||||
type NormalizationFlags uint
|
||||
|
||||
const (
|
||||
// Safe normalizations
|
||||
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
|
||||
FlagLowercaseHost // http://HOST -> http://host
|
||||
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
|
||||
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
|
||||
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
|
||||
FlagRemoveDefaultPort // http://host:80 -> http://host
|
||||
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
|
||||
|
||||
// Usually safe normalizations
|
||||
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
|
||||
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
|
||||
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
|
||||
|
||||
// Unsafe normalizations
|
||||
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
|
||||
FlagRemoveFragment // http://host/path#fragment -> http://host/path
|
||||
FlagForceHTTP // https://host -> http://host
|
||||
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
|
||||
FlagRemoveWWW // http://www.host/ -> http://host/
|
||||
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
|
||||
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
|
||||
|
||||
// Normalizations not in the wikipedia article, required to cover tests cases
|
||||
// submitted by jehiah
|
||||
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
|
||||
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
|
||||
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
|
||||
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
|
||||
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
|
||||
|
||||
// Convenience set of safe normalizations
|
||||
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
|
||||
|
||||
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
|
||||
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
|
||||
|
||||
// Convenience set of usually safe normalizations (includes FlagsSafe)
|
||||
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
|
||||
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
|
||||
|
||||
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
|
||||
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
|
||||
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
|
||||
|
||||
// Convenience set of all available flags
|
||||
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
||||
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHttpPort = ":80"
|
||||
defaultHttpsPort = ":443"
|
||||
)
|
||||
|
||||
// Regular expressions used by the normalizations
|
||||
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
|
||||
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
|
||||
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
|
||||
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
|
||||
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
|
||||
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
|
||||
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
|
||||
var rxEmptyPort = regexp.MustCompile(`:+$`)
|
||||
|
||||
// Map of flags to implementation function.
|
||||
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
|
||||
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
|
||||
|
||||
// Since maps have undefined traversing order, make a slice of ordered keys
|
||||
var flagsOrder = []NormalizationFlags{
|
||||
FlagLowercaseScheme,
|
||||
FlagLowercaseHost,
|
||||
FlagRemoveDefaultPort,
|
||||
FlagRemoveDirectoryIndex,
|
||||
FlagRemoveDotSegments,
|
||||
FlagRemoveFragment,
|
||||
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
|
||||
FlagRemoveDuplicateSlashes,
|
||||
FlagRemoveWWW,
|
||||
FlagAddWWW,
|
||||
FlagSortQuery,
|
||||
FlagDecodeDWORDHost,
|
||||
FlagDecodeOctalHost,
|
||||
FlagDecodeHexHost,
|
||||
FlagRemoveUnnecessaryHostDots,
|
||||
FlagRemoveEmptyPortSeparator,
|
||||
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
|
||||
FlagAddTrailingSlash,
|
||||
}
|
||||
|
||||
// ... and then the map, where order is unimportant
|
||||
var flags = map[NormalizationFlags]func(*url.URL){
|
||||
FlagLowercaseScheme: lowercaseScheme,
|
||||
FlagLowercaseHost: lowercaseHost,
|
||||
FlagRemoveDefaultPort: removeDefaultPort,
|
||||
FlagRemoveDirectoryIndex: removeDirectoryIndex,
|
||||
FlagRemoveDotSegments: removeDotSegments,
|
||||
FlagRemoveFragment: removeFragment,
|
||||
FlagForceHTTP: forceHTTP,
|
||||
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
|
||||
FlagRemoveWWW: removeWWW,
|
||||
FlagAddWWW: addWWW,
|
||||
FlagSortQuery: sortQuery,
|
||||
FlagDecodeDWORDHost: decodeDWORDHost,
|
||||
FlagDecodeOctalHost: decodeOctalHost,
|
||||
FlagDecodeHexHost: decodeHexHost,
|
||||
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
|
||||
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
|
||||
FlagRemoveTrailingSlash: removeTrailingSlash,
|
||||
FlagAddTrailingSlash: addTrailingSlash,
|
||||
}
|
||||
|
||||
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
|
||||
// It takes an URL string as input, as well as the normalization flags.
|
||||
func MustNormalizeURLString(u string, f NormalizationFlags) string {
|
||||
result, e := NormalizeURLString(u, f)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
|
||||
// It takes an URL string as input, as well as the normalization flags.
|
||||
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
|
||||
parsed, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if f&FlagLowercaseHost == FlagLowercaseHost {
|
||||
parsed.Host = strings.ToLower(parsed.Host)
|
||||
}
|
||||
|
||||
// The idna package doesn't fully conform to RFC 5895
|
||||
// (https://tools.ietf.org/html/rfc5895), so we do it here.
|
||||
// Taken from Go 1.8 cycle source, courtesy of bradfitz.
|
||||
// TODO: Remove when (if?) idna package conforms to RFC 5895.
|
||||
parsed.Host = width.Fold.String(parsed.Host)
|
||||
parsed.Host = norm.NFC.String(parsed.Host)
|
||||
if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return NormalizeURL(parsed, f), nil
|
||||
}
|
||||
|
||||
// NormalizeURL returns the normalized string.
|
||||
// It takes a parsed URL object as input, as well as the normalization flags.
|
||||
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
|
||||
for _, k := range flagsOrder {
|
||||
if f&k == k {
|
||||
flags[k](u)
|
||||
}
|
||||
}
|
||||
return urlesc.Escape(u)
|
||||
}
|
||||
|
||||
func lowercaseScheme(u *url.URL) {
|
||||
if len(u.Scheme) > 0 {
|
||||
u.Scheme = strings.ToLower(u.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func lowercaseHost(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
u.Host = strings.ToLower(u.Host)
|
||||
}
|
||||
}
|
||||
|
||||
func removeDefaultPort(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
scheme := strings.ToLower(u.Scheme)
|
||||
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
|
||||
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func removeTrailingSlash(u *url.URL) {
|
||||
if l := len(u.Path); l > 0 {
|
||||
if strings.HasSuffix(u.Path, "/") {
|
||||
u.Path = u.Path[:l-1]
|
||||
}
|
||||
} else if l = len(u.Host); l > 0 {
|
||||
if strings.HasSuffix(u.Host, "/") {
|
||||
u.Host = u.Host[:l-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addTrailingSlash(u *url.URL) {
|
||||
if l := len(u.Path); l > 0 {
|
||||
if !strings.HasSuffix(u.Path, "/") {
|
||||
u.Path += "/"
|
||||
}
|
||||
} else if l = len(u.Host); l > 0 {
|
||||
if !strings.HasSuffix(u.Host, "/") {
|
||||
u.Host += "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeDotSegments(u *url.URL) {
|
||||
if len(u.Path) > 0 {
|
||||
var dotFree []string
|
||||
var lastIsDot bool
|
||||
|
||||
sections := strings.Split(u.Path, "/")
|
||||
for _, s := range sections {
|
||||
if s == ".." {
|
||||
if len(dotFree) > 0 {
|
||||
dotFree = dotFree[:len(dotFree)-1]
|
||||
}
|
||||
} else if s != "." {
|
||||
dotFree = append(dotFree, s)
|
||||
}
|
||||
lastIsDot = (s == "." || s == "..")
|
||||
}
|
||||
// Special case if host does not end with / and new path does not begin with /
|
||||
u.Path = strings.Join(dotFree, "/")
|
||||
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
|
||||
u.Path = "/" + u.Path
|
||||
}
|
||||
// Special case if the last segment was a dot, make sure the path ends with a slash
|
||||
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
|
||||
u.Path += "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeDirectoryIndex(u *url.URL) {
|
||||
if len(u.Path) > 0 {
|
||||
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
|
||||
}
|
||||
}
|
||||
|
||||
func removeFragment(u *url.URL) {
|
||||
u.Fragment = ""
|
||||
}
|
||||
|
||||
func forceHTTP(u *url.URL) {
|
||||
if strings.ToLower(u.Scheme) == "https" {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
}
|
||||
|
||||
func removeDuplicateSlashes(u *url.URL) {
|
||||
if len(u.Path) > 0 {
|
||||
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
|
||||
}
|
||||
}
|
||||
|
||||
func removeWWW(u *url.URL) {
|
||||
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
||||
u.Host = u.Host[4:]
|
||||
}
|
||||
}
|
||||
|
||||
func addWWW(u *url.URL) {
|
||||
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
||||
u.Host = "www." + u.Host
|
||||
}
|
||||
}
|
||||
|
||||
func sortQuery(u *url.URL) {
|
||||
q := u.Query()
|
||||
|
||||
if len(q) > 0 {
|
||||
arKeys := make([]string, len(q))
|
||||
i := 0
|
||||
for k, _ := range q {
|
||||
arKeys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(arKeys)
|
||||
buf := new(bytes.Buffer)
|
||||
for _, k := range arKeys {
|
||||
sort.Strings(q[k])
|
||||
for _, v := range q[k] {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteRune('&')
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the raw query string
|
||||
u.RawQuery = buf.String()
|
||||
}
|
||||
}
|
||||
|
||||
func decodeDWORDHost(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
||||
var parts [4]int64
|
||||
|
||||
dword, _ := strconv.ParseInt(matches[1], 10, 0)
|
||||
for i, shift := range []uint{24, 16, 8, 0} {
|
||||
parts[i] = dword >> shift & 0xFF
|
||||
}
|
||||
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeOctalHost(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
|
||||
var parts [4]int64
|
||||
|
||||
for i := 1; i <= 4; i++ {
|
||||
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
|
||||
}
|
||||
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeHexHost(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
||||
// Conversion is safe because of regex validation
|
||||
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
|
||||
// Set host as DWORD (base 10) encoded host
|
||||
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
|
||||
// The rest is the same as decoding a DWORD host
|
||||
decodeDWORDHost(u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeUnncessaryHostDots(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
|
||||
// Trim the leading and trailing dots
|
||||
u.Host = strings.Trim(matches[1], ".")
|
||||
if len(matches) > 2 {
|
||||
u.Host += matches[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeEmptyPortSeparator(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
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.
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package urlesc implements query escaping as per RFC 3986.
|
||||
// It contains some parts of the net/url package, modified so as to allow
|
||||
// some reserved characters incorrectly escaped by net/url.
|
||||
// See https://github.com/golang/go/issues/5684
|
||||
package urlesc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type encoding int
|
||||
|
||||
const (
|
||||
encodePath encoding = 1 + iota
|
||||
encodeUserPassword
|
||||
encodeQueryComponent
|
||||
encodeFragment
|
||||
)
|
||||
|
||||
// Return true if the specified character should be escaped when
|
||||
// appearing in a URL string, according to RFC 3986.
|
||||
func shouldEscape(c byte, mode encoding) bool {
|
||||
// §2.3 Unreserved characters (alphanum)
|
||||
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
|
||||
return false
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
|
||||
return false
|
||||
|
||||
// §2.2 Reserved characters (reserved)
|
||||
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
|
||||
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
|
||||
// Different sections of the URL allow a few of
|
||||
// the reserved characters to appear unescaped.
|
||||
switch mode {
|
||||
case encodePath: // §3.3
|
||||
// The RFC allows sub-delims and : @.
|
||||
// '/', '[' and ']' can be used to assign meaning to individual path
|
||||
// segments. This package only manipulates the path as a whole,
|
||||
// so we allow those as well. That leaves only ? and # to escape.
|
||||
return c == '?' || c == '#'
|
||||
|
||||
case encodeUserPassword: // §3.2.1
|
||||
// The RFC allows : and sub-delims in
|
||||
// userinfo. The parsing of userinfo treats ':' as special so we must escape
|
||||
// all the gen-delims.
|
||||
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
|
||||
|
||||
case encodeQueryComponent: // §3.4
|
||||
// The RFC allows / and ?.
|
||||
return c != '/' && c != '?'
|
||||
|
||||
case encodeFragment: // §4.1
|
||||
// The RFC text is silent but the grammar allows
|
||||
// everything, so escape nothing but #
|
||||
return c == '#'
|
||||
}
|
||||
}
|
||||
|
||||
// Everything else must be escaped.
|
||||
return true
|
||||
}
|
||||
|
||||
// QueryEscape escapes the string so it can be safely placed
|
||||
// inside a URL query.
|
||||
func QueryEscape(s string) string {
|
||||
return escape(s, encodeQueryComponent)
|
||||
}
|
||||
|
||||
func escape(s string, mode encoding) string {
|
||||
spaceCount, hexCount := 0, 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if shouldEscape(c, mode) {
|
||||
if c == ' ' && mode == encodeQueryComponent {
|
||||
spaceCount++
|
||||
} else {
|
||||
hexCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if spaceCount == 0 && hexCount == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
t := make([]byte, len(s)+2*hexCount)
|
||||
j := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch c := s[i]; {
|
||||
case c == ' ' && mode == encodeQueryComponent:
|
||||
t[j] = '+'
|
||||
j++
|
||||
case shouldEscape(c, mode):
|
||||
t[j] = '%'
|
||||
t[j+1] = "0123456789ABCDEF"[c>>4]
|
||||
t[j+2] = "0123456789ABCDEF"[c&15]
|
||||
j += 3
|
||||
default:
|
||||
t[j] = s[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
return string(t)
|
||||
}
|
||||
|
||||
var uiReplacer = strings.NewReplacer(
|
||||
"%21", "!",
|
||||
"%27", "'",
|
||||
"%28", "(",
|
||||
"%29", ")",
|
||||
"%2A", "*",
|
||||
)
|
||||
|
||||
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
|
||||
func unescapeUserinfo(s string) string {
|
||||
return uiReplacer.Replace(s)
|
||||
}
|
||||
|
||||
// Escape reassembles the URL into a valid URL string.
|
||||
// The general form of the result is one of:
|
||||
//
|
||||
// scheme:opaque
|
||||
// scheme://userinfo@host/path?query#fragment
|
||||
//
|
||||
// If u.Opaque is non-empty, String uses the first form;
|
||||
// otherwise it uses the second form.
|
||||
//
|
||||
// In the second form, the following rules apply:
|
||||
// - if u.Scheme is empty, scheme: is omitted.
|
||||
// - if u.User is nil, userinfo@ is omitted.
|
||||
// - if u.Host is empty, host/ is omitted.
|
||||
// - if u.Scheme and u.Host are empty and u.User is nil,
|
||||
// the entire scheme://userinfo@host/ is omitted.
|
||||
// - if u.Host is non-empty and u.Path begins with a /,
|
||||
// the form host/path does not add its own /.
|
||||
// - if u.RawQuery is empty, ?query is omitted.
|
||||
// - if u.Fragment is empty, #fragment is omitted.
|
||||
func Escape(u *url.URL) string {
|
||||
var buf bytes.Buffer
|
||||
if u.Scheme != "" {
|
||||
buf.WriteString(u.Scheme)
|
||||
buf.WriteByte(':')
|
||||
}
|
||||
if u.Opaque != "" {
|
||||
buf.WriteString(u.Opaque)
|
||||
} else {
|
||||
if u.Scheme != "" || u.Host != "" || u.User != nil {
|
||||
buf.WriteString("//")
|
||||
if ui := u.User; ui != nil {
|
||||
buf.WriteString(unescapeUserinfo(ui.String()))
|
||||
buf.WriteByte('@')
|
||||
}
|
||||
if h := u.Host; h != "" {
|
||||
buf.WriteString(h)
|
||||
}
|
||||
}
|
||||
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
|
||||
buf.WriteByte('/')
|
||||
}
|
||||
buf.WriteString(escape(u.Path, encodePath))
|
||||
}
|
||||
if u.RawQuery != "" {
|
||||
buf.WriteByte('?')
|
||||
buf.WriteString(u.RawQuery)
|
||||
}
|
||||
if u.Fragment != "" {
|
||||
buf.WriteByte('#')
|
||||
buf.WriteString(escape(u.Fragment, encodeFragment))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build !js,!appengine,!safe,!disableunsafe
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
var (
|
||||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||
// internal reflect.Value fields. These values are valid before golang
|
||||
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||
// the original format. Code in the init function updates these offsets
|
||||
// as necessary.
|
||||
offsetPtr = uintptr(ptrSize)
|
||||
offsetScalar = uintptr(0)
|
||||
offsetFlag = uintptr(ptrSize * 2)
|
||||
|
||||
// flagKindWidth and flagKindShift indicate various bits that the
|
||||
// reflect package uses internally to track kind information.
|
||||
//
|
||||
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||
// read-only.
|
||||
//
|
||||
// flagIndir indicates whether the value field of a reflect.Value is
|
||||
// the actual data or a pointer to the data.
|
||||
//
|
||||
// These values are valid before golang commit 90a7c3c86944 which
|
||||
// changed their positions. Code in the init function updates these
|
||||
// flags as necessary.
|
||||
flagKindWidth = uintptr(5)
|
||||
flagKindShift = uintptr(flagKindWidth - 1)
|
||||
flagRO = uintptr(1 << 0)
|
||||
flagIndir = uintptr(1 << 1)
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Older versions of reflect.Value stored small integers directly in the
|
||||
// ptr field (which is named val in the older versions). Versions
|
||||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||
// scalar for this purpose which unfortunately came before the flag
|
||||
// field, so the offset of the flag field is different for those
|
||||
// versions.
|
||||
//
|
||||
// This code constructs a new reflect.Value from a known small integer
|
||||
// and checks if the size of the reflect.Value struct indicates it has
|
||||
// the scalar field. When it does, the offsets are updated accordingly.
|
||||
vv := reflect.ValueOf(0xf00)
|
||||
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
||||
offsetScalar = ptrSize * 2
|
||||
offsetFlag = ptrSize * 3
|
||||
}
|
||||
|
||||
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||
// order bits are the kind. This code extracts the kind from the flags
|
||||
// field and ensures it's the correct type. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are updated
|
||||
// accordingly.
|
||||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
||||
upfv := *(*uintptr)(upf)
|
||||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
||||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
||||
flagKindShift = 0
|
||||
flagRO = 1 << 5
|
||||
flagIndir = 1 << 6
|
||||
|
||||
// Commit adf9b30e5594 modified the flags to separate the
|
||||
// flagRO flag into two bits which specifies whether or not the
|
||||
// field is embedded. This causes flagIndir to move over a bit
|
||||
// and means that flagRO is the combination of either of the
|
||||
// original flagRO bit and the new bit.
|
||||
//
|
||||
// This code detects the change by extracting what used to be
|
||||
// the indirect bit to ensure it's set. When it's not, the flag
|
||||
// order has been changed to the newer format, so the flags are
|
||||
// updated accordingly.
|
||||
if upfv&flagIndir == 0 {
|
||||
flagRO = 3 << 5
|
||||
flagIndir = 1 << 7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
||||
indirects := 1
|
||||
vt := v.Type()
|
||||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
||||
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
||||
if rvf&flagIndir != 0 {
|
||||
vt = reflect.PtrTo(v.Type())
|
||||
indirects++
|
||||
} else if offsetScalar != 0 {
|
||||
// The value is in the scalar field when it's not one of the
|
||||
// reference types.
|
||||
switch vt.Kind() {
|
||||
case reflect.Uintptr:
|
||||
case reflect.Chan:
|
||||
case reflect.Func:
|
||||
case reflect.Map:
|
||||
case reflect.Ptr:
|
||||
case reflect.UnsafePointer:
|
||||
default:
|
||||
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
||||
offsetScalar)
|
||||
}
|
||||
}
|
||||
|
||||
pv := reflect.NewAt(vt, upv)
|
||||
rv = pv
|
||||
for i := 0; i < indirects; i++ {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
return rv
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound == true:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound == true:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||
const supportedFlags = "0-+# "
|
||||
|
||||
// formatState implements the fmt.Formatter interface and contains information
|
||||
// about the state of a formatting operation. The NewFormatter function can
|
||||
// be used to get a new Formatter which can be used directly as arguments
|
||||
// in standard fmt package printing calls.
|
||||
type formatState struct {
|
||||
value interface{}
|
||||
fs fmt.State
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// buildDefaultFormat recreates the original format string without precision
|
||||
// and width information to pass in to fmt.Sprintf in the case of an
|
||||
// unrecognized type. Unless new types are added to the language, this
|
||||
// function won't ever be called.
|
||||
func (f *formatState) buildDefaultFormat() (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteRune('v')
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// constructOrigFormat recreates the original format string including precision
|
||||
// and width information to pass along to the standard fmt package. This allows
|
||||
// automatic deferral of all format strings this package doesn't support.
|
||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if width, ok := f.fs.Width(); ok {
|
||||
buf.WriteString(strconv.Itoa(width))
|
||||
}
|
||||
|
||||
if precision, ok := f.fs.Precision(); ok {
|
||||
buf.Write(precisionBytes)
|
||||
buf.WriteString(strconv.Itoa(precision))
|
||||
}
|
||||
|
||||
buf.WriteRune(verb)
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||
// ensures that types for values which have been unpacked from an interface
|
||||
// are displayed when the show types flag is also set.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface {
|
||||
f.ignoreNextType = false
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (f *formatState) formatPtr(v reflect.Value) {
|
||||
// Display nil if top level pointer is nil.
|
||||
showTypes := f.fs.Flag('#')
|
||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range f.pointers {
|
||||
if depth >= f.depth {
|
||||
delete(f.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to possibly show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by derferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
f.pointers[addr] = f.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type or indirection level depending on flags.
|
||||
if showTypes && !f.ignoreNextType {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
f.fs.Write([]byte(ve.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
} else {
|
||||
if nilFound || cycleFound {
|
||||
indirects += strings.Count(ve.Type().String(), "*")
|
||||
}
|
||||
f.fs.Write(openAngleBytes)
|
||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||
f.fs.Write(closeAngleBytes)
|
||||
}
|
||||
|
||||
// Display pointer information depending on flags.
|
||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||
f.fs.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
f.fs.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(f.fs, addr)
|
||||
}
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound == true:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound == true:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
f.ignoreNextType = true
|
||||
f.format(ve)
|
||||
}
|
||||
}
|
||||
|
||||
// format is the main workhorse for providing the Formatter interface. It
|
||||
// uses the passed reflect value to figure out what kind of object we are
|
||||
// dealing with and formats it appropriately. It is a recursive function,
|
||||
// however circular data structures are detected and handled properly.
|
||||
func (f *formatState) format(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
f.fs.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
f.formatPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write([]byte(v.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
f.ignoreNextType = false
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods
|
||||
// flag is enabled.
|
||||
if !f.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(f.fs, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(f.fs, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(f.fs, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(f.fs, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(f.fs, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(f.fs, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(f.fs, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f.fs.Write(openBracketBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.Index(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBracketBytes)
|
||||
|
||||
case reflect.String:
|
||||
f.fs.Write([]byte(v.String()))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
f.fs.Write(openMapBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
keys := v.MapKeys()
|
||||
if f.cs.SortKeys {
|
||||
sortValues(keys, f.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(key))
|
||||
f.fs.Write(colonBytes)
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.MapIndex(key)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeMapBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
numFields := v.NumField()
|
||||
f.fs.Write(openBraceBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
vtf := vt.Field(i)
|
||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||
f.fs.Write([]byte(vtf.Name))
|
||||
f.fs.Write(colonBytes)
|
||||
}
|
||||
f.format(f.unpackValue(v.Field(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(f.fs, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it if any get added.
|
||||
default:
|
||||
format := f.buildDefaultFormat()
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(f.fs, format, v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(f.fs, format, v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||
// details.
|
||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||
f.fs = fs
|
||||
|
||||
// Use standard formatting for verbs that are not v.
|
||||
if verb != 'v' {
|
||||
format := f.constructOrigFormat(verb)
|
||||
fmt.Fprintf(fs, format, f.value)
|
||||
return
|
||||
}
|
||||
|
||||
if f.value == nil {
|
||||
if fs.Flag('#') {
|
||||
fs.Write(interfaceBytes)
|
||||
}
|
||||
fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
f.format(reflect.ValueOf(f.value))
|
||||
}
|
||||
|
||||
// newFormatter is a helper function to consolidate the logic from the various
|
||||
// public methods which take varying config states.
|
||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||
fs := &formatState{value: v, cs: cs}
|
||||
fs.pointers = make(map[uintptr]int)
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
Printf, Println, or Fprintf.
|
||||
*/
|
||||
func NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(&Config, v)
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the formatted string as a value that satisfies error. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a default Formatter interface returned by NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a default spew Formatter interface.
|
||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = NewFormatter(arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2012,2013 Ernest Micklei
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,123 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OBSOLETE : use restful.DefaultContainer.EnableContentEncoding(true) to change this setting.
|
||||
var EnableContentEncoding = false
|
||||
|
||||
// CompressingResponseWriter is a http.ResponseWriter that can perform content encoding (gzip and zlib)
|
||||
type CompressingResponseWriter struct {
|
||||
writer http.ResponseWriter
|
||||
compressor io.WriteCloser
|
||||
encoding string
|
||||
}
|
||||
|
||||
// Header is part of http.ResponseWriter interface
|
||||
func (c *CompressingResponseWriter) Header() http.Header {
|
||||
return c.writer.Header()
|
||||
}
|
||||
|
||||
// WriteHeader is part of http.ResponseWriter interface
|
||||
func (c *CompressingResponseWriter) WriteHeader(status int) {
|
||||
c.writer.WriteHeader(status)
|
||||
}
|
||||
|
||||
// Write is part of http.ResponseWriter interface
|
||||
// It is passed through the compressor
|
||||
func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) {
|
||||
if c.isCompressorClosed() {
|
||||
return -1, errors.New("Compressing error: tried to write data using closed compressor")
|
||||
}
|
||||
return c.compressor.Write(bytes)
|
||||
}
|
||||
|
||||
// CloseNotify is part of http.CloseNotifier interface
|
||||
func (c *CompressingResponseWriter) CloseNotify() <-chan bool {
|
||||
return c.writer.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Close the underlying compressor
|
||||
func (c *CompressingResponseWriter) Close() error {
|
||||
if c.isCompressorClosed() {
|
||||
return errors.New("Compressing error: tried to close already closed compressor")
|
||||
}
|
||||
|
||||
c.compressor.Close()
|
||||
if ENCODING_GZIP == c.encoding {
|
||||
currentCompressorProvider.ReleaseGzipWriter(c.compressor.(*gzip.Writer))
|
||||
}
|
||||
if ENCODING_DEFLATE == c.encoding {
|
||||
currentCompressorProvider.ReleaseZlibWriter(c.compressor.(*zlib.Writer))
|
||||
}
|
||||
// gc hint needed?
|
||||
c.compressor = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CompressingResponseWriter) isCompressorClosed() bool {
|
||||
return nil == c.compressor
|
||||
}
|
||||
|
||||
// Hijack implements the Hijacker interface
|
||||
// This is especially useful when combining Container.EnabledContentEncoding
|
||||
// in combination with websockets (for instance gorilla/websocket)
|
||||
func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker, ok := c.writer.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface")
|
||||
}
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
|
||||
func wantsCompressedResponse(httpRequest *http.Request) (bool, string) {
|
||||
header := httpRequest.Header.Get(HEADER_AcceptEncoding)
|
||||
gi := strings.Index(header, ENCODING_GZIP)
|
||||
zi := strings.Index(header, ENCODING_DEFLATE)
|
||||
// use in order of appearance
|
||||
if gi == -1 {
|
||||
return zi != -1, ENCODING_DEFLATE
|
||||
} else if zi == -1 {
|
||||
return gi != -1, ENCODING_GZIP
|
||||
} else {
|
||||
if gi < zi {
|
||||
return true, ENCODING_GZIP
|
||||
}
|
||||
return true, ENCODING_DEFLATE
|
||||
}
|
||||
}
|
||||
|
||||
// NewCompressingResponseWriter create a CompressingResponseWriter for a known encoding = {gzip,deflate}
|
||||
func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding string) (*CompressingResponseWriter, error) {
|
||||
httpWriter.Header().Set(HEADER_ContentEncoding, encoding)
|
||||
c := new(CompressingResponseWriter)
|
||||
c.writer = httpWriter
|
||||
var err error
|
||||
if ENCODING_GZIP == encoding {
|
||||
w := currentCompressorProvider.AcquireGzipWriter()
|
||||
w.Reset(httpWriter)
|
||||
c.compressor = w
|
||||
c.encoding = ENCODING_GZIP
|
||||
} else if ENCODING_DEFLATE == encoding {
|
||||
w := currentCompressorProvider.AcquireZlibWriter()
|
||||
w.Reset(httpWriter)
|
||||
c.compressor = w
|
||||
c.encoding = ENCODING_DEFLATE
|
||||
} else {
|
||||
return nil, errors.New("Unknown encoding:" + encoding)
|
||||
}
|
||||
return c, err
|
||||
}
|
103
vendor/github.com/emicklei/go-restful/compressor_cache.go
сгенерированный
поставляемый
Normal file
103
vendor/github.com/emicklei/go-restful/compressor_cache.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,103 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
)
|
||||
|
||||
// BoundedCachedCompressors is a CompressorProvider that uses a cache with a fixed amount
|
||||
// of writers and readers (resources).
|
||||
// If a new resource is acquired and all are in use, it will return a new unmanaged resource.
|
||||
type BoundedCachedCompressors struct {
|
||||
gzipWriters chan *gzip.Writer
|
||||
gzipReaders chan *gzip.Reader
|
||||
zlibWriters chan *zlib.Writer
|
||||
writersCapacity int
|
||||
readersCapacity int
|
||||
}
|
||||
|
||||
// NewBoundedCachedCompressors returns a new, with filled cache, BoundedCachedCompressors.
|
||||
func NewBoundedCachedCompressors(writersCapacity, readersCapacity int) *BoundedCachedCompressors {
|
||||
b := &BoundedCachedCompressors{
|
||||
gzipWriters: make(chan *gzip.Writer, writersCapacity),
|
||||
gzipReaders: make(chan *gzip.Reader, readersCapacity),
|
||||
zlibWriters: make(chan *zlib.Writer, writersCapacity),
|
||||
writersCapacity: writersCapacity,
|
||||
readersCapacity: readersCapacity,
|
||||
}
|
||||
for ix := 0; ix < writersCapacity; ix++ {
|
||||
b.gzipWriters <- newGzipWriter()
|
||||
b.zlibWriters <- newZlibWriter()
|
||||
}
|
||||
for ix := 0; ix < readersCapacity; ix++ {
|
||||
b.gzipReaders <- newGzipReader()
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AcquireGzipWriter returns an resettable *gzip.Writer. Needs to be released.
|
||||
func (b *BoundedCachedCompressors) AcquireGzipWriter() *gzip.Writer {
|
||||
var writer *gzip.Writer
|
||||
select {
|
||||
case writer, _ = <-b.gzipWriters:
|
||||
default:
|
||||
// return a new unmanaged one
|
||||
writer = newGzipWriter()
|
||||
}
|
||||
return writer
|
||||
}
|
||||
|
||||
// ReleaseGzipWriter accepts a writer (does not have to be one that was cached)
|
||||
// only when the cache has room for it. It will ignore it otherwise.
|
||||
func (b *BoundedCachedCompressors) ReleaseGzipWriter(w *gzip.Writer) {
|
||||
// forget the unmanaged ones
|
||||
if len(b.gzipWriters) < b.writersCapacity {
|
||||
b.gzipWriters <- w
|
||||
}
|
||||
}
|
||||
|
||||
// AcquireGzipReader returns a *gzip.Reader. Needs to be released.
|
||||
func (b *BoundedCachedCompressors) AcquireGzipReader() *gzip.Reader {
|
||||
var reader *gzip.Reader
|
||||
select {
|
||||
case reader, _ = <-b.gzipReaders:
|
||||
default:
|
||||
// return a new unmanaged one
|
||||
reader = newGzipReader()
|
||||
}
|
||||
return reader
|
||||
}
|
||||
|
||||
// ReleaseGzipReader accepts a reader (does not have to be one that was cached)
|
||||
// only when the cache has room for it. It will ignore it otherwise.
|
||||
func (b *BoundedCachedCompressors) ReleaseGzipReader(r *gzip.Reader) {
|
||||
// forget the unmanaged ones
|
||||
if len(b.gzipReaders) < b.readersCapacity {
|
||||
b.gzipReaders <- r
|
||||
}
|
||||
}
|
||||
|
||||
// AcquireZlibWriter returns an resettable *zlib.Writer. Needs to be released.
|
||||
func (b *BoundedCachedCompressors) AcquireZlibWriter() *zlib.Writer {
|
||||
var writer *zlib.Writer
|
||||
select {
|
||||
case writer, _ = <-b.zlibWriters:
|
||||
default:
|
||||
// return a new unmanaged one
|
||||
writer = newZlibWriter()
|
||||
}
|
||||
return writer
|
||||
}
|
||||
|
||||
// ReleaseZlibWriter accepts a writer (does not have to be one that was cached)
|
||||
// only when the cache has room for it. It will ignore it otherwise.
|
||||
func (b *BoundedCachedCompressors) ReleaseZlibWriter(w *zlib.Writer) {
|
||||
// forget the unmanaged ones
|
||||
if len(b.zlibWriters) < b.writersCapacity {
|
||||
b.zlibWriters <- w
|
||||
}
|
||||
}
|
91
vendor/github.com/emicklei/go-restful/compressor_pools.go
сгенерированный
поставляемый
Normal file
91
vendor/github.com/emicklei/go-restful/compressor_pools.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,91 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SyncPoolCompessors is a CompressorProvider that use the standard sync.Pool.
|
||||
type SyncPoolCompessors struct {
|
||||
GzipWriterPool *sync.Pool
|
||||
GzipReaderPool *sync.Pool
|
||||
ZlibWriterPool *sync.Pool
|
||||
}
|
||||
|
||||
// NewSyncPoolCompessors returns a new ("empty") SyncPoolCompessors.
|
||||
func NewSyncPoolCompessors() *SyncPoolCompessors {
|
||||
return &SyncPoolCompessors{
|
||||
GzipWriterPool: &sync.Pool{
|
||||
New: func() interface{} { return newGzipWriter() },
|
||||
},
|
||||
GzipReaderPool: &sync.Pool{
|
||||
New: func() interface{} { return newGzipReader() },
|
||||
},
|
||||
ZlibWriterPool: &sync.Pool{
|
||||
New: func() interface{} { return newZlibWriter() },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) AcquireGzipWriter() *gzip.Writer {
|
||||
return s.GzipWriterPool.Get().(*gzip.Writer)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) ReleaseGzipWriter(w *gzip.Writer) {
|
||||
s.GzipWriterPool.Put(w)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) AcquireGzipReader() *gzip.Reader {
|
||||
return s.GzipReaderPool.Get().(*gzip.Reader)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) ReleaseGzipReader(r *gzip.Reader) {
|
||||
s.GzipReaderPool.Put(r)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) AcquireZlibWriter() *zlib.Writer {
|
||||
return s.ZlibWriterPool.Get().(*zlib.Writer)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) ReleaseZlibWriter(w *zlib.Writer) {
|
||||
s.ZlibWriterPool.Put(w)
|
||||
}
|
||||
|
||||
func newGzipWriter() *gzip.Writer {
|
||||
// create with an empty bytes writer; it will be replaced before using the gzipWriter
|
||||
writer, err := gzip.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return writer
|
||||
}
|
||||
|
||||
func newGzipReader() *gzip.Reader {
|
||||
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
|
||||
// we can safely use currentCompressProvider because it is set on package initialization.
|
||||
w := currentCompressorProvider.AcquireGzipWriter()
|
||||
defer currentCompressorProvider.ReleaseGzipWriter(w)
|
||||
b := new(bytes.Buffer)
|
||||
w.Reset(b)
|
||||
w.Flush()
|
||||
w.Close()
|
||||
reader, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return reader
|
||||
}
|
||||
|
||||
func newZlibWriter() *zlib.Writer {
|
||||
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return writer
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
)
|
||||
|
||||
// CompressorProvider describes a component that can provider compressors for the std methods.
|
||||
type CompressorProvider interface {
|
||||
// Returns a *gzip.Writer which needs to be released later.
|
||||
// Before using it, call Reset().
|
||||
AcquireGzipWriter() *gzip.Writer
|
||||
|
||||
// Releases an acquired *gzip.Writer.
|
||||
ReleaseGzipWriter(w *gzip.Writer)
|
||||
|
||||
// Returns a *gzip.Reader which needs to be released later.
|
||||
AcquireGzipReader() *gzip.Reader
|
||||
|
||||
// Releases an acquired *gzip.Reader.
|
||||
ReleaseGzipReader(w *gzip.Reader)
|
||||
|
||||
// Returns a *zlib.Writer which needs to be released later.
|
||||
// Before using it, call Reset().
|
||||
AcquireZlibWriter() *zlib.Writer
|
||||
|
||||
// Releases an acquired *zlib.Writer.
|
||||
ReleaseZlibWriter(w *zlib.Writer)
|
||||
}
|
||||
|
||||
// DefaultCompressorProvider is the actual provider of compressors (zlib or gzip).
|
||||
var currentCompressorProvider CompressorProvider
|
||||
|
||||
func init() {
|
||||
currentCompressorProvider = NewSyncPoolCompessors()
|
||||
}
|
||||
|
||||
// CurrentCompressorProvider returns the current CompressorProvider.
|
||||
// It is initialized using a SyncPoolCompessors.
|
||||
func CurrentCompressorProvider() CompressorProvider {
|
||||
return currentCompressorProvider
|
||||
}
|
||||
|
||||
// SetCompressorProvider sets the actual provider of compressors (zlib or gzip).
|
||||
func SetCompressorProvider(p CompressorProvider) {
|
||||
if p == nil {
|
||||
panic("cannot set compressor provider to nil")
|
||||
}
|
||||
currentCompressorProvider = p
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
const (
|
||||
MIME_XML = "application/xml" // Accept or Content-Type used in Consumes() and/or Produces()
|
||||
MIME_JSON = "application/json" // Accept or Content-Type used in Consumes() and/or Produces()
|
||||
MIME_OCTET = "application/octet-stream" // If Content-Type is not present in request, use the default
|
||||
|
||||
HEADER_Allow = "Allow"
|
||||
HEADER_Accept = "Accept"
|
||||
HEADER_Origin = "Origin"
|
||||
HEADER_ContentType = "Content-Type"
|
||||
HEADER_LastModified = "Last-Modified"
|
||||
HEADER_AcceptEncoding = "Accept-Encoding"
|
||||
HEADER_ContentEncoding = "Content-Encoding"
|
||||
HEADER_AccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
||||
HEADER_AccessControlRequestMethod = "Access-Control-Request-Method"
|
||||
HEADER_AccessControlRequestHeaders = "Access-Control-Request-Headers"
|
||||
HEADER_AccessControlAllowMethods = "Access-Control-Allow-Methods"
|
||||
HEADER_AccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
||||
HEADER_AccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
||||
HEADER_AccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
||||
HEADER_AccessControlMaxAge = "Access-Control-Max-Age"
|
||||
|
||||
ENCODING_GZIP = "gzip"
|
||||
ENCODING_DEFLATE = "deflate"
|
||||
)
|
|
@ -0,0 +1,371 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/emicklei/go-restful/log"
|
||||
)
|
||||
|
||||
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
|
||||
// The requests are further dispatched to routes of WebServices using a RouteSelector
|
||||
type Container struct {
|
||||
webServicesLock sync.RWMutex
|
||||
webServices []*WebService
|
||||
ServeMux *http.ServeMux
|
||||
isRegisteredOnRoot bool
|
||||
containerFilters []FilterFunction
|
||||
doNotRecover bool // default is true
|
||||
recoverHandleFunc RecoverHandleFunction
|
||||
serviceErrorHandleFunc ServiceErrorHandleFunction
|
||||
router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
|
||||
contentEncodingEnabled bool // default is false
|
||||
}
|
||||
|
||||
// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
|
||||
func NewContainer() *Container {
|
||||
return &Container{
|
||||
webServices: []*WebService{},
|
||||
ServeMux: http.NewServeMux(),
|
||||
isRegisteredOnRoot: false,
|
||||
containerFilters: []FilterFunction{},
|
||||
doNotRecover: true,
|
||||
recoverHandleFunc: logStackOnRecover,
|
||||
serviceErrorHandleFunc: writeServiceError,
|
||||
router: CurlyRouter{},
|
||||
contentEncodingEnabled: false}
|
||||
}
|
||||
|
||||
// RecoverHandleFunction declares functions that can be used to handle a panic situation.
|
||||
// The first argument is what recover() returns. The second must be used to communicate an error response.
|
||||
type RecoverHandleFunction func(interface{}, http.ResponseWriter)
|
||||
|
||||
// RecoverHandler changes the default function (logStackOnRecover) to be called
|
||||
// when a panic is detected. DoNotRecover must be have its default value (=false).
|
||||
func (c *Container) RecoverHandler(handler RecoverHandleFunction) {
|
||||
c.recoverHandleFunc = handler
|
||||
}
|
||||
|
||||
// ServiceErrorHandleFunction declares functions that can be used to handle a service error situation.
|
||||
// The first argument is the service error, the second is the request that resulted in the error and
|
||||
// the third must be used to communicate an error response.
|
||||
type ServiceErrorHandleFunction func(ServiceError, *Request, *Response)
|
||||
|
||||
// ServiceErrorHandler changes the default function (writeServiceError) to be called
|
||||
// when a ServiceError is detected.
|
||||
func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) {
|
||||
c.serviceErrorHandleFunc = handler
|
||||
}
|
||||
|
||||
// DoNotRecover controls whether panics will be caught to return HTTP 500.
|
||||
// If set to true, Route functions are responsible for handling any error situation.
|
||||
// Default value is true.
|
||||
func (c *Container) DoNotRecover(doNot bool) {
|
||||
c.doNotRecover = doNot
|
||||
}
|
||||
|
||||
// Router changes the default Router (currently CurlyRouter)
|
||||
func (c *Container) Router(aRouter RouteSelector) {
|
||||
c.router = aRouter
|
||||
}
|
||||
|
||||
// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
|
||||
func (c *Container) EnableContentEncoding(enabled bool) {
|
||||
c.contentEncodingEnabled = enabled
|
||||
}
|
||||
|
||||
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
|
||||
func (c *Container) Add(service *WebService) *Container {
|
||||
c.webServicesLock.Lock()
|
||||
defer c.webServicesLock.Unlock()
|
||||
|
||||
// if rootPath was not set then lazy initialize it
|
||||
if len(service.rootPath) == 0 {
|
||||
service.Path("/")
|
||||
}
|
||||
|
||||
// cannot have duplicate root paths
|
||||
for _, each := range c.webServices {
|
||||
if each.RootPath() == service.RootPath() {
|
||||
log.Printf("[restful] WebService with duplicate root path detected:['%v']", each)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// If not registered on root then add specific mapping
|
||||
if !c.isRegisteredOnRoot {
|
||||
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
|
||||
}
|
||||
c.webServices = append(c.webServices, service)
|
||||
return c
|
||||
}
|
||||
|
||||
// addHandler may set a new HandleFunc for the serveMux
|
||||
// this function must run inside the critical region protected by the webServicesLock.
|
||||
// returns true if the function was registered on root ("/")
|
||||
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
|
||||
pattern := fixedPrefixPath(service.RootPath())
|
||||
// check if root path registration is needed
|
||||
if "/" == pattern || "" == pattern {
|
||||
serveMux.HandleFunc("/", c.dispatch)
|
||||
return true
|
||||
}
|
||||
// detect if registration already exists
|
||||
alreadyMapped := false
|
||||
for _, each := range c.webServices {
|
||||
if each.RootPath() == service.RootPath() {
|
||||
alreadyMapped = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyMapped {
|
||||
serveMux.HandleFunc(pattern, c.dispatch)
|
||||
if !strings.HasSuffix(pattern, "/") {
|
||||
serveMux.HandleFunc(pattern+"/", c.dispatch)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Container) Remove(ws *WebService) error {
|
||||
if c.ServeMux == http.DefaultServeMux {
|
||||
errMsg := fmt.Sprintf("[restful] cannot remove a WebService from a Container using the DefaultServeMux: ['%v']", ws)
|
||||
log.Print(errMsg)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
c.webServicesLock.Lock()
|
||||
defer c.webServicesLock.Unlock()
|
||||
// build a new ServeMux and re-register all WebServices
|
||||
newServeMux := http.NewServeMux()
|
||||
newServices := []*WebService{}
|
||||
newIsRegisteredOnRoot := false
|
||||
for _, each := range c.webServices {
|
||||
if each.rootPath != ws.rootPath {
|
||||
// If not registered on root then add specific mapping
|
||||
if !newIsRegisteredOnRoot {
|
||||
newIsRegisteredOnRoot = c.addHandler(each, newServeMux)
|
||||
}
|
||||
newServices = append(newServices, each)
|
||||
}
|
||||
}
|
||||
c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot
|
||||
return nil
|
||||
}
|
||||
|
||||
// logStackOnRecover is the default RecoverHandleFunction and is called
|
||||
// when DoNotRecover is false and the recoverHandleFunc is not set for the container.
|
||||
// Default implementation logs the stacktrace and writes the stacktrace on the response.
|
||||
// This may be a security issue as it exposes sourcecode information.
|
||||
func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(fmt.Sprintf("[restful] recover from panic situation: - %v\r\n", panicReason))
|
||||
for i := 2; ; i += 1 {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
|
||||
}
|
||||
log.Print(buffer.String())
|
||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
||||
httpWriter.Write(buffer.Bytes())
|
||||
}
|
||||
|
||||
// writeServiceError is the default ServiceErrorHandleFunction and is called
|
||||
// when a ServiceError is returned during route selection. Default implementation
|
||||
// calls resp.WriteErrorString(err.Code, err.Message)
|
||||
func writeServiceError(err ServiceError, req *Request, resp *Response) {
|
||||
resp.WriteErrorString(err.Code, err.Message)
|
||||
}
|
||||
|
||||
// Dispatch the incoming Http Request to a matching WebService.
|
||||
func (c *Container) Dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
||||
if httpWriter == nil {
|
||||
panic("httpWriter cannot be nil")
|
||||
}
|
||||
if httpRequest == nil {
|
||||
panic("httpRequest cannot be nil")
|
||||
}
|
||||
c.dispatch(httpWriter, httpRequest)
|
||||
}
|
||||
|
||||
// Dispatch the incoming Http Request to a matching WebService.
|
||||
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
||||
writer := httpWriter
|
||||
|
||||
// CompressingResponseWriter should be closed after all operations are done
|
||||
defer func() {
|
||||
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
|
||||
compressWriter.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Instal panic recovery unless told otherwise
|
||||
if !c.doNotRecover { // catch all for 500 response
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.recoverHandleFunc(r, writer)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Detect if compression is needed
|
||||
// assume without compression, test for override
|
||||
if c.contentEncodingEnabled {
|
||||
doCompress, encoding := wantsCompressedResponse(httpRequest)
|
||||
if doCompress {
|
||||
var err error
|
||||
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
|
||||
if err != nil {
|
||||
log.Print("[restful] unable to install compressor: ", err)
|
||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find best match Route ; err is non nil if no match was found
|
||||
var webService *WebService
|
||||
var route *Route
|
||||
var err error
|
||||
func() {
|
||||
c.webServicesLock.RLock()
|
||||
defer c.webServicesLock.RUnlock()
|
||||
webService, route, err = c.router.SelectRoute(
|
||||
c.webServices,
|
||||
httpRequest)
|
||||
}()
|
||||
if err != nil {
|
||||
// a non-200 response has already been written
|
||||
// run container filters anyway ; they should not touch the response...
|
||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
||||
switch err.(type) {
|
||||
case ServiceError:
|
||||
ser := err.(ServiceError)
|
||||
c.serviceErrorHandleFunc(ser, req, resp)
|
||||
}
|
||||
// TODO
|
||||
}}
|
||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
|
||||
return
|
||||
}
|
||||
pathProcessor, routerProcessesPath := c.router.(PathProcessor)
|
||||
if !routerProcessesPath {
|
||||
pathProcessor = defaultPathProcessor{}
|
||||
}
|
||||
pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
|
||||
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
|
||||
// pass through filters (if any)
|
||||
if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 {
|
||||
// compose filter chain
|
||||
allFilters := []FilterFunction{}
|
||||
allFilters = append(allFilters, c.containerFilters...)
|
||||
allFilters = append(allFilters, webService.filters...)
|
||||
allFilters = append(allFilters, route.Filters...)
|
||||
chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) {
|
||||
// handle request by route after passing all filters
|
||||
route.Function(wrappedRequest, wrappedResponse)
|
||||
}}
|
||||
chain.ProcessFilter(wrappedRequest, wrappedResponse)
|
||||
} else {
|
||||
// no filters, handle request by route
|
||||
route.Function(wrappedRequest, wrappedResponse)
|
||||
}
|
||||
}
|
||||
|
||||
// fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
|
||||
func fixedPrefixPath(pathspec string) string {
|
||||
varBegin := strings.Index(pathspec, "{")
|
||||
if -1 == varBegin {
|
||||
return pathspec
|
||||
}
|
||||
return pathspec[:varBegin]
|
||||
}
|
||||
|
||||
// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
|
||||
func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
|
||||
c.ServeMux.ServeHTTP(httpwriter, httpRequest)
|
||||
}
|
||||
|
||||
// Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
|
||||
func (c *Container) Handle(pattern string, handler http.Handler) {
|
||||
c.ServeMux.Handle(pattern, handler)
|
||||
}
|
||||
|
||||
// HandleWithFilter registers the handler for the given pattern.
|
||||
// Container's filter chain is applied for handler.
|
||||
// If a handler already exists for pattern, HandleWithFilter panics.
|
||||
func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
|
||||
f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
|
||||
if len(c.containerFilters) == 0 {
|
||||
handler.ServeHTTP(httpResponse, httpRequest)
|
||||
return
|
||||
}
|
||||
|
||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
||||
handler.ServeHTTP(httpResponse, httpRequest)
|
||||
}}
|
||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
|
||||
}
|
||||
|
||||
c.Handle(pattern, http.HandlerFunc(f))
|
||||
}
|
||||
|
||||
// Filter appends a container FilterFunction. These are called before dispatching
|
||||
// a http.Request to a WebService from the container
|
||||
func (c *Container) Filter(filter FilterFunction) {
|
||||
c.containerFilters = append(c.containerFilters, filter)
|
||||
}
|
||||
|
||||
// RegisteredWebServices returns the collections of added WebServices
|
||||
func (c *Container) RegisteredWebServices() []*WebService {
|
||||
c.webServicesLock.RLock()
|
||||
defer c.webServicesLock.RUnlock()
|
||||
result := make([]*WebService, len(c.webServices))
|
||||
for ix := range c.webServices {
|
||||
result[ix] = c.webServices[ix]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// computeAllowedMethods returns a list of HTTP methods that are valid for a Request
|
||||
func (c *Container) computeAllowedMethods(req *Request) []string {
|
||||
// Go through all RegisteredWebServices() and all its Routes to collect the options
|
||||
methods := []string{}
|
||||
requestPath := req.Request.URL.Path
|
||||
for _, ws := range c.RegisteredWebServices() {
|
||||
matches := ws.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
||||
if matches != nil {
|
||||
finalMatch := matches[len(matches)-1]
|
||||
for _, rt := range ws.Routes() {
|
||||
matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
|
||||
if matches != nil {
|
||||
lastMatch := matches[len(matches)-1]
|
||||
if lastMatch == "" || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
||||
methods = append(methods, rt.Method)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// methods = append(methods, "OPTIONS") not sure about this
|
||||
return methods
|
||||
}
|
||||
|
||||
// newBasicRequestResponse creates a pair of Request,Response from its http versions.
|
||||
// It is basic because no parameter or (produces) content-type information is given.
|
||||
func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
|
||||
resp := NewResponse(httpWriter)
|
||||
resp.requestAccept = httpRequest.Header.Get(HEADER_Accept)
|
||||
return NewRequest(httpRequest), resp
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CrossOriginResourceSharing is used to create a Container Filter that implements CORS.
|
||||
// Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
|
||||
// to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
|
||||
//
|
||||
// http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
||||
// http://enable-cors.org/server.html
|
||||
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
|
||||
type CrossOriginResourceSharing struct {
|
||||
ExposeHeaders []string // list of Header names
|
||||
AllowedHeaders []string // list of Header names
|
||||
AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed.
|
||||
AllowedMethods []string
|
||||
MaxAge int // number of seconds before requiring new Options request
|
||||
CookiesAllowed bool
|
||||
Container *Container
|
||||
|
||||
allowedOriginPatterns []*regexp.Regexp // internal field for origin regexp check.
|
||||
}
|
||||
|
||||
// Filter is a filter function that implements the CORS flow as documented on http://enable-cors.org/server.html
|
||||
// and http://www.html5rocks.com/static/images/cors_server_flowchart.png
|
||||
func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain *FilterChain) {
|
||||
origin := req.Request.Header.Get(HEADER_Origin)
|
||||
if len(origin) == 0 {
|
||||
if trace {
|
||||
traceLogger.Print("no Http header Origin set")
|
||||
}
|
||||
chain.ProcessFilter(req, resp)
|
||||
return
|
||||
}
|
||||
if !c.isOriginAllowed(origin) { // check whether this origin is allowed
|
||||
if trace {
|
||||
traceLogger.Printf("HTTP Origin:%s is not part of %v, neither matches any part of %v", origin, c.AllowedDomains, c.allowedOriginPatterns)
|
||||
}
|
||||
chain.ProcessFilter(req, resp)
|
||||
return
|
||||
}
|
||||
if req.Request.Method != "OPTIONS" {
|
||||
c.doActualRequest(req, resp)
|
||||
chain.ProcessFilter(req, resp)
|
||||
return
|
||||
}
|
||||
if acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod); acrm != "" {
|
||||
c.doPreflightRequest(req, resp)
|
||||
} else {
|
||||
c.doActualRequest(req, resp)
|
||||
chain.ProcessFilter(req, resp)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response) {
|
||||
c.setOptionsHeaders(req, resp)
|
||||
// continue processing the response
|
||||
}
|
||||
|
||||
func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) {
|
||||
if len(c.AllowedMethods) == 0 {
|
||||
if c.Container == nil {
|
||||
c.AllowedMethods = DefaultContainer.computeAllowedMethods(req)
|
||||
} else {
|
||||
c.AllowedMethods = c.Container.computeAllowedMethods(req)
|
||||
}
|
||||
}
|
||||
|
||||
acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod)
|
||||
if !c.isValidAccessControlRequestMethod(acrm, c.AllowedMethods) {
|
||||
if trace {
|
||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
||||
HEADER_AccessControlRequestMethod,
|
||||
acrm,
|
||||
c.AllowedMethods)
|
||||
}
|
||||
return
|
||||
}
|
||||
acrhs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
|
||||
if len(acrhs) > 0 {
|
||||
for _, each := range strings.Split(acrhs, ",") {
|
||||
if !c.isValidAccessControlRequestHeader(strings.Trim(each, " ")) {
|
||||
if trace {
|
||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
||||
HEADER_AccessControlRequestHeaders,
|
||||
acrhs,
|
||||
c.AllowedHeaders)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.AddHeader(HEADER_AccessControlAllowMethods, strings.Join(c.AllowedMethods, ","))
|
||||
resp.AddHeader(HEADER_AccessControlAllowHeaders, acrhs)
|
||||
c.setOptionsHeaders(req, resp)
|
||||
|
||||
// return http 200 response, no body
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) setOptionsHeaders(req *Request, resp *Response) {
|
||||
c.checkAndSetExposeHeaders(resp)
|
||||
c.setAllowOriginHeader(req, resp)
|
||||
c.checkAndSetAllowCredentials(resp)
|
||||
if c.MaxAge > 0 {
|
||||
resp.AddHeader(HEADER_AccessControlMaxAge, strconv.Itoa(c.MaxAge))
|
||||
}
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) isOriginAllowed(origin string) bool {
|
||||
if len(origin) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(c.AllowedDomains) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
allowed := false
|
||||
for _, domain := range c.AllowedDomains {
|
||||
if domain == origin {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
if len(c.allowedOriginPatterns) == 0 {
|
||||
// compile allowed domains to allowed origin patterns
|
||||
allowedOriginRegexps, err := compileRegexps(c.AllowedDomains)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
c.allowedOriginPatterns = allowedOriginRegexps
|
||||
}
|
||||
|
||||
for _, pattern := range c.allowedOriginPatterns {
|
||||
if allowed = pattern.MatchString(origin); allowed {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allowed
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) setAllowOriginHeader(req *Request, resp *Response) {
|
||||
origin := req.Request.Header.Get(HEADER_Origin)
|
||||
if c.isOriginAllowed(origin) {
|
||||
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
|
||||
}
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) checkAndSetExposeHeaders(resp *Response) {
|
||||
if len(c.ExposeHeaders) > 0 {
|
||||
resp.AddHeader(HEADER_AccessControlExposeHeaders, strings.Join(c.ExposeHeaders, ","))
|
||||
}
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) checkAndSetAllowCredentials(resp *Response) {
|
||||
if c.CookiesAllowed {
|
||||
resp.AddHeader(HEADER_AccessControlAllowCredentials, "true")
|
||||
}
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestMethod(method string, allowedMethods []string) bool {
|
||||
for _, each := range allowedMethods {
|
||||
if each == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header string) bool {
|
||||
for _, each := range c.AllowedHeaders {
|
||||
if strings.ToLower(each) == strings.ToLower(header) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Take a list of strings and compile them into a list of regular expressions.
|
||||
func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
|
||||
regexps := []*regexp.Regexp{}
|
||||
for _, regexpStr := range regexpStrings {
|
||||
r, err := regexp.Compile(regexpStr)
|
||||
if err != nil {
|
||||
return regexps, err
|
||||
}
|
||||
regexps = append(regexps, r)
|
||||
}
|
||||
return regexps, nil
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
|
||||
type CurlyRouter struct{}
|
||||
|
||||
// SelectRoute is part of the Router interface and returns the best match
|
||||
// for the WebService and its Route for the given Request.
|
||||
func (c CurlyRouter) SelectRoute(
|
||||
webServices []*WebService,
|
||||
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
|
||||
|
||||
requestTokens := tokenizePath(httpRequest.URL.Path)
|
||||
|
||||
detectedService := c.detectWebService(requestTokens, webServices)
|
||||
if detectedService == nil {
|
||||
if trace {
|
||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
|
||||
}
|
||||
return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
||||
}
|
||||
candidateRoutes := c.selectRoutes(detectedService, requestTokens)
|
||||
if len(candidateRoutes) == 0 {
|
||||
if trace {
|
||||
traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
|
||||
}
|
||||
return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
||||
}
|
||||
selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
|
||||
if selectedRoute == nil {
|
||||
return detectedService, nil, err
|
||||
}
|
||||
return detectedService, selectedRoute, nil
|
||||
}
|
||||
|
||||
// selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
|
||||
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
|
||||
candidates := sortableCurlyRoutes{}
|
||||
for _, each := range ws.routes {
|
||||
matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens)
|
||||
if matches {
|
||||
candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
|
||||
}
|
||||
}
|
||||
sort.Sort(sort.Reverse(candidates))
|
||||
return candidates
|
||||
}
|
||||
|
||||
// matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
|
||||
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) {
|
||||
if len(routeTokens) < len(requestTokens) {
|
||||
// proceed in matching only if last routeToken is wildcard
|
||||
count := len(routeTokens)
|
||||
if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
|
||||
return false, 0, 0
|
||||
}
|
||||
// proceed
|
||||
}
|
||||
for i, routeToken := range routeTokens {
|
||||
if i == len(requestTokens) {
|
||||
// reached end of request path
|
||||
return false, 0, 0
|
||||
}
|
||||
requestToken := requestTokens[i]
|
||||
if strings.HasPrefix(routeToken, "{") {
|
||||
paramCount++
|
||||
if colon := strings.Index(routeToken, ":"); colon != -1 {
|
||||
// match by regex
|
||||
matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
|
||||
if !matchesToken {
|
||||
return false, 0, 0
|
||||
}
|
||||
if matchesRemainder {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else { // no { prefix
|
||||
if requestToken != routeToken {
|
||||
return false, 0, 0
|
||||
}
|
||||
staticCount++
|
||||
}
|
||||
}
|
||||
return true, paramCount, staticCount
|
||||
}
|
||||
|
||||
// regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
|
||||
// format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
|
||||
func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
|
||||
regPart := routeToken[colon+1 : len(routeToken)-1]
|
||||
if regPart == "*" {
|
||||
if trace {
|
||||
traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
|
||||
}
|
||||
return true, true
|
||||
}
|
||||
matched, err := regexp.MatchString(regPart, requestToken)
|
||||
return (matched && err == nil), false
|
||||
}
|
||||
|
||||
var jsr311Router = RouterJSR311{}
|
||||
|
||||
// detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
|
||||
// headers of the Request. See also RouterJSR311 in jsr311.go
|
||||
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
|
||||
// tracing is done inside detectRoute
|
||||
return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
|
||||
}
|
||||
|
||||
// detectWebService returns the best matching webService given the list of path tokens.
|
||||
// see also computeWebserviceScore
|
||||
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
|
||||
var best *WebService
|
||||
score := -1
|
||||
for _, each := range webServices {
|
||||
matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
|
||||
if matches && (eachScore > score) {
|
||||
best = each
|
||||
score = eachScore
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// computeWebserviceScore returns whether tokens match and
|
||||
// the weighted score of the longest matching consecutive tokens from the beginning.
|
||||
func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
|
||||
if len(tokens) > len(requestTokens) {
|
||||
return false, 0
|
||||
}
|
||||
score := 0
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
each := requestTokens[i]
|
||||
other := tokens[i]
|
||||
if len(each) == 0 && len(other) == 0 {
|
||||
score++
|
||||
continue
|
||||
}
|
||||
if len(other) > 0 && strings.HasPrefix(other, "{") {
|
||||
// no empty match
|
||||
if len(each) == 0 {
|
||||
return false, score
|
||||
}
|
||||
score += 1
|
||||
} else {
|
||||
// not a parameter
|
||||
if each != other {
|
||||
return false, score
|
||||
}
|
||||
score += (len(tokens) - i) * 10 //fuzzy
|
||||
}
|
||||
}
|
||||
return true, score
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// curlyRoute exits for sorting Routes by the CurlyRouter based on number of parameters and number of static path elements.
|
||||
type curlyRoute struct {
|
||||
route Route
|
||||
paramCount int
|
||||
staticCount int
|
||||
}
|
||||
|
||||
type sortableCurlyRoutes []curlyRoute
|
||||
|
||||
func (s *sortableCurlyRoutes) add(route curlyRoute) {
|
||||
*s = append(*s, route)
|
||||
}
|
||||
|
||||
func (s sortableCurlyRoutes) routes() (routes []Route) {
|
||||
for _, each := range s {
|
||||
routes = append(routes, each.route) // TODO change return type
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
func (s sortableCurlyRoutes) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s sortableCurlyRoutes) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s sortableCurlyRoutes) Less(i, j int) bool {
|
||||
ci := s[i]
|
||||
cj := s[j]
|
||||
|
||||
// primary key
|
||||
if ci.staticCount < cj.staticCount {
|
||||
return true
|
||||
}
|
||||
if ci.staticCount > cj.staticCount {
|
||||
return false
|
||||
}
|
||||
// secundary key
|
||||
if ci.paramCount < cj.paramCount {
|
||||
return true
|
||||
}
|
||||
if ci.paramCount > cj.paramCount {
|
||||
return false
|
||||
}
|
||||
return ci.route.Path < cj.route.Path
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
Package restful , a lean package for creating REST-style WebServices without magic.
|
||||
|
||||
WebServices and Routes
|
||||
|
||||
A WebService has a collection of Route objects that dispatch incoming Http Requests to a function calls.
|
||||
Typically, a WebService has a root path (e.g. /users) and defines common MIME types for its routes.
|
||||
WebServices must be added to a container (see below) in order to handler Http requests from a server.
|
||||
|
||||
A Route is defined by a HTTP method, an URL path and (optionally) the MIME types it consumes (Content-Type) and produces (Accept).
|
||||
This package has the logic to find the best matching Route and if found, call its Function.
|
||||
|
||||
ws := new(restful.WebService)
|
||||
ws.
|
||||
Path("/users").
|
||||
Consumes(restful.MIME_JSON, restful.MIME_XML).
|
||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
||||
|
||||
ws.Route(ws.GET("/{user-id}").To(u.findUser)) // u is a UserResource
|
||||
|
||||
...
|
||||
|
||||
// GET http://localhost:8080/users/1
|
||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
||||
id := request.PathParameter("user-id")
|
||||
...
|
||||
}
|
||||
|
||||
The (*Request, *Response) arguments provide functions for reading information from the request and writing information back to the response.
|
||||
|
||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-user-resource.go with a full implementation.
|
||||
|
||||
Regular expression matching Routes
|
||||
|
||||
A Route parameter can be specified using the format "uri/{var[:regexp]}" or the special version "uri/{var:*}" for matching the tail of the path.
|
||||
For example, /persons/{name:[A-Z][A-Z]} can be used to restrict values for the parameter "name" to only contain capital alphabetic characters.
|
||||
Regular expressions must use the standard Go syntax as described in the regexp package. (https://code.google.com/p/re2/wiki/Syntax)
|
||||
This feature requires the use of a CurlyRouter.
|
||||
|
||||
Containers
|
||||
|
||||
A Container holds a collection of WebServices, Filters and a http.ServeMux for multiplexing http requests.
|
||||
Using the statements "restful.Add(...) and restful.Filter(...)" will register WebServices and Filters to the Default Container.
|
||||
The Default container of go-restful uses the http.DefaultServeMux.
|
||||
You can create your own Container and create a new http.Server for that particular container.
|
||||
|
||||
container := restful.NewContainer()
|
||||
server := &http.Server{Addr: ":8081", Handler: container}
|
||||
|
||||
Filters
|
||||
|
||||
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
|
||||
You can use filters to perform generic logging, measurement, authentication, redirect, set response headers etc.
|
||||
In the restful package there are three hooks into the request,response flow where filters can be added.
|
||||
Each filter must define a FilterFunction:
|
||||
|
||||
func (req *restful.Request, resp *restful.Response, chain *restful.FilterChain)
|
||||
|
||||
Use the following statement to pass the request,response pair to the next filter or RouteFunction
|
||||
|
||||
chain.ProcessFilter(req, resp)
|
||||
|
||||
Container Filters
|
||||
|
||||
These are processed before any registered WebService.
|
||||
|
||||
// install a (global) filter for the default container (processed before any webservice)
|
||||
restful.Filter(globalLogging)
|
||||
|
||||
WebService Filters
|
||||
|
||||
These are processed before any Route of a WebService.
|
||||
|
||||
// install a webservice filter (processed before any route)
|
||||
ws.Filter(webserviceLogging).Filter(measureTime)
|
||||
|
||||
|
||||
Route Filters
|
||||
|
||||
These are processed before calling the function associated with the Route.
|
||||
|
||||
// install 2 chained route filters (processed before calling findUser)
|
||||
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
|
||||
|
||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-filters.go with full implementations.
|
||||
|
||||
Response Encoding
|
||||
|
||||
Two encodings are supported: gzip and deflate. To enable this for all responses:
|
||||
|
||||
restful.DefaultContainer.EnableContentEncoding(true)
|
||||
|
||||
If a Http request includes the Accept-Encoding header then the response content will be compressed using the specified encoding.
|
||||
Alternatively, you can create a Filter that performs the encoding and install it per WebService or Route.
|
||||
|
||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-encoding-filter.go
|
||||
|
||||
OPTIONS support
|
||||
|
||||
By installing a pre-defined container filter, your Webservice(s) can respond to the OPTIONS Http request.
|
||||
|
||||
Filter(OPTIONSFilter())
|
||||
|
||||
CORS
|
||||
|
||||
By installing the filter of a CrossOriginResourceSharing (CORS), your WebService(s) can handle CORS requests.
|
||||
|
||||
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
|
||||
Filter(cors.Filter)
|
||||
|
||||
Error Handling
|
||||
|
||||
Unexpected things happen. If a request cannot be processed because of a failure, your service needs to tell via the response what happened and why.
|
||||
For this reason HTTP status codes exist and it is important to use the correct code in every exceptional situation.
|
||||
|
||||
400: Bad Request
|
||||
|
||||
If path or query parameters are not valid (content or type) then use http.StatusBadRequest.
|
||||
|
||||
404: Not Found
|
||||
|
||||
Despite a valid URI, the resource requested may not be available
|
||||
|
||||
500: Internal Server Error
|
||||
|
||||
If the application logic could not process the request (or write the response) then use http.StatusInternalServerError.
|
||||
|
||||
405: Method Not Allowed
|
||||
|
||||
The request has a valid URL but the method (GET,PUT,POST,...) is not allowed.
|
||||
|
||||
406: Not Acceptable
|
||||
|
||||
The request does not have or has an unknown Accept Header set for this operation.
|
||||
|
||||
415: Unsupported Media Type
|
||||
|
||||
The request does not have or has an unknown Content-Type Header set for this operation.
|
||||
|
||||
ServiceError
|
||||
|
||||
In addition to setting the correct (error) Http status code, you can choose to write a ServiceError message on the response.
|
||||
|
||||
Performance options
|
||||
|
||||
This package has several options that affect the performance of your service. It is important to understand them and how you can change it.
|
||||
|
||||
restful.DefaultContainer.DoNotRecover(false)
|
||||
|
||||
DoNotRecover controls whether panics will be caught to return HTTP 500.
|
||||
If set to false, the container will recover from panics.
|
||||
Default value is true
|
||||
|
||||
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
|
||||
|
||||
If content encoding is enabled then the default strategy for getting new gzip/zlib writers and readers is to use a sync.Pool.
|
||||
Because writers are expensive structures, performance is even more improved when using a preloaded cache. You can also inject your own implementation.
|
||||
|
||||
Trouble shooting
|
||||
|
||||
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
|
||||
Enabling this feature requires you to set an implementation of restful.StdLogger (e.g. log.Logger) instance such as:
|
||||
|
||||
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
|
||||
|
||||
Logging
|
||||
|
||||
The restful.SetLogger() method allows you to override the logger used by the package. By default restful
|
||||
uses the standard library `log` package and logs to stdout. Different logging packages are supported as
|
||||
long as they conform to `StdLogger` interface defined in the `log` sub-package, writing an adapter for your
|
||||
preferred package is simple.
|
||||
|
||||
Resources
|
||||
|
||||
[project]: https://github.com/emicklei/go-restful
|
||||
|
||||
[examples]: https://github.com/emicklei/go-restful/blob/master/examples
|
||||
|
||||
[design]: http://ernestmicklei.com/2012/11/11/go-restful-api-design/
|
||||
|
||||
[showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape
|
||||
|
||||
(c) 2012-2015, http://ernestmicklei.com. MIT License
|
||||
*/
|
||||
package restful
|
162
vendor/github.com/emicklei/go-restful/entity_accessors.go
сгенерированный
поставляемый
Normal file
162
vendor/github.com/emicklei/go-restful/entity_accessors.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,162 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// EntityReaderWriter can read and write values using an encoding such as JSON,XML.
|
||||
type EntityReaderWriter interface {
|
||||
// Read a serialized version of the value from the request.
|
||||
// The Request may have a decompressing reader. Depends on Content-Encoding.
|
||||
Read(req *Request, v interface{}) error
|
||||
|
||||
// Write a serialized version of the value on the response.
|
||||
// The Response may have a compressing writer. Depends on Accept-Encoding.
|
||||
// status should be a valid Http Status code
|
||||
Write(resp *Response, status int, v interface{}) error
|
||||
}
|
||||
|
||||
// entityAccessRegistry is a singleton
|
||||
var entityAccessRegistry = &entityReaderWriters{
|
||||
protection: new(sync.RWMutex),
|
||||
accessors: map[string]EntityReaderWriter{},
|
||||
}
|
||||
|
||||
// entityReaderWriters associates MIME to an EntityReaderWriter
|
||||
type entityReaderWriters struct {
|
||||
protection *sync.RWMutex
|
||||
accessors map[string]EntityReaderWriter
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON))
|
||||
RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML))
|
||||
}
|
||||
|
||||
// RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type.
|
||||
func RegisterEntityAccessor(mime string, erw EntityReaderWriter) {
|
||||
entityAccessRegistry.protection.Lock()
|
||||
defer entityAccessRegistry.protection.Unlock()
|
||||
entityAccessRegistry.accessors[mime] = erw
|
||||
}
|
||||
|
||||
// NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content.
|
||||
// This package is already initialized with such an accessor using the MIME_JSON contentType.
|
||||
func NewEntityAccessorJSON(contentType string) EntityReaderWriter {
|
||||
return entityJSONAccess{ContentType: contentType}
|
||||
}
|
||||
|
||||
// NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content.
|
||||
// This package is already initialized with such an accessor using the MIME_XML contentType.
|
||||
func NewEntityAccessorXML(contentType string) EntityReaderWriter {
|
||||
return entityXMLAccess{ContentType: contentType}
|
||||
}
|
||||
|
||||
// accessorAt returns the registered ReaderWriter for this MIME type.
|
||||
func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) {
|
||||
r.protection.RLock()
|
||||
defer r.protection.RUnlock()
|
||||
er, ok := r.accessors[mime]
|
||||
if !ok {
|
||||
// retry with reverse lookup
|
||||
// more expensive but we are in an exceptional situation anyway
|
||||
for k, v := range r.accessors {
|
||||
if strings.Contains(mime, k) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return er, ok
|
||||
}
|
||||
|
||||
// entityXMLAccess is a EntityReaderWriter for XML encoding
|
||||
type entityXMLAccess struct {
|
||||
// This is used for setting the Content-Type header when writing
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// Read unmarshalls the value from XML
|
||||
func (e entityXMLAccess) Read(req *Request, v interface{}) error {
|
||||
return xml.NewDecoder(req.Request.Body).Decode(v)
|
||||
}
|
||||
|
||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
||||
func (e entityXMLAccess) Write(resp *Response, status int, v interface{}) error {
|
||||
return writeXML(resp, status, e.ContentType, v)
|
||||
}
|
||||
|
||||
// writeXML marshalls the value to JSON and set the Content-Type Header.
|
||||
func writeXML(resp *Response, status int, contentType string, v interface{}) error {
|
||||
if v == nil {
|
||||
resp.WriteHeader(status)
|
||||
// do not write a nil representation
|
||||
return nil
|
||||
}
|
||||
if resp.prettyPrint {
|
||||
// pretty output must be created and written explicitly
|
||||
output, err := xml.MarshalIndent(v, " ", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
_, err = resp.Write([]byte(xml.Header))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = resp.Write(output)
|
||||
return err
|
||||
}
|
||||
// not-so-pretty
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
return xml.NewEncoder(resp).Encode(v)
|
||||
}
|
||||
|
||||
// entityJSONAccess is a EntityReaderWriter for JSON encoding
|
||||
type entityJSONAccess struct {
|
||||
// This is used for setting the Content-Type header when writing
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// Read unmarshalls the value from JSON
|
||||
func (e entityJSONAccess) Read(req *Request, v interface{}) error {
|
||||
decoder := NewDecoder(req.Request.Body)
|
||||
decoder.UseNumber()
|
||||
return decoder.Decode(v)
|
||||
}
|
||||
|
||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
||||
func (e entityJSONAccess) Write(resp *Response, status int, v interface{}) error {
|
||||
return writeJSON(resp, status, e.ContentType, v)
|
||||
}
|
||||
|
||||
// write marshalls the value to JSON and set the Content-Type Header.
|
||||
func writeJSON(resp *Response, status int, contentType string, v interface{}) error {
|
||||
if v == nil {
|
||||
resp.WriteHeader(status)
|
||||
// do not write a nil representation
|
||||
return nil
|
||||
}
|
||||
if resp.prettyPrint {
|
||||
// pretty output must be created and written explicitly
|
||||
output, err := MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
_, err = resp.Write(output)
|
||||
return err
|
||||
}
|
||||
// not-so-pretty
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
return NewEncoder(resp).Encode(v)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// FilterChain is a request scoped object to process one or more filters before calling the target RouteFunction.
|
||||
type FilterChain struct {
|
||||
Filters []FilterFunction // ordered list of FilterFunction
|
||||
Index int // index into filters that is currently in progress
|
||||
Target RouteFunction // function to call after passing all filters
|
||||
}
|
||||
|
||||
// ProcessFilter passes the request,response pair through the next of Filters.
|
||||
// Each filter can decide to proceed to the next Filter or handle the Response itself.
|
||||
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
|
||||
if f.Index < len(f.Filters) {
|
||||
f.Index++
|
||||
f.Filters[f.Index-1](request, response, f)
|
||||
} else {
|
||||
f.Target(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
// FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction
|
||||
type FilterFunction func(*Request, *Response, *FilterChain)
|
||||
|
||||
// NoBrowserCacheFilter is a filter function to set HTTP headers that disable browser caching
|
||||
// See examples/restful-no-cache-filter.go for usage
|
||||
func NoBrowserCacheFilter(req *Request, resp *Response, chain *FilterChain) {
|
||||
resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1.
|
||||
resp.Header().Set("Pragma", "no-cache") // HTTP 1.0.
|
||||
resp.Header().Set("Expires", "0") // Proxies.
|
||||
chain.ProcessFilter(req, resp)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// +build !jsoniter
|
||||
|
||||
package restful
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
var (
|
||||
MarshalIndent = json.MarshalIndent
|
||||
NewDecoder = json.NewDecoder
|
||||
NewEncoder = json.NewEncoder
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
// +build jsoniter
|
||||
|
||||
package restful
|
||||
|
||||
import "github.com/json-iterator/go"
|
||||
|
||||
var (
|
||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
MarshalIndent = json.MarshalIndent
|
||||
NewDecoder = json.NewDecoder
|
||||
NewEncoder = json.NewEncoder
|
||||
)
|
|
@ -0,0 +1,293 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
|
||||
// as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
|
||||
// RouterJSR311 implements the Router interface.
|
||||
// Concept of locators is not implemented.
|
||||
type RouterJSR311 struct{}
|
||||
|
||||
// SelectRoute is part of the Router interface and returns the best match
|
||||
// for the WebService and its Route for the given Request.
|
||||
func (r RouterJSR311) SelectRoute(
|
||||
webServices []*WebService,
|
||||
httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
|
||||
|
||||
// Identify the root resource class (WebService)
|
||||
dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
|
||||
if err != nil {
|
||||
return nil, nil, NewError(http.StatusNotFound, "")
|
||||
}
|
||||
// Obtain the set of candidate methods (Routes)
|
||||
routes := r.selectRoutes(dispatcher, finalMatch)
|
||||
if len(routes) == 0 {
|
||||
return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
||||
}
|
||||
|
||||
// Identify the method (Route) that will handle the request
|
||||
route, ok := r.detectRoute(routes, httpRequest)
|
||||
return dispatcher, route, ok
|
||||
}
|
||||
|
||||
// ExtractParameters is used to obtain the path parameters from the route using the same matching
|
||||
// engine as the JSR 311 router.
|
||||
func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string {
|
||||
webServiceExpr := webService.pathExpr
|
||||
webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath)
|
||||
pathParameters := r.extractParams(webServiceExpr, webServiceMatches)
|
||||
routeExpr := route.pathExpr
|
||||
routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1])
|
||||
routeParams := r.extractParams(routeExpr, routeMatches)
|
||||
for key, value := range routeParams {
|
||||
pathParameters[key] = value
|
||||
}
|
||||
return pathParameters
|
||||
}
|
||||
|
||||
func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string {
|
||||
params := map[string]string{}
|
||||
for i := 1; i < len(matches); i++ {
|
||||
if len(pathExpr.VarNames) >= i {
|
||||
params[pathExpr.VarNames[i-1]] = matches[i]
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
||||
func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
|
||||
ifOk := []Route{}
|
||||
for _, each := range routes {
|
||||
ok := true
|
||||
for _, fn := range each.If {
|
||||
if !fn(httpRequest) {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
ifOk = append(ifOk, each)
|
||||
}
|
||||
}
|
||||
if len(ifOk) == 0 {
|
||||
if trace {
|
||||
traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
|
||||
}
|
||||
return nil, NewError(http.StatusNotFound, "404: Not Found")
|
||||
}
|
||||
|
||||
// http method
|
||||
methodOk := []Route{}
|
||||
for _, each := range ifOk {
|
||||
if httpRequest.Method == each.Method {
|
||||
methodOk = append(methodOk, each)
|
||||
}
|
||||
}
|
||||
if len(methodOk) == 0 {
|
||||
if trace {
|
||||
traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method)
|
||||
}
|
||||
return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed")
|
||||
}
|
||||
inputMediaOk := methodOk
|
||||
|
||||
// content-type
|
||||
contentType := httpRequest.Header.Get(HEADER_ContentType)
|
||||
inputMediaOk = []Route{}
|
||||
for _, each := range methodOk {
|
||||
if each.matchesContentType(contentType) {
|
||||
inputMediaOk = append(inputMediaOk, each)
|
||||
}
|
||||
}
|
||||
if len(inputMediaOk) == 0 {
|
||||
if trace {
|
||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(methodOk), contentType)
|
||||
}
|
||||
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
|
||||
}
|
||||
|
||||
// accept
|
||||
outputMediaOk := []Route{}
|
||||
accept := httpRequest.Header.Get(HEADER_Accept)
|
||||
if len(accept) == 0 {
|
||||
accept = "*/*"
|
||||
}
|
||||
for _, each := range inputMediaOk {
|
||||
if each.matchesAccept(accept) {
|
||||
outputMediaOk = append(outputMediaOk, each)
|
||||
}
|
||||
}
|
||||
if len(outputMediaOk) == 0 {
|
||||
if trace {
|
||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept)
|
||||
}
|
||||
return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable")
|
||||
}
|
||||
// return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
|
||||
return &outputMediaOk[0], nil
|
||||
}
|
||||
|
||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
||||
// n/m > n/* > */*
|
||||
func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
|
||||
// TODO
|
||||
return &routes[0]
|
||||
}
|
||||
|
||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2)
|
||||
func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
|
||||
filtered := &sortableRouteCandidates{}
|
||||
for _, each := range dispatcher.Routes() {
|
||||
pathExpr := each.pathExpr
|
||||
matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
|
||||
if matches != nil {
|
||||
lastMatch := matches[len(matches)-1]
|
||||
if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
||||
filtered.candidates = append(filtered.candidates,
|
||||
routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filtered.candidates) == 0 {
|
||||
if trace {
|
||||
traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
|
||||
}
|
||||
return []Route{}
|
||||
}
|
||||
sort.Sort(sort.Reverse(filtered))
|
||||
|
||||
// select other routes from candidates whoes expression matches rmatch
|
||||
matchingRoutes := []Route{filtered.candidates[0].route}
|
||||
for c := 1; c < len(filtered.candidates); c++ {
|
||||
each := filtered.candidates[c]
|
||||
if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
|
||||
matchingRoutes = append(matchingRoutes, each.route)
|
||||
}
|
||||
}
|
||||
return matchingRoutes
|
||||
}
|
||||
|
||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
|
||||
func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
|
||||
filtered := &sortableDispatcherCandidates{}
|
||||
for _, each := range dispatchers {
|
||||
matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
||||
if matches != nil {
|
||||
filtered.candidates = append(filtered.candidates,
|
||||
dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
|
||||
}
|
||||
}
|
||||
if len(filtered.candidates) == 0 {
|
||||
if trace {
|
||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
|
||||
}
|
||||
return nil, "", errors.New("not found")
|
||||
}
|
||||
sort.Sort(sort.Reverse(filtered))
|
||||
return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
|
||||
}
|
||||
|
||||
// Types and functions to support the sorting of Routes
|
||||
|
||||
type routeCandidate struct {
|
||||
route Route
|
||||
matchesCount int // the number of capturing groups
|
||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
||||
}
|
||||
|
||||
func (r routeCandidate) expressionToMatch() string {
|
||||
return r.route.pathExpr.Source
|
||||
}
|
||||
|
||||
func (r routeCandidate) String() string {
|
||||
return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
|
||||
}
|
||||
|
||||
type sortableRouteCandidates struct {
|
||||
candidates []routeCandidate
|
||||
}
|
||||
|
||||
func (rcs *sortableRouteCandidates) Len() int {
|
||||
return len(rcs.candidates)
|
||||
}
|
||||
func (rcs *sortableRouteCandidates) Swap(i, j int) {
|
||||
rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
|
||||
}
|
||||
func (rcs *sortableRouteCandidates) Less(i, j int) bool {
|
||||
ci := rcs.candidates[i]
|
||||
cj := rcs.candidates[j]
|
||||
// primary key
|
||||
if ci.literalCount < cj.literalCount {
|
||||
return true
|
||||
}
|
||||
if ci.literalCount > cj.literalCount {
|
||||
return false
|
||||
}
|
||||
// secundary key
|
||||
if ci.matchesCount < cj.matchesCount {
|
||||
return true
|
||||
}
|
||||
if ci.matchesCount > cj.matchesCount {
|
||||
return false
|
||||
}
|
||||
// tertiary key
|
||||
if ci.nonDefaultCount < cj.nonDefaultCount {
|
||||
return true
|
||||
}
|
||||
if ci.nonDefaultCount > cj.nonDefaultCount {
|
||||
return false
|
||||
}
|
||||
// quaternary key ("source" is interpreted as Path)
|
||||
return ci.route.Path < cj.route.Path
|
||||
}
|
||||
|
||||
// Types and functions to support the sorting of Dispatchers
|
||||
|
||||
type dispatcherCandidate struct {
|
||||
dispatcher *WebService
|
||||
finalMatch string
|
||||
matchesCount int // the number of capturing groups
|
||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
||||
}
|
||||
type sortableDispatcherCandidates struct {
|
||||
candidates []dispatcherCandidate
|
||||
}
|
||||
|
||||
func (dc *sortableDispatcherCandidates) Len() int {
|
||||
return len(dc.candidates)
|
||||
}
|
||||
func (dc *sortableDispatcherCandidates) Swap(i, j int) {
|
||||
dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
|
||||
}
|
||||
func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
|
||||
ci := dc.candidates[i]
|
||||
cj := dc.candidates[j]
|
||||
// primary key
|
||||
if ci.matchesCount < cj.matchesCount {
|
||||
return true
|
||||
}
|
||||
if ci.matchesCount > cj.matchesCount {
|
||||
return false
|
||||
}
|
||||
// secundary key
|
||||
if ci.literalCount < cj.literalCount {
|
||||
return true
|
||||
}
|
||||
if ci.literalCount > cj.literalCount {
|
||||
return false
|
||||
}
|
||||
// tertiary key
|
||||
return ci.nonDefaultCount < cj.nonDefaultCount
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
stdlog "log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// StdLogger corresponds to a minimal subset of the interface satisfied by stdlib log.Logger
|
||||
type StdLogger interface {
|
||||
Print(v ...interface{})
|
||||
Printf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
var Logger StdLogger
|
||||
|
||||
func init() {
|
||||
// default Logger
|
||||
SetLogger(stdlog.New(os.Stderr, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile))
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for this package
|
||||
func SetLogger(customLogger StdLogger) {
|
||||
Logger = customLogger
|
||||
}
|
||||
|
||||
// Print delegates to the Logger
|
||||
func Print(v ...interface{}) {
|
||||
Logger.Print(v...)
|
||||
}
|
||||
|
||||
// Printf delegates to the Logger
|
||||
func Printf(format string, v ...interface{}) {
|
||||
Logger.Printf(format, v...)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2014 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
import (
|
||||
"github.com/emicklei/go-restful/log"
|
||||
)
|
||||
|
||||
var trace bool = false
|
||||
var traceLogger log.StdLogger
|
||||
|
||||
func init() {
|
||||
traceLogger = log.Logger // use the package logger by default
|
||||
}
|
||||
|
||||
// TraceLogger enables detailed logging of Http request matching and filter invocation. Default no logger is set.
|
||||
// You may call EnableTracing() directly to enable trace logging to the package-wide logger.
|
||||
func TraceLogger(logger log.StdLogger) {
|
||||
traceLogger = logger
|
||||
EnableTracing(logger != nil)
|
||||
}
|
||||
|
||||
// SetLogger exposes the setter for the global logger on the top-level package
|
||||
func SetLogger(customLogger log.StdLogger) {
|
||||
log.SetLogger(customLogger)
|
||||
}
|
||||
|
||||
// EnableTracing can be used to Trace logging on and off.
|
||||
func EnableTracing(enabled bool) {
|
||||
trace = enabled
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package restful
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type mime struct {
|
||||
media string
|
||||
quality float64
|
||||
}
|
||||
|
||||
// insertMime adds a mime to a list and keeps it sorted by quality.
|
||||
func insertMime(l []mime, e mime) []mime {
|
||||
for i, each := range l {
|
||||
// if current mime has lower quality then insert before
|
||||
if e.quality > each.quality {
|
||||
left := append([]mime{}, l[0:i]...)
|
||||
return append(append(left, e), l[i:]...)
|
||||
}
|
||||
}
|
||||
return append(l, e)
|
||||
}
|
||||
|
||||
// sortedMimes returns a list of mime sorted (desc) by its specified quality.
|
||||
func sortedMimes(accept string) (sorted []mime) {
|
||||
for _, each := range strings.Split(accept, ",") {
|
||||
typeAndQuality := strings.Split(strings.Trim(each, " "), ";")
|
||||
if len(typeAndQuality) == 1 {
|
||||
sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0})
|
||||
} else {
|
||||
// take factor
|
||||
parts := strings.Split(typeAndQuality[1], "=")
|
||||
if len(parts) == 2 {
|
||||
f, err := strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
traceLogger.Printf("unable to parse quality in %s, %v", each, err)
|
||||
} else {
|
||||
sorted = insertMime(sorted, mime{typeAndQuality[0], f})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package restful
|
||||
|
||||
import "strings"
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
||||
// and provides the response with a set of allowed methods for the request URL Path.
|
||||
// As for any filter, you can also install it for a particular WebService within a Container.
|
||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
||||
func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) {
|
||||
if "OPTIONS" != req.Request.Method {
|
||||
chain.ProcessFilter(req, resp)
|
||||
return
|
||||
}
|
||||
|
||||
archs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
|
||||
methods := strings.Join(c.computeAllowedMethods(req), ",")
|
||||
origin := req.Request.Header.Get(HEADER_Origin)
|
||||
|
||||
resp.AddHeader(HEADER_Allow, methods)
|
||||
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
|
||||
resp.AddHeader(HEADER_AccessControlAllowHeaders, archs)
|
||||
resp.AddHeader(HEADER_AccessControlAllowMethods, methods)
|
||||
}
|
||||
|
||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
||||
// and provides the response with a set of allowed methods for the request URL Path.
|
||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
||||
func OPTIONSFilter() FilterFunction {
|
||||
return DefaultContainer.OPTIONSFilter
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
const (
|
||||
// PathParameterKind = indicator of Request parameter type "path"
|
||||
PathParameterKind = iota
|
||||
|
||||
// QueryParameterKind = indicator of Request parameter type "query"
|
||||
QueryParameterKind
|
||||
|
||||
// BodyParameterKind = indicator of Request parameter type "body"
|
||||
BodyParameterKind
|
||||
|
||||
// HeaderParameterKind = indicator of Request parameter type "header"
|
||||
HeaderParameterKind
|
||||
|
||||
// FormParameterKind = indicator of Request parameter type "form"
|
||||
FormParameterKind
|
||||
|
||||
// CollectionFormatCSV comma separated values `foo,bar`
|
||||
CollectionFormatCSV = CollectionFormat("csv")
|
||||
|
||||
// CollectionFormatSSV space separated values `foo bar`
|
||||
CollectionFormatSSV = CollectionFormat("ssv")
|
||||
|
||||
// CollectionFormatTSV tab separated values `foo\tbar`
|
||||
CollectionFormatTSV = CollectionFormat("tsv")
|
||||
|
||||
// CollectionFormatPipes pipe separated values `foo|bar`
|
||||
CollectionFormatPipes = CollectionFormat("pipes")
|
||||
|
||||
// CollectionFormatMulti corresponds to multiple parameter instances instead of multiple values for a single
|
||||
// instance `foo=bar&foo=baz`. This is valid only for QueryParameters and FormParameters
|
||||
CollectionFormatMulti = CollectionFormat("multi")
|
||||
)
|
||||
|
||||
type CollectionFormat string
|
||||
|
||||
func (cf CollectionFormat) String() string {
|
||||
return string(cf)
|
||||
}
|
||||
|
||||
// Parameter is for documententing the parameter used in a Http Request
|
||||
// ParameterData kinds are Path,Query and Body
|
||||
type Parameter struct {
|
||||
data *ParameterData
|
||||
}
|
||||
|
||||
// ParameterData represents the state of a Parameter.
|
||||
// It is made public to make it accessible to e.g. the Swagger package.
|
||||
type ParameterData struct {
|
||||
Name, Description, DataType, DataFormat string
|
||||
Kind int
|
||||
Required bool
|
||||
AllowableValues map[string]string
|
||||
AllowMultiple bool
|
||||
DefaultValue string
|
||||
CollectionFormat string
|
||||
}
|
||||
|
||||
// Data returns the state of the Parameter
|
||||
func (p *Parameter) Data() ParameterData {
|
||||
return *p.data
|
||||
}
|
||||
|
||||
// Kind returns the parameter type indicator (see const for valid values)
|
||||
func (p *Parameter) Kind() int {
|
||||
return p.data.Kind
|
||||
}
|
||||
|
||||
func (p *Parameter) bePath() *Parameter {
|
||||
p.data.Kind = PathParameterKind
|
||||
return p
|
||||
}
|
||||
func (p *Parameter) beQuery() *Parameter {
|
||||
p.data.Kind = QueryParameterKind
|
||||
return p
|
||||
}
|
||||
func (p *Parameter) beBody() *Parameter {
|
||||
p.data.Kind = BodyParameterKind
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parameter) beHeader() *Parameter {
|
||||
p.data.Kind = HeaderParameterKind
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parameter) beForm() *Parameter {
|
||||
p.data.Kind = FormParameterKind
|
||||
return p
|
||||
}
|
||||
|
||||
// Required sets the required field and returns the receiver
|
||||
func (p *Parameter) Required(required bool) *Parameter {
|
||||
p.data.Required = required
|
||||
return p
|
||||
}
|
||||
|
||||
// AllowMultiple sets the allowMultiple field and returns the receiver
|
||||
func (p *Parameter) AllowMultiple(multiple bool) *Parameter {
|
||||
p.data.AllowMultiple = multiple
|
||||
return p
|
||||
}
|
||||
|
||||
// AllowableValues sets the allowableValues field and returns the receiver
|
||||
func (p *Parameter) AllowableValues(values map[string]string) *Parameter {
|
||||
p.data.AllowableValues = values
|
||||
return p
|
||||
}
|
||||
|
||||
// DataType sets the dataType field and returns the receiver
|
||||
func (p *Parameter) DataType(typeName string) *Parameter {
|
||||
p.data.DataType = typeName
|
||||
return p
|
||||
}
|
||||
|
||||
// DataFormat sets the dataFormat field for Swagger UI
|
||||
func (p *Parameter) DataFormat(formatName string) *Parameter {
|
||||
p.data.DataFormat = formatName
|
||||
return p
|
||||
}
|
||||
|
||||
// DefaultValue sets the default value field and returns the receiver
|
||||
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
|
||||
p.data.DefaultValue = stringRepresentation
|
||||
return p
|
||||
}
|
||||
|
||||
// Description sets the description value field and returns the receiver
|
||||
func (p *Parameter) Description(doc string) *Parameter {
|
||||
p.data.Description = doc
|
||||
return p
|
||||
}
|
||||
|
||||
// CollectionFormat sets the collection format for an array type
|
||||
func (p *Parameter) CollectionFormat(format CollectionFormat) *Parameter {
|
||||
p.data.CollectionFormat = format.String()
|
||||
return p
|
||||
}
|
74
vendor/github.com/emicklei/go-restful/path_expression.go
сгенерированный
поставляемый
Normal file
74
vendor/github.com/emicklei/go-restful/path_expression.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,74 @@
|
|||
package restful
|
||||
|
||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PathExpression holds a compiled path expression (RegExp) needed to match against
|
||||
// Http request paths and to extract path parameter values.
|
||||
type pathExpression struct {
|
||||
LiteralCount int // the number of literal characters (means those not resulting from template variable substitution)
|
||||
VarNames []string // the names of parameters (enclosed by {}) in the path
|
||||
VarCount int // the number of named parameters (enclosed by {}) in the path
|
||||
Matcher *regexp.Regexp
|
||||
Source string // Path as defined by the RouteBuilder
|
||||
tokens []string
|
||||
}
|
||||
|
||||
// NewPathExpression creates a PathExpression from the input URL path.
|
||||
// Returns an error if the path is invalid.
|
||||
func newPathExpression(path string) (*pathExpression, error) {
|
||||
expression, literalCount, varNames, varCount, tokens := templateToRegularExpression(path)
|
||||
compiled, err := regexp.Compile(expression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pathExpression{literalCount, varNames, varCount, compiled, expression, tokens}, nil
|
||||
}
|
||||
|
||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-370003.7.3
|
||||
func templateToRegularExpression(template string) (expression string, literalCount int, varNames []string, varCount int, tokens []string) {
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("^")
|
||||
//tokens = strings.Split(template, "/")
|
||||
tokens = tokenizePath(template)
|
||||
for _, each := range tokens {
|
||||
if each == "" {
|
||||
continue
|
||||
}
|
||||
buffer.WriteString("/")
|
||||
if strings.HasPrefix(each, "{") {
|
||||
// check for regular expression in variable
|
||||
colon := strings.Index(each, ":")
|
||||
var varName string
|
||||
if colon != -1 {
|
||||
// extract expression
|
||||
varName = strings.TrimSpace(each[1:colon])
|
||||
paramExpr := strings.TrimSpace(each[colon+1 : len(each)-1])
|
||||
if paramExpr == "*" { // special case
|
||||
buffer.WriteString("(.*)")
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("(%s)", paramExpr)) // between colon and closing moustache
|
||||
}
|
||||
} else {
|
||||
// plain var
|
||||
varName = strings.TrimSpace(each[1 : len(each)-1])
|
||||
buffer.WriteString("([^/]+?)")
|
||||
}
|
||||
varNames = append(varNames, varName)
|
||||
varCount += 1
|
||||
} else {
|
||||
literalCount += len(each)
|
||||
encoded := each // TODO URI encode
|
||||
buffer.WriteString(regexp.QuoteMeta(encoded))
|
||||
}
|
||||
}
|
||||
return strings.TrimRight(buffer.String(), "/") + "(/.*)?$", literalCount, varNames, varCount, tokens
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package restful
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Copyright 2018 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// PathProcessor is extra behaviour that a Router can provide to extract path parameters from the path.
|
||||
// If a Router does not implement this interface then the default behaviour will be used.
|
||||
type PathProcessor interface {
|
||||
// ExtractParameters gets the path parameters defined in the route and webService from the urlPath
|
||||
ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string
|
||||
}
|
||||
|
||||
type defaultPathProcessor struct{}
|
||||
|
||||
// Extract the parameters from the request url path
|
||||
func (d defaultPathProcessor) ExtractParameters(r *Route, _ *WebService, urlPath string) map[string]string {
|
||||
urlParts := tokenizePath(urlPath)
|
||||
pathParameters := map[string]string{}
|
||||
for i, key := range r.pathParts {
|
||||
var value string
|
||||
if i >= len(urlParts) {
|
||||
value = ""
|
||||
} else {
|
||||
value = urlParts[i]
|
||||
}
|
||||
if strings.HasPrefix(key, "{") { // path-parameter
|
||||
if colon := strings.Index(key, ":"); colon != -1 {
|
||||
// extract by regex
|
||||
regPart := key[colon+1 : len(key)-1]
|
||||
keyPart := key[1:colon]
|
||||
if regPart == "*" {
|
||||
pathParameters[keyPart] = untokenizePath(i, urlParts)
|
||||
break
|
||||
} else {
|
||||
pathParameters[keyPart] = value
|
||||
}
|
||||
} else {
|
||||
// without enclosing {}
|
||||
pathParameters[key[1:len(key)-1]] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return pathParameters
|
||||
}
|
||||
|
||||
// Untokenize back into an URL path using the slash separator
|
||||
func untokenizePath(offset int, parts []string) string {
|
||||
var buffer bytes.Buffer
|
||||
for p := offset; p < len(parts); p++ {
|
||||
buffer.WriteString(parts[p])
|
||||
// do not end
|
||||
if p < len(parts)-1 {
|
||||
buffer.WriteString("/")
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче