switch from godep to glide

This commit is contained in:
Tomas Kral
2016-12-07 19:57:19 +01:00
parent 862419b836
commit fe679fbca6
861 changed files with 16916 additions and 81678 deletions
-693
View File
@@ -1,693 +0,0 @@
package graph
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/gonum/graph"
"github.com/gonum/graph/concrete"
"github.com/gonum/graph/encoding/dot"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/util/sets"
)
type Node struct {
concrete.Node
UniqueName
}
// DOTAttributes implements an attribute getter for the DOT encoding
func (n Node) DOTAttributes() []dot.Attribute {
return []dot.Attribute{{Key: "label", Value: fmt.Sprintf("%q", n.UniqueName)}}
}
// ExistenceChecker is an interface for those nodes that can be created without a backing object.
// This can happen when a node wants an edge to a non-existent node. We know the node should exist,
// The graph needs something in that location to track the information we have about the node, but the
// backing object doesn't exist.
type ExistenceChecker interface {
// Found returns false if the node represents an object that we don't have the backing object for
Found() bool
}
type UniqueName string
type UniqueNameFunc func(obj interface{}) UniqueName
func (n UniqueName) UniqueName() string {
return string(n)
}
func (n UniqueName) String() string {
return string(n)
}
type uniqueNamer interface {
UniqueName() UniqueName
}
type NodeFinder interface {
Find(name UniqueName) graph.Node
}
// UniqueNodeInitializer is a graph that allows nodes with a unique name to be added without duplication.
// If the node is newly added, true will be returned.
type UniqueNodeInitializer interface {
FindOrCreate(name UniqueName, fn NodeInitializerFunc) (graph.Node, bool)
}
type NodeInitializerFunc func(Node) graph.Node
func EnsureUnique(g UniqueNodeInitializer, name UniqueName, fn NodeInitializerFunc) graph.Node {
node, _ := g.FindOrCreate(name, fn)
return node
}
type MutableDirectedEdge interface {
AddEdge(from, to graph.Node, edgeKind string)
}
type MutableUniqueGraph interface {
graph.Mutable
MutableDirectedEdge
UniqueNodeInitializer
NodeFinder
}
type Edge struct {
concrete.Edge
kinds sets.String
}
func NewEdge(from, to graph.Node, kinds ...string) Edge {
return Edge{concrete.Edge{F: from, T: to}, sets.NewString(kinds...)}
}
func (e Edge) Kinds() sets.String {
return e.kinds
}
func (e Edge) IsKind(kind string) bool {
return e.kinds.Has(kind)
}
// DOTAttributes implements an attribute getter for the DOT encoding
func (e Edge) DOTAttributes() []dot.Attribute {
return []dot.Attribute{{Key: "label", Value: fmt.Sprintf("%q", strings.Join(e.Kinds().List(), ","))}}
}
type GraphDescriber interface {
Name(node graph.Node) string
Kind(node graph.Node) string
Object(node graph.Node) interface{}
EdgeKinds(edge graph.Edge) sets.String
}
type Interface interface {
graph.Directed
GraphDescriber
MutableUniqueGraph
Edges() []graph.Edge
}
type Namer interface {
ResourceName(obj interface{}) string
}
type namer struct{}
var DefaultNamer Namer = namer{}
func (namer) ResourceName(obj interface{}) string {
switch t := obj.(type) {
case uniqueNamer:
return t.UniqueName().String()
default:
return reflect.TypeOf(obj).String()
}
}
type Graph struct {
// the standard graph
graph.Directed
// helper methods for switching on the kind and types of the node
GraphDescriber
// exposes the public interface for adding nodes
uniqueNamedGraph
// the internal graph object, which allows edges and nodes to be directly added
internal *concrete.DirectedGraph
}
// Graph must implement MutableUniqueGraph
var _ MutableUniqueGraph = Graph{}
// New initializes a graph from input to output.
func New() Graph {
g := concrete.NewDirectedGraph()
return Graph{
Directed: g,
GraphDescriber: typedGraph{},
uniqueNamedGraph: newUniqueNamedGraph(g),
internal: g,
}
}
// Edges returns all the edges of the graph. Note that the returned set
// will have no specific ordering.
func (g Graph) Edges() []graph.Edge {
return g.internal.Edges()
}
func (g Graph) String() string {
ret := ""
nodes := g.Nodes()
sort.Sort(ByID(nodes))
for _, node := range nodes {
ret += fmt.Sprintf("%d: %v\n", node.ID(), g.GraphDescriber.Name(node))
// can't use SuccessorEdges, because I want stable ordering
successors := g.From(node)
sort.Sort(ByID(successors))
for _, successor := range successors {
edge := g.Edge(node, successor)
kinds := g.EdgeKinds(edge)
for _, kind := range kinds.List() {
ret += fmt.Sprintf("\t%v to %d: %v\n", kind, successor.ID(), g.GraphDescriber.Name(successor))
}
}
}
return ret
}
// ByID is a sorted group of nodes by ID
type ByID []graph.Node
func (m ByID) Len() int { return len(m) }
func (m ByID) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m ByID) Less(i, j int) bool {
return m[i].ID() < m[j].ID()
}
// SyntheticNodes returns back the set of nodes that were created in response to edge requests, but did not exist
func (g Graph) SyntheticNodes() []graph.Node {
ret := []graph.Node{}
nodes := g.Nodes()
sort.Sort(ByID(nodes))
for _, node := range nodes {
if potentiallySyntheticNode, ok := node.(ExistenceChecker); ok {
if !potentiallySyntheticNode.Found() {
ret = append(ret, node)
}
}
}
return ret
}
// NodesByKind returns all the nodes of the graph with the provided kinds
func (g Graph) NodesByKind(nodeKinds ...string) []graph.Node {
ret := []graph.Node{}
kinds := sets.NewString(nodeKinds...)
for _, node := range g.internal.Nodes() {
if kinds.Has(g.Kind(node)) {
ret = append(ret, node)
}
}
return ret
}
// RootNodes returns all the roots of this graph.
func (g Graph) RootNodes() []graph.Node {
roots := []graph.Node{}
for _, n := range g.Nodes() {
if len(g.To(n)) != 0 {
continue
}
roots = append(roots, n)
}
return roots
}
// PredecessorEdges invokes fn with all of the predecessor edges of node that have the specified
// edge kind.
func (g Graph) PredecessorEdges(node graph.Node, fn EdgeFunc, edgeKinds ...string) {
for _, n := range g.To(node) {
edge := g.Edge(n, node)
kinds := g.EdgeKinds(edge)
if kinds.HasAny(edgeKinds...) {
fn(g, n, node, kinds)
}
}
}
// SuccessorEdges invokes fn with all of the successor edges of node that have the specified
// edge kind.
func (g Graph) SuccessorEdges(node graph.Node, fn EdgeFunc, edgeKinds ...string) {
for _, n := range g.From(node) {
edge := g.Edge(node, n)
kinds := g.EdgeKinds(edge)
if kinds.HasAny(edgeKinds...) {
fn(g, n, node, kinds)
}
}
}
// OutboundEdges returns all the outbound edges from node that are in the list of edgeKinds
// if edgeKinds is empty, then all edges are returned
func (g Graph) OutboundEdges(node graph.Node, edgeKinds ...string) []graph.Edge {
ret := []graph.Edge{}
for _, n := range g.From(node) {
edge := g.Edge(node, n)
if edge == nil {
continue
}
if len(edgeKinds) == 0 || g.EdgeKinds(edge).HasAny(edgeKinds...) {
ret = append(ret, edge)
}
}
return ret
}
// InboundEdges returns all the inbound edges to node that are in the list of edgeKinds
// if edgeKinds is empty, then all edges are returned
func (g Graph) InboundEdges(node graph.Node, edgeKinds ...string) []graph.Edge {
ret := []graph.Edge{}
for _, n := range g.To(node) {
edge := g.Edge(n, node)
if edge == nil {
continue
}
if len(edgeKinds) == 0 || g.EdgeKinds(edge).HasAny(edgeKinds...) {
ret = append(ret, edge)
}
}
return ret
}
// PredecessorNodesByEdgeKind returns all the predecessor nodes of the given node
// that can be reached via edges of the provided kinds
func (g Graph) PredecessorNodesByEdgeKind(node graph.Node, edgeKinds ...string) []graph.Node {
ret := []graph.Node{}
for _, inboundEdges := range g.InboundEdges(node, edgeKinds...) {
ret = append(ret, inboundEdges.From())
}
return ret
}
// SuccessorNodesByEdgeKind returns all the successor nodes of the given node
// that can be reached via edges of the provided kinds
func (g Graph) SuccessorNodesByEdgeKind(node graph.Node, edgeKinds ...string) []graph.Node {
ret := []graph.Node{}
for _, outboundEdge := range g.OutboundEdges(node, edgeKinds...) {
ret = append(ret, outboundEdge.To())
}
return ret
}
func (g Graph) SuccessorNodesByNodeAndEdgeKind(node graph.Node, nodeKind, edgeKind string) []graph.Node {
ret := []graph.Node{}
for _, successor := range g.SuccessorNodesByEdgeKind(node, edgeKind) {
if g.Kind(successor) != nodeKind {
continue
}
ret = append(ret, successor)
}
return ret
}
func (g Graph) AddNode(n graph.Node) {
g.internal.AddNode(n)
}
// AddEdge implements MutableUniqueGraph
func (g Graph) AddEdge(from, to graph.Node, edgeKind string) {
// a Contains edge has semantic meaning for osgraph.Graph objects. It never makes sense
// to allow a single object to be "contained" by multiple nodes.
if edgeKind == ContainsEdgeKind {
// check incoming edges on the 'to' node to be certain that we aren't already contained
containsEdges := g.InboundEdges(to, ContainsEdgeKind)
if len(containsEdges) != 0 {
// TODO consider changing the AddEdge API to make this cleaner. This is a pretty severe programming error
panic(fmt.Sprintf("%v is already contained by %v", to, containsEdges))
}
}
kinds := sets.NewString(edgeKind)
if existingEdge := g.Edge(from, to); existingEdge != nil {
kinds.Insert(g.EdgeKinds(existingEdge).List()...)
}
g.internal.SetEdge(NewEdge(from, to, kinds.List()...), 1.0)
}
// addEdges adds the specified edges, filtered by the provided edge connection
// function.
func (g Graph) addEdges(edges []graph.Edge, fn EdgeFunc) {
for _, e := range edges {
switch t := e.(type) {
case concrete.WeightedEdge:
if fn(g, t.From(), t.To(), t.Edge.(Edge).Kinds()) {
g.internal.SetEdge(t.Edge.(Edge), t.Cost)
}
case Edge:
if fn(g, t.From(), t.To(), t.Kinds()) {
g.internal.SetEdge(t, 1.0)
}
default:
panic("bad edge")
}
}
}
// NodeFunc is passed a new graph, a node in the graph, and should return true if the
// node should be included.
type NodeFunc func(g Interface, n graph.Node) bool
// NodesOfKind returns a new NodeFunc accepting the provided kinds of nodes
// If no kinds are specified, the returned NodeFunc will accept all nodes
func NodesOfKind(kinds ...string) NodeFunc {
if len(kinds) == 0 {
return func(g Interface, n graph.Node) bool {
return true
}
}
allowedKinds := sets.NewString(kinds...)
return func(g Interface, n graph.Node) bool {
return allowedKinds.Has(g.Kind(n))
}
}
// EdgeFunc is passed a new graph, an edge in the current graph, and should mutate
// the new graph as needed. If true is returned, the existing edge will be added to the graph.
type EdgeFunc func(g Interface, from, to graph.Node, edgeKinds sets.String) bool
// EdgesOfKind returns a new EdgeFunc accepting the provided kinds of edges
// If no kinds are specified, the returned EdgeFunc will accept all edges
func EdgesOfKind(kinds ...string) EdgeFunc {
if len(kinds) == 0 {
return func(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
return true
}
}
allowedKinds := sets.NewString(kinds...)
return func(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
return allowedKinds.HasAny(edgeKinds.List()...)
}
}
// RemoveInboundEdges returns a new EdgeFunc dismissing any inbound edges to
// the provided set of nodes
func RemoveInboundEdges(nodes []graph.Node) EdgeFunc {
return func(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
for _, node := range nodes {
if node == to {
return false
}
}
return true
}
}
func RemoveOutboundEdges(nodes []graph.Node) EdgeFunc {
return func(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
for _, node := range nodes {
if node == from {
return false
}
}
return true
}
}
// EdgeSubgraph returns the directed subgraph with only the edges that match the
// provided function.
func (g Graph) EdgeSubgraph(edgeFn EdgeFunc) Graph {
out := New()
for _, node := range g.Nodes() {
out.internal.AddNode(node)
}
out.addEdges(g.internal.Edges(), edgeFn)
return out
}
// Subgraph returns the directed subgraph with only the nodes and edges that match the
// provided functions.
func (g Graph) Subgraph(nodeFn NodeFunc, edgeFn EdgeFunc) Graph {
out := New()
for _, node := range g.Nodes() {
if nodeFn(out, node) {
out.internal.AddNode(node)
}
}
out.addEdges(g.internal.Edges(), edgeFn)
return out
}
// SubgraphWithNodes returns the directed subgraph with only the listed nodes and edges that
// match the provided function.
func (g Graph) SubgraphWithNodes(nodes []graph.Node, fn EdgeFunc) Graph {
out := New()
for _, node := range nodes {
out.internal.AddNode(node)
}
out.addEdges(g.internal.Edges(), fn)
return out
}
// ConnectedEdgeSubgraph creates a new graph that iterates through all edges in the graph
// and includes all edges the provided function returns true for. Nodes not referenced by
// an edge will be dropped unless the function adds them explicitly.
func (g Graph) ConnectedEdgeSubgraph(fn EdgeFunc) Graph {
out := New()
out.addEdges(g.internal.Edges(), fn)
return out
}
// AllNodes includes all nodes in the graph
func AllNodes(g Interface, node graph.Node) bool {
return true
}
// ExistingDirectEdge returns true if both from and to already exist in the graph and the edge kind is
// not ReferencedByEdgeKind (the generic reverse edge kind). This will purge the graph of any
// edges created by AddReversedEdge.
func ExistingDirectEdge(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
return !edgeKinds.Has(ReferencedByEdgeKind) && g.Has(from) && g.Has(to)
}
// ReverseExistingDirectEdge reverses the order of the edge and drops the existing edge only if
// both from and to already exist in the graph and the edge kind is not ReferencedByEdgeKind
// (the generic reverse edge kind).
func ReverseExistingDirectEdge(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
return ExistingDirectEdge(g, from, to, edgeKinds) && ReverseGraphEdge(g, from, to, edgeKinds)
}
// ReverseGraphEdge reverses the order of the edge and drops the existing edge.
func ReverseGraphEdge(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
for edgeKind := range edgeKinds {
g.AddEdge(to, from, edgeKind)
}
return false
}
// AddReversedEdge adds a reversed edge for every passed edge and preserves the existing
// edge. Used to convert a one directional edge into a bidirectional edge, but will
// create duplicate edges if a bidirectional edge between two nodes already exists.
func AddReversedEdge(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
g.AddEdge(to, from, ReferencedByEdgeKind)
return true
}
// AddGraphEdgesTo returns an EdgeFunc that will add the selected edges to the passed
// graph.
func AddGraphEdgesTo(g Interface) EdgeFunc {
return func(_ Interface, from, to graph.Node, edgeKinds sets.String) bool {
for edgeKind := range edgeKinds {
g.AddEdge(from, to, edgeKind)
}
return false
}
}
type uniqueNamedGraph struct {
graph.Mutable
names map[UniqueName]graph.Node
}
func newUniqueNamedGraph(g graph.Mutable) uniqueNamedGraph {
return uniqueNamedGraph{
Mutable: g,
names: make(map[UniqueName]graph.Node),
}
}
func (g uniqueNamedGraph) FindOrCreate(name UniqueName, fn NodeInitializerFunc) (graph.Node, bool) {
if node, ok := g.names[name]; ok {
return node, true
}
id := g.NewNodeID()
node := fn(Node{concrete.Node(id), name})
g.names[name] = node
g.AddNode(node)
return node, false
}
func (g uniqueNamedGraph) Find(name UniqueName) graph.Node {
if node, ok := g.names[name]; ok {
return node
}
return nil
}
type typedGraph struct{}
func (g typedGraph) Name(node graph.Node) string {
switch t := node.(type) {
case fmt.Stringer:
return t.String()
case uniqueNamer:
return t.UniqueName().String()
default:
return fmt.Sprintf("<unknown:%d>", node.ID())
}
}
type objectifier interface {
Object() interface{}
}
func (g typedGraph) Object(node graph.Node) interface{} {
switch t := node.(type) {
case objectifier:
return t.Object()
default:
return nil
}
}
type kind interface {
Kind() string
}
func (g typedGraph) Kind(node graph.Node) string {
if k, ok := node.(kind); ok {
return k.Kind()
}
return UnknownNodeKind
}
func (g typedGraph) EdgeKinds(edge graph.Edge) sets.String {
var e Edge
switch t := edge.(type) {
case concrete.WeightedEdge:
e = t.Edge.(Edge)
case Edge:
e = t
default:
return sets.NewString(UnknownEdgeKind)
}
return e.Kinds()
}
type NodeSet map[int]struct{}
func (n NodeSet) Has(id int) bool {
_, ok := n[id]
return ok
}
func (n NodeSet) Add(id int) {
n[id] = struct{}{}
}
func NodesByKind(g Interface, nodes []graph.Node, kinds ...string) [][]graph.Node {
buckets := make(map[string]int)
for i, kind := range kinds {
buckets[kind] = i
}
if nodes == nil {
nodes = g.Nodes()
}
last := len(kinds)
result := make([][]graph.Node, last+1)
for _, node := range nodes {
if bucket, ok := buckets[g.Kind(node)]; ok {
result[bucket] = append(result[bucket], node)
} else {
result[last] = append(result[last], node)
}
}
return result
}
// IsFromDifferentNamespace returns if a node is in a different namespace
// than the one provided.
func IsFromDifferentNamespace(namespace string, node graph.Node) bool {
potentiallySyntheticNode, ok := node.(ExistenceChecker)
if !ok || potentiallySyntheticNode.Found() {
return false
}
objectified, ok := node.(objectifier)
if !ok {
return false
}
object, err := meta.Accessor(objectified)
if err != nil {
return false
}
return object.GetNamespace() != namespace
}
func pathCovered(path []graph.Node, paths map[int][]graph.Node) bool {
l := len(path)
for _, existing := range paths {
if l >= len(existing) {
continue
}
if pathEqual(path, existing) {
return true
}
}
return false
}
func pathEqual(a, b []graph.Node) bool {
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
@@ -1,84 +0,0 @@
package graphview
import (
"sort"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
deployedges "github.com/openshift/origin/pkg/deploy/graph"
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
)
type DeploymentConfigPipeline struct {
Deployment *deploygraph.DeploymentConfigNode
ActiveDeployment *kubegraph.ReplicationControllerNode
InactiveDeployments []*kubegraph.ReplicationControllerNode
Images []ImagePipeline
}
// AllDeploymentConfigPipelines returns all the DCPipelines that aren't in the excludes set and the set of covered NodeIDs
func AllDeploymentConfigPipelines(g osgraph.Graph, excludeNodeIDs IntSet) ([]DeploymentConfigPipeline, IntSet) {
covered := IntSet{}
dcPipelines := []DeploymentConfigPipeline{}
for _, uncastNode := range g.NodesByKind(deploygraph.DeploymentConfigNodeKind) {
if excludeNodeIDs.Has(uncastNode.ID()) {
continue
}
pipeline, covers := NewDeploymentConfigPipeline(g, uncastNode.(*deploygraph.DeploymentConfigNode))
covered.Insert(covers.List()...)
dcPipelines = append(dcPipelines, pipeline)
}
sort.Sort(SortedDeploymentConfigPipeline(dcPipelines))
return dcPipelines, covered
}
// NewDeploymentConfigPipeline returns the DeploymentConfigPipeline and a set of all the NodeIDs covered by the DeploymentConfigPipeline
func NewDeploymentConfigPipeline(g osgraph.Graph, dcNode *deploygraph.DeploymentConfigNode) (DeploymentConfigPipeline, IntSet) {
covered := IntSet{}
covered.Insert(dcNode.ID())
dcPipeline := DeploymentConfigPipeline{}
dcPipeline.Deployment = dcNode
// for everything that can trigger a deployment, create an image pipeline and add it to the list
for _, istNode := range g.PredecessorNodesByEdgeKind(dcNode, deployedges.TriggersDeploymentEdgeKind) {
imagePipeline, covers := NewImagePipelineFromImageTagLocation(g, istNode, istNode.(ImageTagLocation))
covered.Insert(covers.List()...)
dcPipeline.Images = append(dcPipeline.Images, imagePipeline)
}
// for image that we use, create an image pipeline and add it to the list
for _, tagNode := range g.PredecessorNodesByEdgeKind(dcNode, deployedges.UsedInDeploymentEdgeKind) {
imagePipeline, covers := NewImagePipelineFromImageTagLocation(g, tagNode, tagNode.(ImageTagLocation))
covered.Insert(covers.List()...)
dcPipeline.Images = append(dcPipeline.Images, imagePipeline)
}
dcPipeline.ActiveDeployment, dcPipeline.InactiveDeployments = deployedges.RelevantDeployments(g, dcNode)
for _, rc := range dcPipeline.InactiveDeployments {
_, covers := NewReplicationController(g, rc)
covered.Insert(covers.List()...)
}
if dcPipeline.ActiveDeployment != nil {
_, covers := NewReplicationController(g, dcPipeline.ActiveDeployment)
covered.Insert(covers.List()...)
}
return dcPipeline, covered
}
type SortedDeploymentConfigPipeline []DeploymentConfigPipeline
func (m SortedDeploymentConfigPipeline) Len() int { return len(m) }
func (m SortedDeploymentConfigPipeline) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m SortedDeploymentConfigPipeline) Less(i, j int) bool {
return CompareObjectMeta(&m[i].Deployment.DeploymentConfig.ObjectMeta, &m[j].Deployment.DeploymentConfig.ObjectMeta)
}
@@ -1,240 +0,0 @@
package graphview
import (
"sort"
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildedges "github.com/openshift/origin/pkg/build/graph"
buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
imageedges "github.com/openshift/origin/pkg/image/graph"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)
// ImagePipeline represents a build, its output, and any inputs. The input
// to a build may be another ImagePipeline.
type ImagePipeline struct {
Image ImageTagLocation
DestinationResolved bool
ScheduledImport bool
Build *buildgraph.BuildConfigNode
LastSuccessfulBuild *buildgraph.BuildNode
LastUnsuccessfulBuild *buildgraph.BuildNode
ActiveBuilds []*buildgraph.BuildNode
// If set, the base image used by the build
BaseImage ImageTagLocation
// if set, the build config names that produces the base image
BaseBuilds []string
// If set, the source repository that inputs to the build
Source SourceLocation
}
// ImageTagLocation identifies the source or destination of an image. Represents
// both a tag in a Docker image repository, as well as a tag in an OpenShift image stream.
type ImageTagLocation interface {
ID() int
ImageSpec() string
ImageTag() string
}
// SourceLocation identifies a repository that is an input to a build.
type SourceLocation interface {
ID() int
}
func AllImagePipelinesFromBuildConfig(g osgraph.Graph, excludeNodeIDs IntSet) ([]ImagePipeline, IntSet) {
covered := IntSet{}
pipelines := []ImagePipeline{}
for _, uncastNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
if excludeNodeIDs.Has(uncastNode.ID()) {
continue
}
pipeline, covers := NewImagePipelineFromBuildConfigNode(g, uncastNode.(*buildgraph.BuildConfigNode))
covered.Insert(covers.List()...)
pipelines = append(pipelines, pipeline)
}
sort.Sort(SortedImagePipelines(pipelines))
outputImageToBCMap := make(map[string][]string)
for _, pipeline := range pipelines {
// note, bc does not have to have an output image
if pipeline.Image != nil {
bcs, ok := outputImageToBCMap[pipeline.Image.ImageSpec()]
if !ok {
bcs = []string{}
}
bcs = append(bcs, pipeline.Build.BuildConfig.Name)
outputImageToBCMap[pipeline.Image.ImageSpec()] = bcs
}
}
if len(outputImageToBCMap) > 0 {
for i, pipeline := range pipelines {
// note, bc does not have to have an input strategy image
if pipeline.BaseImage != nil {
baseBCs, ok := outputImageToBCMap[pipeline.BaseImage.ImageSpec()]
if ok && len(baseBCs) > 0 {
pipelines[i].BaseBuilds = baseBCs
}
}
}
}
return pipelines, covered
}
// NewImagePipeline attempts to locate a build flow from the provided node. If no such
// build flow can be located, false is returned.
func NewImagePipelineFromBuildConfigNode(g osgraph.Graph, bcNode *buildgraph.BuildConfigNode) (ImagePipeline, IntSet) {
covered := IntSet{}
covered.Insert(bcNode.ID())
flow := ImagePipeline{}
base, src, coveredInputs, scheduled, _ := findBuildInputs(g, bcNode)
covered.Insert(coveredInputs.List()...)
flow.BaseImage = base
flow.Source = src
flow.Build = bcNode
flow.ScheduledImport = scheduled
flow.LastSuccessfulBuild, flow.LastUnsuccessfulBuild, flow.ActiveBuilds = buildedges.RelevantBuilds(g, flow.Build)
flow.Image = findBuildOutput(g, bcNode)
// we should have at most one
for _, buildOutputNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
// this will handle the imagestream tag case
for _, input := range g.SuccessorNodesByEdgeKind(buildOutputNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
// this will handle the imagestream image case
for _, input := range g.SuccessorNodesByEdgeKind(buildOutputNode, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
// TODO handle the DockerImage case
}
return flow, covered
}
// NewImagePipelineFromImageTagLocation returns the ImagePipeline and all the nodes contributing to it
func NewImagePipelineFromImageTagLocation(g osgraph.Graph, node graph.Node, imageTagLocation ImageTagLocation) (ImagePipeline, IntSet) {
covered := IntSet{}
covered.Insert(node.ID())
flow := ImagePipeline{}
flow.Image = imageTagLocation
for _, input := range g.PredecessorNodesByEdgeKind(node, buildedges.BuildOutputEdgeKind) {
covered.Insert(input.ID())
build := input.(*buildgraph.BuildConfigNode)
if flow.Build != nil {
// report this as an error (unexpected duplicate input build)
}
if build.BuildConfig == nil {
// report this as as a missing build / broken link
break
}
base, src, coveredInputs, scheduled, _ := findBuildInputs(g, build)
covered.Insert(coveredInputs.List()...)
flow.BaseImage = base
flow.Source = src
flow.Build = build
flow.ScheduledImport = scheduled
flow.LastSuccessfulBuild, flow.LastUnsuccessfulBuild, flow.ActiveBuilds = buildedges.RelevantBuilds(g, flow.Build)
}
for _, input := range g.SuccessorNodesByEdgeKind(node, imageedges.ReferencedImageStreamGraphEdgeKind) {
covered.Insert(input.ID())
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
for _, input := range g.SuccessorNodesByEdgeKind(node, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
covered.Insert(input.ID())
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
return flow, covered
}
func findBuildInputs(g osgraph.Graph, bcNode *buildgraph.BuildConfigNode) (base ImageTagLocation, source SourceLocation, covered IntSet, scheduled bool, err error) {
covered = IntSet{}
// find inputs to the build
for _, input := range g.PredecessorNodesByEdgeKind(bcNode, buildedges.BuildInputEdgeKind) {
if source != nil {
// report this as an error (unexpected duplicate source)
}
covered.Insert(input.ID())
source = input.(SourceLocation)
}
for _, input := range g.PredecessorNodesByEdgeKind(bcNode, buildedges.BuildInputImageEdgeKind) {
if base != nil {
// report this as an error (unexpected duplicate input build)
}
covered.Insert(input.ID())
base = input.(ImageTagLocation)
scheduled = imageStreamTagScheduled(g, input, base)
}
return
}
func findBuildOutput(g osgraph.Graph, bcNode *buildgraph.BuildConfigNode) (result ImageTagLocation) {
for _, output := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
result = output.(ImageTagLocation)
return
}
return
}
func imageStreamTagScheduled(g osgraph.Graph, input graph.Node, base ImageTagLocation) (scheduled bool) {
for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(input, imageedges.ReferencedImageStreamGraphEdgeKind) {
imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
if imageStreamNode.ImageStream != nil {
if tag, ok := imageStreamNode.ImageStream.Spec.Tags[base.ImageTag()]; ok {
scheduled = tag.ImportPolicy.Scheduled
return
}
}
}
return
}
type SortedImagePipelines []ImagePipeline
func (m SortedImagePipelines) Len() int { return len(m) }
func (m SortedImagePipelines) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m SortedImagePipelines) Less(i, j int) bool {
return CompareImagePipeline(&m[i], &m[j])
}
func CompareImagePipeline(a, b *ImagePipeline) bool {
switch {
case a.Build != nil && b.Build != nil && a.Build.BuildConfig != nil && b.Build.BuildConfig != nil:
return CompareObjectMeta(&a.Build.BuildConfig.ObjectMeta, &b.Build.BuildConfig.ObjectMeta)
case a.Build != nil && a.Build.BuildConfig != nil:
return true
case b.Build != nil && b.Build.BuildConfig != nil:
return false
}
if a.Image == nil || b.Image == nil {
return true
}
return a.Image.ImageSpec() < b.Image.ImageSpec()
}
-46
View File
@@ -1,46 +0,0 @@
package graphview
import (
"sort"
"k8s.io/kubernetes/pkg/util/sets"
)
type IntSet map[int]sets.Empty
// NewIntSet creates a IntSet from a list of values.
func NewIntSet(items ...int) IntSet {
ss := IntSet{}
ss.Insert(items...)
return ss
}
// Insert adds items to the set.
func (s IntSet) Insert(items ...int) {
for _, item := range items {
s[item] = sets.Empty{}
}
}
// Delete removes all items from the set.
func (s IntSet) Delete(items ...int) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true iff item is contained in the set.
func (s IntSet) Has(item int) bool {
_, contained := s[item]
return contained
}
// List returns the contents as a sorted string slice.
func (s IntSet) List() []int {
res := make([]int, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.IntSlice(res).Sort()
return res
}
-51
View File
@@ -1,51 +0,0 @@
package graphview
import (
osgraph "github.com/openshift/origin/pkg/api/graph"
kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
)
type PetSet struct {
PetSet *kubegraph.PetSetNode
OwnedPods []*kubegraph.PodNode
CreatedPods []*kubegraph.PodNode
// TODO: handle conflicting once controller refs are present, not worth it yet
}
// AllPetSets returns all the PetSets that aren't in the excludes set and the set of covered NodeIDs
func AllPetSets(g osgraph.Graph, excludeNodeIDs IntSet) ([]PetSet, IntSet) {
covered := IntSet{}
views := []PetSet{}
for _, uncastNode := range g.NodesByKind(kubegraph.PetSetNodeKind) {
if excludeNodeIDs.Has(uncastNode.ID()) {
continue
}
view, covers := NewPetSet(g, uncastNode.(*kubegraph.PetSetNode))
covered.Insert(covers.List()...)
views = append(views, view)
}
return views, covered
}
// NewPetSet returns the PetSet and a set of all the NodeIDs covered by the PetSet
func NewPetSet(g osgraph.Graph, node *kubegraph.PetSetNode) (PetSet, IntSet) {
covered := IntSet{}
covered.Insert(node.ID())
view := PetSet{}
view.PetSet = node
for _, uncastPodNode := range g.PredecessorNodesByEdgeKind(node, kubeedges.ManagedByControllerEdgeKind) {
podNode := uncastPodNode.(*kubegraph.PodNode)
covered.Insert(podNode.ID())
view.OwnedPods = append(view.OwnedPods, podNode)
}
return view, covered
}
-39
View File
@@ -1,39 +0,0 @@
package graphview
import (
osgraph "github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
)
type Pod struct {
Pod *kubegraph.PodNode
}
// AllPods returns all Pods and the set of covered NodeIDs
func AllPods(g osgraph.Graph, excludeNodeIDs IntSet) ([]Pod, IntSet) {
covered := IntSet{}
pods := []Pod{}
for _, uncastNode := range g.NodesByKind(kubegraph.PodNodeKind) {
if excludeNodeIDs.Has(uncastNode.ID()) {
continue
}
pod, covers := NewPod(g, uncastNode.(*kubegraph.PodNode))
covered.Insert(covers.List()...)
pods = append(pods, pod)
}
return pods, covered
}
// NewPod returns the Pod and a set of all the NodeIDs covered by the Pod
func NewPod(g osgraph.Graph, podNode *kubegraph.PodNode) (Pod, IntSet) {
covered := IntSet{}
covered.Insert(podNode.ID())
podView := Pod{}
podView.Pod = podNode
return podView, covered
}
-100
View File
@@ -1,100 +0,0 @@
package graphview
import (
"k8s.io/kubernetes/pkg/api/unversioned"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
"github.com/openshift/origin/pkg/api/kubegraph/analysis"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
)
type ReplicationController struct {
RC *kubegraph.ReplicationControllerNode
OwnedPods []*kubegraph.PodNode
CreatedPods []*kubegraph.PodNode
ConflictingRCs []*kubegraph.ReplicationControllerNode
ConflictingRCIDToPods map[int][]*kubegraph.PodNode
}
// AllReplicationControllers returns all the ReplicationControllers that aren't in the excludes set and the set of covered NodeIDs
func AllReplicationControllers(g osgraph.Graph, excludeNodeIDs IntSet) ([]ReplicationController, IntSet) {
covered := IntSet{}
rcViews := []ReplicationController{}
for _, uncastNode := range g.NodesByKind(kubegraph.ReplicationControllerNodeKind) {
if excludeNodeIDs.Has(uncastNode.ID()) {
continue
}
rcView, covers := NewReplicationController(g, uncastNode.(*kubegraph.ReplicationControllerNode))
covered.Insert(covers.List()...)
rcViews = append(rcViews, rcView)
}
return rcViews, covered
}
// MaxRecentContainerRestarts returns the maximum container restarts for all pods in
// replication controller.
func (rc *ReplicationController) MaxRecentContainerRestarts() int32 {
var maxRestarts int32
for _, pod := range rc.OwnedPods {
for _, status := range pod.Status.ContainerStatuses {
if status.RestartCount > maxRestarts && analysis.ContainerRestartedRecently(status, unversioned.Now()) {
maxRestarts = status.RestartCount
}
}
}
return maxRestarts
}
// NewReplicationController returns the ReplicationController and a set of all the NodeIDs covered by the ReplicationController
func NewReplicationController(g osgraph.Graph, rcNode *kubegraph.ReplicationControllerNode) (ReplicationController, IntSet) {
covered := IntSet{}
covered.Insert(rcNode.ID())
rcView := ReplicationController{}
rcView.RC = rcNode
rcView.ConflictingRCIDToPods = map[int][]*kubegraph.PodNode{}
for _, uncastPodNode := range g.PredecessorNodesByEdgeKind(rcNode, kubeedges.ManagedByControllerEdgeKind) {
podNode := uncastPodNode.(*kubegraph.PodNode)
covered.Insert(podNode.ID())
rcView.OwnedPods = append(rcView.OwnedPods, podNode)
// check to see if this pod is managed by more than one RC
uncastOwningRCs := g.SuccessorNodesByEdgeKind(podNode, kubeedges.ManagedByControllerEdgeKind)
if len(uncastOwningRCs) > 1 {
for _, uncastOwningRC := range uncastOwningRCs {
if uncastOwningRC.ID() == rcNode.ID() {
continue
}
conflictingRC := uncastOwningRC.(*kubegraph.ReplicationControllerNode)
rcView.ConflictingRCs = append(rcView.ConflictingRCs, conflictingRC)
conflictingPods, ok := rcView.ConflictingRCIDToPods[conflictingRC.ID()]
if !ok {
conflictingPods = []*kubegraph.PodNode{}
}
conflictingPods = append(conflictingPods, podNode)
rcView.ConflictingRCIDToPods[conflictingRC.ID()] = conflictingPods
}
}
}
return rcView, covered
}
// MaxRecentContainerRestartsForRC returns the maximum container restarts in pods
// in the replication controller node for the last 10 minutes.
func MaxRecentContainerRestartsForRC(g osgraph.Graph, rcNode *kubegraph.ReplicationControllerNode) int32 {
if rcNode == nil {
return 0
}
rc, _ := NewReplicationController(g, rcNode)
return rc.MaxRecentContainerRestarts()
}
@@ -1,134 +0,0 @@
package graphview
import (
"fmt"
"sort"
kapi "k8s.io/kubernetes/pkg/api"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
routeedges "github.com/openshift/origin/pkg/route/graph"
routegraph "github.com/openshift/origin/pkg/route/graph/nodes"
)
// ServiceGroup is a service, the DeploymentConfigPipelines it covers, and lists of the other nodes that fulfill it
type ServiceGroup struct {
Service *kubegraph.ServiceNode
DeploymentConfigPipelines []DeploymentConfigPipeline
ReplicationControllers []ReplicationController
PetSets []PetSet
// TODO: this has to stop
FulfillingPetSets []*kubegraph.PetSetNode
FulfillingDCs []*deploygraph.DeploymentConfigNode
FulfillingRCs []*kubegraph.ReplicationControllerNode
FulfillingPods []*kubegraph.PodNode
ExposingRoutes []*routegraph.RouteNode
}
// AllServiceGroups returns all the ServiceGroups that aren't in the excludes set and the set of covered NodeIDs
func AllServiceGroups(g osgraph.Graph, excludeNodeIDs IntSet) ([]ServiceGroup, IntSet) {
covered := IntSet{}
services := []ServiceGroup{}
for _, uncastNode := range g.NodesByKind(kubegraph.ServiceNodeKind) {
if excludeNodeIDs.Has(uncastNode.ID()) {
continue
}
service, covers := NewServiceGroup(g, uncastNode.(*kubegraph.ServiceNode))
covered.Insert(covers.List()...)
services = append(services, service)
}
sort.Sort(ServiceGroupByObjectMeta(services))
return services, covered
}
// NewServiceGroup returns the ServiceGroup and a set of all the NodeIDs covered by the service
func NewServiceGroup(g osgraph.Graph, serviceNode *kubegraph.ServiceNode) (ServiceGroup, IntSet) {
covered := IntSet{}
covered.Insert(serviceNode.ID())
service := ServiceGroup{}
service.Service = serviceNode
for _, uncastServiceFulfiller := range g.PredecessorNodesByEdgeKind(serviceNode, kubeedges.ExposedThroughServiceEdgeKind) {
container := osgraph.GetTopLevelContainerNode(g, uncastServiceFulfiller)
switch castContainer := container.(type) {
case *deploygraph.DeploymentConfigNode:
service.FulfillingDCs = append(service.FulfillingDCs, castContainer)
case *kubegraph.ReplicationControllerNode:
service.FulfillingRCs = append(service.FulfillingRCs, castContainer)
case *kubegraph.PodNode:
service.FulfillingPods = append(service.FulfillingPods, castContainer)
case *kubegraph.PetSetNode:
service.FulfillingPetSets = append(service.FulfillingPetSets, castContainer)
default:
utilruntime.HandleError(fmt.Errorf("unrecognized container: %v", castContainer))
}
}
for _, uncastServiceFulfiller := range g.PredecessorNodesByEdgeKind(serviceNode, routeedges.ExposedThroughRouteEdgeKind) {
container := osgraph.GetTopLevelContainerNode(g, uncastServiceFulfiller)
switch castContainer := container.(type) {
case *routegraph.RouteNode:
service.ExposingRoutes = append(service.ExposingRoutes, castContainer)
default:
utilruntime.HandleError(fmt.Errorf("unrecognized container: %v", castContainer))
}
}
// add the DCPipelines for all the DCs that fulfill the service
for _, fulfillingDC := range service.FulfillingDCs {
dcPipeline, dcCovers := NewDeploymentConfigPipeline(g, fulfillingDC)
covered.Insert(dcCovers.List()...)
service.DeploymentConfigPipelines = append(service.DeploymentConfigPipelines, dcPipeline)
}
for _, fulfillingRC := range service.FulfillingRCs {
rcView, rcCovers := NewReplicationController(g, fulfillingRC)
covered.Insert(rcCovers.List()...)
service.ReplicationControllers = append(service.ReplicationControllers, rcView)
}
for _, fulfillingPetSet := range service.FulfillingPetSets {
view, covers := NewPetSet(g, fulfillingPetSet)
covered.Insert(covers.List()...)
service.PetSets = append(service.PetSets, view)
}
for _, fulfillingPod := range service.FulfillingPods {
_, podCovers := NewPod(g, fulfillingPod)
covered.Insert(podCovers.List()...)
}
return service, covered
}
type ServiceGroupByObjectMeta []ServiceGroup
func (m ServiceGroupByObjectMeta) Len() int { return len(m) }
func (m ServiceGroupByObjectMeta) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m ServiceGroupByObjectMeta) Less(i, j int) bool {
a, b := m[i], m[j]
return CompareObjectMeta(&a.Service.Service.ObjectMeta, &b.Service.Service.ObjectMeta)
}
func CompareObjectMeta(a, b *kapi.ObjectMeta) bool {
if a.Namespace == b.Namespace {
return a.Name < b.Name
}
return a.Namespace < b.Namespace
}
-134
View File
@@ -1,134 +0,0 @@
package graph
import (
"github.com/gonum/graph"
)
// Marker is a struct that describes something interesting on a Node
type Marker struct {
// Node is the optional node that this message is attached to
Node graph.Node
// RelatedNodes is an optional list of other nodes that are involved in this marker.
RelatedNodes []graph.Node
// Severity indicates how important this problem is.
Severity Severity
// Key is a short string to identify this message
Key string
// Message is a human-readable string that describes what is interesting
Message string
// Suggestion is a human-readable string that holds advice for resolving this
// marker.
Suggestion Suggestion
}
// Severity indicates how important this problem is.
type Severity string
const (
// InfoSeverity is interesting
// TODO: Consider what to do with this once we revisit the graph api - currently not used.
InfoSeverity Severity = "info"
// WarningSeverity is probably wrong, but we aren't certain
WarningSeverity Severity = "warning"
// ErrorSeverity is definitely wrong, this won't work
ErrorSeverity Severity = "error"
)
type Markers []Marker
// MarkerScanner is a function for analyzing a graph and finding interesting things in it
type MarkerScanner func(g Graph, f Namer) []Marker
func (m Markers) BySeverity(severity Severity) []Marker {
ret := []Marker{}
for i := range m {
if m[i].Severity == severity {
ret = append(ret, m[i])
}
}
return ret
}
// FilterByNamespace returns all the markers that are not associated with missing nodes
// from other namespaces (other than the provided namespace).
func (m Markers) FilterByNamespace(namespace string) Markers {
filtered := Markers{}
for i := range m {
markerNodes := []graph.Node{}
markerNodes = append(markerNodes, m[i].Node)
markerNodes = append(markerNodes, m[i].RelatedNodes...)
hasCrossNamespaceLink := false
for _, node := range markerNodes {
if IsFromDifferentNamespace(namespace, node) {
hasCrossNamespaceLink = true
break
}
}
if !hasCrossNamespaceLink {
filtered = append(filtered, m[i])
}
}
return filtered
}
type BySeverity []Marker
func (m BySeverity) Len() int { return len(m) }
func (m BySeverity) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m BySeverity) Less(i, j int) bool {
lhs := m[i]
rhs := m[j]
switch lhs.Severity {
case ErrorSeverity:
switch rhs.Severity {
case ErrorSeverity:
return false
}
case WarningSeverity:
switch rhs.Severity {
case ErrorSeverity, WarningSeverity:
return false
}
case InfoSeverity:
switch rhs.Severity {
case ErrorSeverity, WarningSeverity, InfoSeverity:
return false
}
}
return true
}
type ByNodeID []Marker
func (m ByNodeID) Len() int { return len(m) }
func (m ByNodeID) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m ByNodeID) Less(i, j int) bool {
if m[i].Node == nil {
return true
}
if m[j].Node == nil {
return false
}
return m[i].Node.ID() < m[j].Node.ID()
}
type ByKey []Marker
func (m ByKey) Len() int { return len(m) }
func (m ByKey) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m ByKey) Less(i, j int) bool {
return m[i].Key < m[j].Key
}
type Suggestion string
func (s Suggestion) String() string {
return string(s)
}
-69
View File
@@ -1,69 +0,0 @@
package graph
import (
"fmt"
"github.com/gonum/graph"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/runtime"
)
const (
UnknownNodeKind = "UnknownNode"
)
const (
UnknownEdgeKind = "UnknownEdge"
// ReferencedByEdgeKind is the kind to use if you're building reverse links that don't have a specific edge in the other direction
// other uses are discouraged. You should create a kind for your edge
ReferencedByEdgeKind = "ReferencedBy"
// ContainsEdgeKind is the kind to use if one node's contents logically contain another node's contents. A given node can only have
// a single inbound Contais edge. The code does not prevent contains cycles, but that's insane, don't do that.
ContainsEdgeKind = "Contains"
)
func GetUniqueRuntimeObjectNodeName(nodeKind string, obj runtime.Object) UniqueName {
meta, err := kapi.ObjectMetaFor(obj)
if err != nil {
panic(err)
}
return UniqueName(fmt.Sprintf("%s|%s/%s", nodeKind, meta.Namespace, meta.Name))
}
// GetTopLevelContainerNode traverses the reverse ContainsEdgeKind edges until it finds a node
// that does not have an inbound ContainsEdgeKind edge. This could be the node itself
func GetTopLevelContainerNode(g Graph, containedNode graph.Node) graph.Node {
// my kingdom for a LinkedHashSet
visited := map[int]bool{}
prevContainingNode := containedNode
for {
visited[prevContainingNode.ID()] = true
currContainingNode := GetContainingNode(g, prevContainingNode)
if currContainingNode == nil {
return prevContainingNode
}
if _, alreadyVisited := visited[currContainingNode.ID()]; alreadyVisited {
panic(fmt.Sprintf("contains cycle in %v", visited))
}
prevContainingNode = currContainingNode
}
}
// GetContainingNode returns the direct predecessor that is linked to the node by a ContainsEdgeKind. It returns
// nil if no container is found.
func GetContainingNode(g Graph, containedNode graph.Node) graph.Node {
for _, node := range g.To(containedNode) {
edge := g.Edge(node, containedNode)
if g.EdgeKinds(edge).Has(ContainsEdgeKind) {
return node
}
}
return nil
}
-180
View File
@@ -1,180 +0,0 @@
package analysis
import (
"fmt"
"strings"
"k8s.io/kubernetes/pkg/util/sets"
graphapi "github.com/gonum/graph"
"github.com/gonum/graph/path"
osgraph "github.com/openshift/origin/pkg/api/graph"
"github.com/openshift/origin/pkg/api/kubegraph"
kubenodes "github.com/openshift/origin/pkg/api/kubegraph/nodes"
deploygraph "github.com/openshift/origin/pkg/deploy/graph"
deploynodes "github.com/openshift/origin/pkg/deploy/graph/nodes"
)
const (
// HPAMissingScaleRefError denotes an error where a Horizontal Pod Autoscaler does not have a reference to an object to scale
HPAMissingScaleRefError = "HPAMissingScaleRef"
// HPAMissingCPUTargetError denotes an error where a Horizontal Pod Autoscaler does not have a CPU target to scale by.
// Currently, the only supported scale metric is CPU utilization, so without this metric an HPA is useless.
HPAMissingCPUTargetError = "HPAMissingCPUTarget"
// HPAOverlappingScaleRefWarning denotes a warning where a Horizontal Pod Autoscaler scales an object that is scaled by some other object as well
HPAOverlappingScaleRefWarning = "HPAOverlappingScaleRef"
)
// FindHPASpecsMissingCPUTargets scans the graph in search of HorizontalPodAutoscalers that are missing a CPU utilization target.
// As of right now, the only metric that HPAs can use to scale pods is the CPU utilization, so if a HPA is missing this target it
// is effectively useless.
func FindHPASpecsMissingCPUTargets(graph osgraph.Graph, namer osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastNode := range graph.NodesByKind(kubenodes.HorizontalPodAutoscalerNodeKind) {
node := uncastNode.(*kubenodes.HorizontalPodAutoscalerNode)
if node.HorizontalPodAutoscaler.Spec.TargetCPUUtilizationPercentage == nil {
markers = append(markers, osgraph.Marker{
Node: node,
Severity: osgraph.ErrorSeverity,
Key: HPAMissingCPUTargetError,
Message: fmt.Sprintf("%s is missing a CPU utilization target", namer.ResourceName(node)),
Suggestion: osgraph.Suggestion(fmt.Sprintf(`oc patch %s -p '{"spec":{"targetCPUUtilizationPercentage": 80}}'`, namer.ResourceName(node))),
})
}
}
return markers
}
// FindHPASpecsMissingScaleRefs finds all Horizontal Pod Autoscalers whose scale reference points to an object that doesn't exist
// or that the client does not have the permission to see.
func FindHPASpecsMissingScaleRefs(graph osgraph.Graph, namer osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastNode := range graph.NodesByKind(kubenodes.HorizontalPodAutoscalerNodeKind) {
node := uncastNode.(*kubenodes.HorizontalPodAutoscalerNode)
scaledObjects := graph.SuccessorNodesByEdgeKind(
uncastNode,
kubegraph.ScalingEdgeKind,
)
if len(scaledObjects) < 1 {
markers = append(markers, createMissingScaleRefMarker(node, nil, namer))
continue
}
for _, scaleRef := range scaledObjects {
if existenceChecker, ok := scaleRef.(osgraph.ExistenceChecker); ok && !existenceChecker.Found() {
// if this node is synthetic, we can't be sure that the HPA is scaling something that actually exists
markers = append(markers, createMissingScaleRefMarker(node, scaleRef, namer))
}
}
}
return markers
}
func createMissingScaleRefMarker(hpaNode *kubenodes.HorizontalPodAutoscalerNode, scaleRef graphapi.Node, namer osgraph.Namer) osgraph.Marker {
return osgraph.Marker{
Node: hpaNode,
Severity: osgraph.ErrorSeverity,
RelatedNodes: []graphapi.Node{scaleRef},
Key: HPAMissingScaleRefError,
Message: fmt.Sprintf("%s is attempting to scale %s/%s, which doesn't exist",
namer.ResourceName(hpaNode),
hpaNode.HorizontalPodAutoscaler.Spec.ScaleTargetRef.Kind,
hpaNode.HorizontalPodAutoscaler.Spec.ScaleTargetRef.Name,
),
}
}
// FindOverlappingHPAs scans the graph in search of HorizontalPodAutoscalers that are attempting to scale the same set of pods.
// This can occur in two ways:
// - 1. label selectors for two ReplicationControllers/DeploymentConfigs/etc overlap
// - 2. multiple HorizontalPodAutoscalers are attempting to scale the same ReplicationController/DeploymentConfig/etc
// Case 1 is handled by deconflicting the area of influence of ReplicationControllers/DeploymentConfigs/etc, and therefore we
// can assume that it will be handled before this step. Therefore, we are only concerned with finding HPAs that are trying to
// scale the same resources.
//
// The algorithm that is used to implement this check is described as follows:
// - create a sub-graph containing only HPA nodes and other nodes that can be scaled, as well as any scaling edges or other
// edges used to connect between objects that can be scaled
// - for every resulting edge in the new sub-graph, create an edge in the reverse direction
// - find the shortest paths between all HPA nodes in the graph
// - shortest paths connecting two horizontal pod autoscalers are used to create markers for the graph
func FindOverlappingHPAs(graph osgraph.Graph, namer osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
nodeFilter := osgraph.NodesOfKind(
kubenodes.HorizontalPodAutoscalerNodeKind,
kubenodes.ReplicationControllerNodeKind,
deploynodes.DeploymentConfigNodeKind,
)
edgeFilter := osgraph.EdgesOfKind(
kubegraph.ScalingEdgeKind,
deploygraph.DeploymentEdgeKind,
)
hpaSubGraph := graph.Subgraph(nodeFilter, edgeFilter)
for _, edge := range hpaSubGraph.Edges() {
osgraph.AddReversedEdge(hpaSubGraph, edge.From(), edge.To(), sets.NewString())
}
hpaNodes := hpaSubGraph.NodesByKind(kubenodes.HorizontalPodAutoscalerNodeKind)
for _, firstHPA := range hpaNodes {
// we can use Dijkstra's algorithm as we know we do not have any negative edge weights
shortestPaths := path.DijkstraFrom(firstHPA, hpaSubGraph)
for _, secondHPA := range hpaNodes {
if firstHPA == secondHPA {
continue
}
shortestPath, _ := shortestPaths.To(secondHPA)
if shortestPath == nil {
// if two HPAs have no path between them, no error exists
continue
}
markers = append(markers, osgraph.Marker{
Node: firstHPA,
Severity: osgraph.WarningSeverity,
RelatedNodes: shortestPath[1:],
Key: HPAOverlappingScaleRefWarning,
Message: fmt.Sprintf("%s and %s overlap because they both attempt to scale %s",
namer.ResourceName(firstHPA), namer.ResourceName(secondHPA), nameList(shortestPath[1:len(shortestPath)-1], namer)),
})
}
}
return markers
}
// nameList outputs a nicely-formatted list of names:
// - given nodes ['a', 'b', 'c'], this will return "one of a, b, or c"
// - given nodes ['a', 'b'], this will return "a or b"
// - given nodes ['a'], this will return "a"
func nameList(nodes []graphapi.Node, namer osgraph.Namer) string {
names := []string{}
for _, node := range nodes {
names = append(names, namer.ResourceName(node))
}
switch len(names) {
case 0:
return ""
case 1:
return names[0]
case 2:
return names[0] + " or " + names[1]
default:
return "one of " + strings.Join(names[:len(names)-1], ", ") + ", or " + names[len(names)-1]
}
}
-135
View File
@@ -1,135 +0,0 @@
package analysis
import (
"fmt"
"time"
"github.com/MakeNowJust/heredoc"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
)
const (
CrashLoopingPodError = "CrashLoopingPod"
RestartingPodWarning = "RestartingPod"
RestartThreshold = 5
// TODO: if you change this, you must change the messages below.
RestartRecentDuration = 10 * time.Minute
)
// exposed for testing
var nowFn = unversioned.Now
// FindRestartingPods inspects all Pods to see if they've restarted more than the threshold. logsCommandName is the name of
// the command that should be invoked to see pod logs. securityPolicyCommandPattern is a format string accepting two replacement
// variables for fmt.Sprintf - 1, the namespace of the current pod, 2 the service account of the pod.
func FindRestartingPods(g osgraph.Graph, f osgraph.Namer, logsCommandName, securityPolicyCommandPattern string) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastPodNode := range g.NodesByKind(kubegraph.PodNodeKind) {
podNode := uncastPodNode.(*kubegraph.PodNode)
pod, ok := podNode.Object().(*kapi.Pod)
if !ok {
continue
}
for _, containerStatus := range pod.Status.ContainerStatuses {
containerString := ""
if len(pod.Spec.Containers) > 1 {
containerString = fmt.Sprintf("container %q in ", containerStatus.Name)
}
switch {
case containerCrashLoopBackOff(containerStatus):
var suggestion string
switch {
case containerIsNonRoot(pod, containerStatus.Name):
suggestion = heredoc.Docf(`
The container is starting and exiting repeatedly. This usually means the container is unable
to start, misconfigured, or limited by security restrictions. Check the container logs with
%s %s -c %s
Current security policy prevents your containers from being run as the root user. Some images
may fail expecting to be able to change ownership or permissions on directories. Your admin
can grant you access to run containers that need to run as the root user with this command:
%s
`, logsCommandName, pod.Name, containerStatus.Name, fmt.Sprintf(securityPolicyCommandPattern, pod.Namespace, pod.Spec.ServiceAccountName))
default:
suggestion = heredoc.Docf(`
The container is starting and exiting repeatedly. This usually means the container is unable
to start, misconfigured, or limited by security restrictions. Check the container logs with
%s %s -c %s
`, logsCommandName, pod.Name, containerStatus.Name)
}
markers = append(markers, osgraph.Marker{
Node: podNode,
Severity: osgraph.ErrorSeverity,
Key: CrashLoopingPodError,
Message: fmt.Sprintf("%s%s is crash-looping", containerString,
f.ResourceName(podNode)),
Suggestion: osgraph.Suggestion(suggestion),
})
case ContainerRestartedRecently(containerStatus, nowFn()):
markers = append(markers, osgraph.Marker{
Node: podNode,
Severity: osgraph.WarningSeverity,
Key: RestartingPodWarning,
Message: fmt.Sprintf("%s%s has restarted within the last 10 minutes", containerString,
f.ResourceName(podNode)),
})
case containerRestartedFrequently(containerStatus):
markers = append(markers, osgraph.Marker{
Node: podNode,
Severity: osgraph.WarningSeverity,
Key: RestartingPodWarning,
Message: fmt.Sprintf("%s%s has restarted %d times", containerString,
f.ResourceName(podNode), containerStatus.RestartCount),
})
}
}
}
return markers
}
func containerIsNonRoot(pod *kapi.Pod, container string) bool {
for _, c := range pod.Spec.Containers {
if c.Name != container || c.SecurityContext == nil {
continue
}
switch {
case c.SecurityContext.RunAsUser != nil && *c.SecurityContext.RunAsUser != 0:
//c.SecurityContext.RunAsNonRoot != nil && *c.SecurityContext.RunAsNonRoot,
return true
}
}
return false
}
func containerCrashLoopBackOff(status kapi.ContainerStatus) bool {
return status.State.Waiting != nil && status.State.Waiting.Reason == "CrashLoopBackOff"
}
func ContainerRestartedRecently(status kapi.ContainerStatus, now unversioned.Time) bool {
if status.RestartCount == 0 {
return false
}
if status.LastTerminationState.Terminated != nil && now.Sub(status.LastTerminationState.Terminated.FinishedAt.Time) < RestartRecentDuration {
return true
}
return false
}
func containerRestartedFrequently(status kapi.ContainerStatus) bool {
return status.RestartCount > RestartThreshold
}
@@ -1,124 +0,0 @@
package analysis
import (
"fmt"
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
)
const (
UnmountableSecretWarning = "UnmountableSecret"
MissingSecretWarning = "MissingSecret"
)
// FindUnmountableSecrets inspects all PodSpecs for any Secret reference that isn't listed as mountable by the referenced ServiceAccount
func FindUnmountableSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
unmountableSecrets := CheckForUnmountableSecrets(g, podSpecNode)
topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
topLevelString := f.ResourceName(topLevelNode)
saString := "MISSING_SA"
saNodes := g.SuccessorNodesByEdgeKind(podSpecNode, kubeedges.ReferencedServiceAccountEdgeKind)
if len(saNodes) > 0 {
saString = f.ResourceName(saNodes[0])
}
for _, unmountableSecret := range unmountableSecrets {
markers = append(markers, osgraph.Marker{
Node: podSpecNode,
RelatedNodes: []graph.Node{unmountableSecret},
Severity: osgraph.WarningSeverity,
Key: UnmountableSecretWarning,
Message: fmt.Sprintf("%s is attempting to mount a secret %s disallowed by %s",
topLevelString, f.ResourceName(unmountableSecret), saString),
})
}
}
return markers
}
// FindMissingSecrets inspects all PodSpecs for any Secret reference that is a synthetic node (not a pre-existing node in the graph)
func FindMissingSecrets(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
missingSecrets := CheckMissingMountedSecrets(g, podSpecNode)
topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
topLevelString := f.ResourceName(topLevelNode)
for _, missingSecret := range missingSecrets {
markers = append(markers, osgraph.Marker{
Node: podSpecNode,
RelatedNodes: []graph.Node{missingSecret},
Severity: osgraph.WarningSeverity,
Key: UnmountableSecretWarning,
Message: fmt.Sprintf("%s is attempting to mount a missing secret %s",
topLevelString, f.ResourceName(missingSecret)),
})
}
}
return markers
}
// CheckForUnmountableSecrets checks to be sure that all the referenced secrets are mountable (by service account)
func CheckForUnmountableSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
saNodes := g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.ServiceAccountNodeKind, kubeedges.ReferencedServiceAccountEdgeKind)
saMountableSecrets := []*kubegraph.SecretNode{}
if len(saNodes) > 0 {
saNode := saNodes[0].(*kubegraph.ServiceAccountNode)
for _, secretNode := range g.SuccessorNodesByNodeAndEdgeKind(saNode, kubegraph.SecretNodeKind, kubeedges.MountableSecretEdgeKind) {
saMountableSecrets = append(saMountableSecrets, secretNode.(*kubegraph.SecretNode))
}
}
unmountableSecrets := []*kubegraph.SecretNode{}
for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)
mountable := false
for _, mountableSecretNode := range saMountableSecrets {
if mountableSecretNode == mountedSecretNode {
mountable = true
break
}
}
if !mountable {
unmountableSecrets = append(unmountableSecrets, mountedSecretNode)
continue
}
}
return unmountableSecrets
}
// CheckMissingMountedSecrets checks to be sure that all the referenced secrets are present (not synthetic)
func CheckMissingMountedSecrets(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) []*kubegraph.SecretNode {
missingSecrets := []*kubegraph.SecretNode{}
for _, uncastMountedSecretNode := range g.SuccessorNodesByNodeAndEdgeKind(podSpecNode, kubegraph.SecretNodeKind, kubeedges.MountedSecretEdgeKind) {
mountedSecretNode := uncastMountedSecretNode.(*kubegraph.SecretNode)
if !mountedSecretNode.Found() {
missingSecrets = append(missingSecrets, mountedSecretNode)
}
}
return missingSecrets
}
-56
View File
@@ -1,56 +0,0 @@
package analysis
import (
"fmt"
"strings"
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubeedges "github.com/openshift/origin/pkg/api/kubegraph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
)
const (
DuelingReplicationControllerWarning = "DuelingReplicationControllers"
)
func FindDuelingReplicationControllers(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastRCNode := range g.NodesByKind(kubegraph.ReplicationControllerNodeKind) {
rcNode := uncastRCNode.(*kubegraph.ReplicationControllerNode)
for _, uncastPodNode := range g.PredecessorNodesByEdgeKind(rcNode, kubeedges.ManagedByControllerEdgeKind) {
podNode := uncastPodNode.(*kubegraph.PodNode)
// check to see if this pod is managed by more than one RC
uncastOwningRCs := g.SuccessorNodesByEdgeKind(podNode, kubeedges.ManagedByControllerEdgeKind)
if len(uncastOwningRCs) > 1 {
involvedRCNames := []string{}
relatedNodes := []graph.Node{uncastPodNode}
for _, uncastOwningRC := range uncastOwningRCs {
if uncastOwningRC.ID() == rcNode.ID() {
continue
}
owningRC := uncastOwningRC.(*kubegraph.ReplicationControllerNode)
involvedRCNames = append(involvedRCNames, f.ResourceName(owningRC))
relatedNodes = append(relatedNodes, uncastOwningRC)
}
markers = append(markers, osgraph.Marker{
Node: rcNode,
RelatedNodes: relatedNodes,
Severity: osgraph.WarningSeverity,
Key: DuelingReplicationControllerWarning,
Message: fmt.Sprintf("%s is competing for %s with %s", f.ResourceName(rcNode), f.ResourceName(podNode), strings.Join(involvedRCNames, ", ")),
})
}
}
}
return markers
}
-248
View File
@@ -1,248 +0,0 @@
package kubegraph
import (
"strings"
"github.com/gonum/graph"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
)
const (
// ExposedThroughServiceEdgeKind goes from a PodTemplateSpec or a Pod to Service. The head should make the service's selector.
ExposedThroughServiceEdgeKind = "ExposedThroughService"
// ManagedByControllerEdgeKind goes from Pod to controller when the Pod satisfies a controller's label selector
ManagedByControllerEdgeKind = "ManagedByController"
// MountedSecretEdgeKind goes from PodSpec to Secret indicating that is or will be a request to mount a volume with the Secret.
MountedSecretEdgeKind = "MountedSecret"
// MountableSecretEdgeKind goes from ServiceAccount to Secret indicating that the SA allows the Secret to be mounted
MountableSecretEdgeKind = "MountableSecret"
// ReferencedServiceAccountEdgeKind goes from PodSpec to ServiceAccount indicating that Pod is or will be running as the SA.
ReferencedServiceAccountEdgeKind = "ReferencedServiceAccount"
// ScalingEdgeKind goes from HorizontalPodAutoscaler to scaled objects indicating that the HPA scales the object
ScalingEdgeKind = "Scaling"
)
// AddExposedPodTemplateSpecEdges ensures that a directed edge exists between a service and all the PodTemplateSpecs
// in the graph that match the service selector
func AddExposedPodTemplateSpecEdges(g osgraph.MutableUniqueGraph, node *kubegraph.ServiceNode) {
if node.Service.Spec.Selector == nil {
return
}
query := labels.SelectorFromSet(node.Service.Spec.Selector)
for _, n := range g.(graph.Graph).Nodes() {
switch target := n.(type) {
case *kubegraph.PodTemplateSpecNode:
if target.Namespace != node.Namespace {
continue
}
if query.Matches(labels.Set(target.PodTemplateSpec.Labels)) {
g.AddEdge(target, node, ExposedThroughServiceEdgeKind)
}
}
}
}
// AddAllExposedPodTemplateSpecEdges calls AddExposedPodTemplateSpecEdges for every ServiceNode in the graph
func AddAllExposedPodTemplateSpecEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if serviceNode, ok := node.(*kubegraph.ServiceNode); ok {
AddExposedPodTemplateSpecEdges(g, serviceNode)
}
}
}
// AddExposedPodEdges ensures that a directed edge exists between a service and all the pods
// in the graph that match the service selector
func AddExposedPodEdges(g osgraph.MutableUniqueGraph, node *kubegraph.ServiceNode) {
if node.Service.Spec.Selector == nil {
return
}
query := labels.SelectorFromSet(node.Service.Spec.Selector)
for _, n := range g.(graph.Graph).Nodes() {
switch target := n.(type) {
case *kubegraph.PodNode:
if target.Namespace != node.Namespace {
continue
}
if query.Matches(labels.Set(target.Labels)) {
g.AddEdge(target, node, ExposedThroughServiceEdgeKind)
}
}
}
}
// AddAllExposedPodEdges calls AddExposedPodEdges for every ServiceNode in the graph
func AddAllExposedPodEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if serviceNode, ok := node.(*kubegraph.ServiceNode); ok {
AddExposedPodEdges(g, serviceNode)
}
}
}
// AddManagedByControllerPodEdges ensures that a directed edge exists between a controller and all the pods
// in the graph that match the label selector
func AddManagedByControllerPodEdges(g osgraph.MutableUniqueGraph, to graph.Node, namespace string, selector map[string]string) {
if selector == nil {
return
}
query := labels.SelectorFromSet(selector)
for _, n := range g.(graph.Graph).Nodes() {
switch target := n.(type) {
case *kubegraph.PodNode:
if target.Namespace != namespace {
continue
}
if query.Matches(labels.Set(target.Labels)) {
g.AddEdge(target, to, ManagedByControllerEdgeKind)
}
}
}
}
// AddAllManagedByControllerPodEdges calls AddManagedByControllerPodEdges for every node in the graph
// TODO: should do this through an interface (selects pods)
func AddAllManagedByControllerPodEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
switch cast := node.(type) {
case *kubegraph.ReplicationControllerNode:
AddManagedByControllerPodEdges(g, cast, cast.ReplicationController.Namespace, cast.ReplicationController.Spec.Selector)
case *kubegraph.PetSetNode:
// TODO: refactor to handle expanded selectors (along with ReplicaSets and Deployments)
AddManagedByControllerPodEdges(g, cast, cast.PetSet.Namespace, cast.PetSet.Spec.Selector.MatchLabels)
}
}
}
func AddMountedSecretEdges(g osgraph.Graph, podSpec *kubegraph.PodSpecNode) {
//pod specs are always contained. We'll get the toplevel container so that we can pull a namespace from it
containerNode := osgraph.GetTopLevelContainerNode(g, podSpec)
containerObj := g.GraphDescriber.Object(containerNode)
meta, err := kapi.ObjectMetaFor(containerObj.(runtime.Object))
if err != nil {
// this should never happen. it means that a podSpec is owned by a top level container that is not a runtime.Object
panic(err)
}
for _, volume := range podSpec.Volumes {
source := volume.VolumeSource
if source.Secret == nil {
continue
}
// pod secrets must be in the same namespace
syntheticSecret := &kapi.Secret{}
syntheticSecret.Namespace = meta.Namespace
syntheticSecret.Name = source.Secret.SecretName
secretNode := kubegraph.FindOrCreateSyntheticSecretNode(g, syntheticSecret)
g.AddEdge(podSpec, secretNode, MountedSecretEdgeKind)
}
}
func AddAllMountedSecretEdges(g osgraph.Graph) {
for _, node := range g.Nodes() {
if podSpecNode, ok := node.(*kubegraph.PodSpecNode); ok {
AddMountedSecretEdges(g, podSpecNode)
}
}
}
func AddMountableSecretEdges(g osgraph.Graph, saNode *kubegraph.ServiceAccountNode) {
for _, mountableSecret := range saNode.ServiceAccount.Secrets {
syntheticSecret := &kapi.Secret{}
syntheticSecret.Namespace = saNode.ServiceAccount.Namespace
syntheticSecret.Name = mountableSecret.Name
secretNode := kubegraph.FindOrCreateSyntheticSecretNode(g, syntheticSecret)
g.AddEdge(saNode, secretNode, MountableSecretEdgeKind)
}
}
func AddAllMountableSecretEdges(g osgraph.Graph) {
for _, node := range g.Nodes() {
if saNode, ok := node.(*kubegraph.ServiceAccountNode); ok {
AddMountableSecretEdges(g, saNode)
}
}
}
func AddRequestedServiceAccountEdges(g osgraph.Graph, podSpecNode *kubegraph.PodSpecNode) {
//pod specs are always contained. We'll get the toplevel container so that we can pull a namespace from it
containerNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)
containerObj := g.GraphDescriber.Object(containerNode)
meta, err := kapi.ObjectMetaFor(containerObj.(runtime.Object))
if err != nil {
panic(err)
}
// if no SA name is present, admission will set 'default'
name := "default"
if len(podSpecNode.ServiceAccountName) > 0 {
name = podSpecNode.ServiceAccountName
}
syntheticSA := &kapi.ServiceAccount{}
syntheticSA.Namespace = meta.Namespace
syntheticSA.Name = name
saNode := kubegraph.FindOrCreateSyntheticServiceAccountNode(g, syntheticSA)
g.AddEdge(podSpecNode, saNode, ReferencedServiceAccountEdgeKind)
}
func AddAllRequestedServiceAccountEdges(g osgraph.Graph) {
for _, node := range g.Nodes() {
if podSpecNode, ok := node.(*kubegraph.PodSpecNode); ok {
AddRequestedServiceAccountEdges(g, podSpecNode)
}
}
}
func AddHPAScaleRefEdges(g osgraph.Graph) {
for _, node := range g.NodesByKind(kubegraph.HorizontalPodAutoscalerNodeKind) {
hpaNode := node.(*kubegraph.HorizontalPodAutoscalerNode)
syntheticMeta := kapi.ObjectMeta{
Name: hpaNode.HorizontalPodAutoscaler.Spec.ScaleTargetRef.Name,
Namespace: hpaNode.HorizontalPodAutoscaler.Namespace,
}
var groupVersionResource unversioned.GroupVersionResource
resource := strings.ToLower(hpaNode.HorizontalPodAutoscaler.Spec.ScaleTargetRef.Kind)
if groupVersion, err := unversioned.ParseGroupVersion(hpaNode.HorizontalPodAutoscaler.Spec.ScaleTargetRef.APIVersion); err == nil {
groupVersionResource = groupVersion.WithResource(resource)
} else {
groupVersionResource = unversioned.GroupVersionResource{Resource: resource}
}
groupVersionResource, err := registered.RESTMapper().ResourceFor(groupVersionResource)
if err != nil {
continue
}
var syntheticNode graph.Node
switch groupVersionResource.GroupResource() {
case kapi.Resource("replicationcontrollers"):
syntheticNode = kubegraph.FindOrCreateSyntheticReplicationControllerNode(g, &kapi.ReplicationController{ObjectMeta: syntheticMeta})
case deployapi.Resource("deploymentconfigs"):
syntheticNode = deploygraph.FindOrCreateSyntheticDeploymentConfigNode(g, &deployapi.DeploymentConfig{ObjectMeta: syntheticMeta})
default:
continue
}
g.AddEdge(hpaNode, syntheticNode, ScalingEdgeKind)
}
}
-187
View File
@@ -1,187 +0,0 @@
package nodes
import (
"github.com/gonum/graph"
kapi "k8s.io/kubernetes/pkg/api"
kapps "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
osgraph "github.com/openshift/origin/pkg/api/graph"
)
func EnsurePodNode(g osgraph.MutableUniqueGraph, pod *kapi.Pod) *PodNode {
podNodeName := PodNodeName(pod)
podNode := osgraph.EnsureUnique(g,
podNodeName,
func(node osgraph.Node) graph.Node {
return &PodNode{node, pod}
},
).(*PodNode)
podSpecNode := EnsurePodSpecNode(g, &pod.Spec, pod.Namespace, podNodeName)
g.AddEdge(podNode, podSpecNode, osgraph.ContainsEdgeKind)
return podNode
}
func EnsurePodSpecNode(g osgraph.MutableUniqueGraph, podSpec *kapi.PodSpec, namespace string, ownerName osgraph.UniqueName) *PodSpecNode {
return osgraph.EnsureUnique(g,
PodSpecNodeName(podSpec, ownerName),
func(node osgraph.Node) graph.Node {
return &PodSpecNode{node, podSpec, namespace, ownerName}
},
).(*PodSpecNode)
}
// EnsureServiceNode adds the provided service to the graph if it does not already exist.
func EnsureServiceNode(g osgraph.MutableUniqueGraph, svc *kapi.Service) *ServiceNode {
return osgraph.EnsureUnique(g,
ServiceNodeName(svc),
func(node osgraph.Node) graph.Node {
return &ServiceNode{node, svc, true}
},
).(*ServiceNode)
}
// FindOrCreateSyntheticServiceNode returns the existing service node or creates a synthetic node in its place
func FindOrCreateSyntheticServiceNode(g osgraph.MutableUniqueGraph, svc *kapi.Service) *ServiceNode {
return osgraph.EnsureUnique(g,
ServiceNodeName(svc),
func(node osgraph.Node) graph.Node {
return &ServiceNode{node, svc, false}
},
).(*ServiceNode)
}
func EnsureServiceAccountNode(g osgraph.MutableUniqueGraph, o *kapi.ServiceAccount) *ServiceAccountNode {
return osgraph.EnsureUnique(g,
ServiceAccountNodeName(o),
func(node osgraph.Node) graph.Node {
return &ServiceAccountNode{node, o, true}
},
).(*ServiceAccountNode)
}
func FindOrCreateSyntheticServiceAccountNode(g osgraph.MutableUniqueGraph, o *kapi.ServiceAccount) *ServiceAccountNode {
return osgraph.EnsureUnique(g,
ServiceAccountNodeName(o),
func(node osgraph.Node) graph.Node {
return &ServiceAccountNode{node, o, false}
},
).(*ServiceAccountNode)
}
func EnsureSecretNode(g osgraph.MutableUniqueGraph, o *kapi.Secret) *SecretNode {
return osgraph.EnsureUnique(g,
SecretNodeName(o),
func(node osgraph.Node) graph.Node {
return &SecretNode{node, o, true}
},
).(*SecretNode)
}
func FindOrCreateSyntheticSecretNode(g osgraph.MutableUniqueGraph, o *kapi.Secret) *SecretNode {
return osgraph.EnsureUnique(g,
SecretNodeName(o),
func(node osgraph.Node) graph.Node {
return &SecretNode{node, o, false}
},
).(*SecretNode)
}
// EnsureReplicationControllerNode adds a graph node for the ReplicationController if it does not already exist.
func EnsureReplicationControllerNode(g osgraph.MutableUniqueGraph, rc *kapi.ReplicationController) *ReplicationControllerNode {
rcNodeName := ReplicationControllerNodeName(rc)
rcNode := osgraph.EnsureUnique(g,
rcNodeName,
func(node osgraph.Node) graph.Node {
return &ReplicationControllerNode{node, rc, true}
},
).(*ReplicationControllerNode)
rcSpecNode := EnsureReplicationControllerSpecNode(g, &rc.Spec, rc.Namespace, rcNodeName)
g.AddEdge(rcNode, rcSpecNode, osgraph.ContainsEdgeKind)
return rcNode
}
func FindOrCreateSyntheticReplicationControllerNode(g osgraph.MutableUniqueGraph, rc *kapi.ReplicationController) *ReplicationControllerNode {
return osgraph.EnsureUnique(g,
ReplicationControllerNodeName(rc),
func(node osgraph.Node) graph.Node {
return &ReplicationControllerNode{node, rc, false}
},
).(*ReplicationControllerNode)
}
func EnsureReplicationControllerSpecNode(g osgraph.MutableUniqueGraph, rcSpec *kapi.ReplicationControllerSpec, namespace string, ownerName osgraph.UniqueName) *ReplicationControllerSpecNode {
rcSpecName := ReplicationControllerSpecNodeName(rcSpec, ownerName)
rcSpecNode := osgraph.EnsureUnique(g,
rcSpecName,
func(node osgraph.Node) graph.Node {
return &ReplicationControllerSpecNode{node, rcSpec, namespace, ownerName}
},
).(*ReplicationControllerSpecNode)
if rcSpec.Template != nil {
ptSpecNode := EnsurePodTemplateSpecNode(g, rcSpec.Template, namespace, rcSpecName)
g.AddEdge(rcSpecNode, ptSpecNode, osgraph.ContainsEdgeKind)
}
return rcSpecNode
}
func EnsurePodTemplateSpecNode(g osgraph.MutableUniqueGraph, ptSpec *kapi.PodTemplateSpec, namespace string, ownerName osgraph.UniqueName) *PodTemplateSpecNode {
ptSpecName := PodTemplateSpecNodeName(ptSpec, ownerName)
ptSpecNode := osgraph.EnsureUnique(g,
ptSpecName,
func(node osgraph.Node) graph.Node {
return &PodTemplateSpecNode{node, ptSpec, namespace, ownerName}
},
).(*PodTemplateSpecNode)
podSpecNode := EnsurePodSpecNode(g, &ptSpec.Spec, namespace, ptSpecName)
g.AddEdge(ptSpecNode, podSpecNode, osgraph.ContainsEdgeKind)
return ptSpecNode
}
func EnsureHorizontalPodAutoscalerNode(g osgraph.MutableUniqueGraph, hpa *autoscaling.HorizontalPodAutoscaler) *HorizontalPodAutoscalerNode {
return osgraph.EnsureUnique(g,
HorizontalPodAutoscalerNodeName(hpa),
func(node osgraph.Node) graph.Node {
return &HorizontalPodAutoscalerNode{Node: node, HorizontalPodAutoscaler: hpa}
},
).(*HorizontalPodAutoscalerNode)
}
func EnsurePetSetNode(g osgraph.MutableUniqueGraph, petset *kapps.PetSet) *PetSetNode {
nodeName := PetSetNodeName(petset)
node := osgraph.EnsureUnique(g,
nodeName,
func(node osgraph.Node) graph.Node {
return &PetSetNode{node, petset}
},
).(*PetSetNode)
specNode := EnsurePetSetSpecNode(g, &petset.Spec, petset.Namespace, nodeName)
g.AddEdge(node, specNode, osgraph.ContainsEdgeKind)
return node
}
func EnsurePetSetSpecNode(g osgraph.MutableUniqueGraph, spec *kapps.PetSetSpec, namespace string, ownerName osgraph.UniqueName) *PetSetSpecNode {
specName := PetSetSpecNodeName(spec, ownerName)
specNode := osgraph.EnsureUnique(g,
specName,
func(node osgraph.Node) graph.Node {
return &PetSetSpecNode{node, spec, namespace, ownerName}
},
).(*PetSetSpecNode)
ptSpecNode := EnsurePodTemplateSpecNode(g, &spec.Template, namespace, specName)
g.AddEdge(specNode, ptSpecNode, osgraph.ContainsEdgeKind)
return specNode
}
-325
View File
@@ -1,325 +0,0 @@
package nodes
import (
"fmt"
"reflect"
kapi "k8s.io/kubernetes/pkg/api"
kapps "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
osgraph "github.com/openshift/origin/pkg/api/graph"
)
var (
ServiceNodeKind = reflect.TypeOf(kapi.Service{}).Name()
PodNodeKind = reflect.TypeOf(kapi.Pod{}).Name()
PodSpecNodeKind = reflect.TypeOf(kapi.PodSpec{}).Name()
PodTemplateSpecNodeKind = reflect.TypeOf(kapi.PodTemplateSpec{}).Name()
ReplicationControllerNodeKind = reflect.TypeOf(kapi.ReplicationController{}).Name()
ReplicationControllerSpecNodeKind = reflect.TypeOf(kapi.ReplicationControllerSpec{}).Name()
ServiceAccountNodeKind = reflect.TypeOf(kapi.ServiceAccount{}).Name()
SecretNodeKind = reflect.TypeOf(kapi.Secret{}).Name()
HorizontalPodAutoscalerNodeKind = reflect.TypeOf(autoscaling.HorizontalPodAutoscaler{}).Name()
PetSetNodeKind = reflect.TypeOf(kapps.PetSet{}).Name()
PetSetSpecNodeKind = reflect.TypeOf(kapps.PetSetSpec{}).Name()
)
func ServiceNodeName(o *kapi.Service) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(ServiceNodeKind, o)
}
type ServiceNode struct {
osgraph.Node
*kapi.Service
IsFound bool
}
func (n ServiceNode) Object() interface{} {
return n.Service
}
func (n ServiceNode) String() string {
return string(ServiceNodeName(n.Service))
}
func (*ServiceNode) Kind() string {
return ServiceNodeKind
}
func (n ServiceNode) Found() bool {
return n.IsFound
}
func PodNodeName(o *kapi.Pod) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(PodNodeKind, o)
}
type PodNode struct {
osgraph.Node
*kapi.Pod
}
func (n PodNode) Object() interface{} {
return n.Pod
}
func (n PodNode) String() string {
return string(PodNodeName(n.Pod))
}
func (n PodNode) UniqueName() osgraph.UniqueName {
return PodNodeName(n.Pod)
}
func (*PodNode) Kind() string {
return PodNodeKind
}
func PodSpecNodeName(o *kapi.PodSpec, ownerName osgraph.UniqueName) osgraph.UniqueName {
return osgraph.UniqueName(fmt.Sprintf("%s|%v", PodSpecNodeKind, ownerName))
}
type PodSpecNode struct {
osgraph.Node
*kapi.PodSpec
Namespace string
OwnerName osgraph.UniqueName
}
func (n PodSpecNode) Object() interface{} {
return n.PodSpec
}
func (n PodSpecNode) String() string {
return string(n.UniqueName())
}
func (n PodSpecNode) UniqueName() osgraph.UniqueName {
return PodSpecNodeName(n.PodSpec, n.OwnerName)
}
func (*PodSpecNode) Kind() string {
return PodSpecNodeKind
}
func ReplicationControllerNodeName(o *kapi.ReplicationController) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(ReplicationControllerNodeKind, o)
}
type ReplicationControllerNode struct {
osgraph.Node
ReplicationController *kapi.ReplicationController
IsFound bool
}
func (n ReplicationControllerNode) Found() bool {
return n.IsFound
}
func (n ReplicationControllerNode) Object() interface{} {
return n.ReplicationController
}
func (n ReplicationControllerNode) String() string {
return string(ReplicationControllerNodeName(n.ReplicationController))
}
func (n ReplicationControllerNode) UniqueName() osgraph.UniqueName {
return ReplicationControllerNodeName(n.ReplicationController)
}
func (*ReplicationControllerNode) Kind() string {
return ReplicationControllerNodeKind
}
func ReplicationControllerSpecNodeName(o *kapi.ReplicationControllerSpec, ownerName osgraph.UniqueName) osgraph.UniqueName {
return osgraph.UniqueName(fmt.Sprintf("%s|%v", ReplicationControllerSpecNodeKind, ownerName))
}
type ReplicationControllerSpecNode struct {
osgraph.Node
ReplicationControllerSpec *kapi.ReplicationControllerSpec
Namespace string
OwnerName osgraph.UniqueName
}
func (n ReplicationControllerSpecNode) Object() interface{} {
return n.ReplicationControllerSpec
}
func (n ReplicationControllerSpecNode) String() string {
return string(n.UniqueName())
}
func (n ReplicationControllerSpecNode) UniqueName() osgraph.UniqueName {
return ReplicationControllerSpecNodeName(n.ReplicationControllerSpec, n.OwnerName)
}
func (*ReplicationControllerSpecNode) Kind() string {
return ReplicationControllerSpecNodeKind
}
func PodTemplateSpecNodeName(o *kapi.PodTemplateSpec, ownerName osgraph.UniqueName) osgraph.UniqueName {
return osgraph.UniqueName(fmt.Sprintf("%s|%v", PodTemplateSpecNodeKind, ownerName))
}
type PodTemplateSpecNode struct {
osgraph.Node
*kapi.PodTemplateSpec
Namespace string
OwnerName osgraph.UniqueName
}
func (n PodTemplateSpecNode) Object() interface{} {
return n.PodTemplateSpec
}
func (n PodTemplateSpecNode) String() string {
return string(n.UniqueName())
}
func (n PodTemplateSpecNode) UniqueName() osgraph.UniqueName {
return PodTemplateSpecNodeName(n.PodTemplateSpec, n.OwnerName)
}
func (*PodTemplateSpecNode) Kind() string {
return PodTemplateSpecNodeKind
}
func ServiceAccountNodeName(o *kapi.ServiceAccount) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(ServiceAccountNodeKind, o)
}
type ServiceAccountNode struct {
osgraph.Node
*kapi.ServiceAccount
IsFound bool
}
func (n ServiceAccountNode) Found() bool {
return n.IsFound
}
func (n ServiceAccountNode) Object() interface{} {
return n.ServiceAccount
}
func (n ServiceAccountNode) String() string {
return string(ServiceAccountNodeName(n.ServiceAccount))
}
func (*ServiceAccountNode) Kind() string {
return ServiceAccountNodeKind
}
func SecretNodeName(o *kapi.Secret) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(SecretNodeKind, o)
}
type SecretNode struct {
osgraph.Node
*kapi.Secret
IsFound bool
}
func (n SecretNode) Found() bool {
return n.IsFound
}
func (n SecretNode) Object() interface{} {
return n.Secret
}
func (n SecretNode) String() string {
return string(SecretNodeName(n.Secret))
}
func (*SecretNode) Kind() string {
return SecretNodeKind
}
func HorizontalPodAutoscalerNodeName(o *autoscaling.HorizontalPodAutoscaler) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(HorizontalPodAutoscalerNodeKind, o)
}
type HorizontalPodAutoscalerNode struct {
osgraph.Node
HorizontalPodAutoscaler *autoscaling.HorizontalPodAutoscaler
}
func (n HorizontalPodAutoscalerNode) Object() interface{} {
return n.HorizontalPodAutoscaler
}
func (n HorizontalPodAutoscalerNode) String() string {
return string(n.UniqueName())
}
func (*HorizontalPodAutoscalerNode) Kind() string {
return HorizontalPodAutoscalerNodeKind
}
func (n HorizontalPodAutoscalerNode) UniqueName() osgraph.UniqueName {
return HorizontalPodAutoscalerNodeName(n.HorizontalPodAutoscaler)
}
func PetSetNodeName(o *kapps.PetSet) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(PetSetNodeKind, o)
}
type PetSetNode struct {
osgraph.Node
PetSet *kapps.PetSet
}
func (n PetSetNode) Object() interface{} {
return n.PetSet
}
func (n PetSetNode) String() string {
return string(n.UniqueName())
}
func (n PetSetNode) UniqueName() osgraph.UniqueName {
return PetSetNodeName(n.PetSet)
}
func (*PetSetNode) Kind() string {
return PetSetNodeKind
}
func PetSetSpecNodeName(o *kapps.PetSetSpec, ownerName osgraph.UniqueName) osgraph.UniqueName {
return osgraph.UniqueName(fmt.Sprintf("%s|%v", PetSetSpecNodeKind, ownerName))
}
type PetSetSpecNode struct {
osgraph.Node
PetSetSpec *kapps.PetSetSpec
Namespace string
OwnerName osgraph.UniqueName
}
func (n PetSetSpecNode) Object() interface{} {
return n.PetSetSpec
}
func (n PetSetSpecNode) String() string {
return string(n.UniqueName())
}
func (n PetSetSpecNode) UniqueName() osgraph.UniqueName {
return PetSetSpecNodeName(n.PetSetSpec, n.OwnerName)
}
func (*PetSetSpecNode) Kind() string {
return PetSetSpecNodeKind
}
-176
View File
@@ -1,176 +0,0 @@
package restmapper
import (
"sync"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/typed/discovery"
)
type discoveryRESTMapper struct {
discoveryClient discovery.DiscoveryInterface
delegate meta.RESTMapper
initLock sync.Mutex
}
// NewDiscoveryRESTMapper that initializes using the discovery APIs, relying on group ordering and preferred versions
// to build its appropriate priorities. Only versions are registered with API machinery are added now.
// TODO make this work with generic resources at some point. For now, this handles enabled and disabled resources cleanly.
func NewDiscoveryRESTMapper(discoveryClient discovery.DiscoveryInterface) meta.RESTMapper {
return &discoveryRESTMapper{discoveryClient: discoveryClient}
}
func (d *discoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
d.initLock.Lock()
defer d.initLock.Unlock()
if d.delegate != nil {
return d.delegate, nil
}
serverGroups, err := d.discoveryClient.ServerGroups()
if err != nil {
return nil, err
}
// always prefer our default group for now. The version should be discovered from discovery, but this will hold us
// for quite some time.
resourcePriority := []unversioned.GroupVersionResource{
{Group: kapi.GroupName, Version: meta.AnyVersion, Resource: meta.AnyResource},
}
kindPriority := []unversioned.GroupVersionKind{
{Group: kapi.GroupName, Version: meta.AnyVersion, Kind: meta.AnyKind},
}
groupPriority := []string{}
unionMapper := meta.MultiRESTMapper{}
for _, group := range serverGroups.Groups {
if len(group.Versions) == 0 {
continue
}
groupPriority = append(groupPriority, group.Name)
if len(group.PreferredVersion.Version) != 0 {
preferredVersion := unversioned.GroupVersion{Group: group.Name, Version: group.PreferredVersion.Version}
if registered.IsEnabledVersion(preferredVersion) {
resourcePriority = append(resourcePriority, preferredVersion.WithResource(meta.AnyResource))
kindPriority = append(kindPriority, preferredVersion.WithKind(meta.AnyKind))
}
}
for _, discoveryVersion := range group.Versions {
version := unversioned.GroupVersion{Group: group.Name, Version: discoveryVersion.Version}
if !registered.IsEnabledVersion(version) {
continue
}
groupMeta, err := registered.Group(group.Name)
if err != nil {
return nil, err
}
resources, err := d.discoveryClient.ServerResourcesForGroupVersion(version.String())
if err != nil {
return nil, err
}
versionMapper := meta.NewDefaultRESTMapper([]unversioned.GroupVersion{version}, groupMeta.InterfacesFor)
for _, resource := range resources.APIResources {
// TODO properly handle resource versus kind
gvk := version.WithKind(resource.Kind)
scope := meta.RESTScopeNamespace
if !resource.Namespaced {
scope = meta.RESTScopeRoot
}
versionMapper.Add(gvk, scope)
// TODO formalize this by checking to see if they support listing
versionMapper.Add(version.WithKind(resource.Kind+"List"), scope)
}
// we need to add List. Its a special case of something we need that isn't in the discovery doc
if group.Name == kapi.GroupName {
versionMapper.Add(version.WithKind("List"), meta.RESTScopeNamespace)
}
unionMapper = append(unionMapper, versionMapper)
}
}
for _, group := range groupPriority {
resourcePriority = append(resourcePriority, unversioned.GroupVersionResource{Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource})
kindPriority = append(kindPriority, unversioned.GroupVersionKind{Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind})
}
d.delegate = meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority}
return d.delegate, nil
}
func (d *discoveryRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) {
delegate, err := d.getDelegate()
if err != nil {
return unversioned.GroupVersionKind{}, err
}
return delegate.KindFor(resource)
}
func (d *discoveryRESTMapper) KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) {
delegate, err := d.getDelegate()
if err != nil {
return nil, err
}
return delegate.KindsFor(resource)
}
func (d *discoveryRESTMapper) ResourceFor(input unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) {
delegate, err := d.getDelegate()
if err != nil {
return unversioned.GroupVersionResource{}, err
}
return delegate.ResourceFor(input)
}
func (d *discoveryRESTMapper) ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) {
delegate, err := d.getDelegate()
if err != nil {
return nil, err
}
return delegate.ResourcesFor(input)
}
func (d *discoveryRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*meta.RESTMapping, error) {
delegate, err := d.getDelegate()
if err != nil {
return nil, err
}
return delegate.RESTMapping(gk, versions...)
}
func (d *discoveryRESTMapper) RESTMappings(gk unversioned.GroupKind) ([]*meta.RESTMapping, error) {
delegate, err := d.getDelegate()
if err != nil {
return nil, err
}
return delegate.RESTMappings(gk)
}
func (d *discoveryRESTMapper) AliasesForResource(resource string) ([]string, bool) {
delegate, err := d.getDelegate()
if err != nil {
return nil, false
}
return delegate.AliasesForResource(resource)
}
func (d *discoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
delegate, err := d.getDelegate()
if err != nil {
return resource, err
}
return delegate.ResourceSingularizer(resource)
}
+1 -1
View File
@@ -41,7 +41,7 @@ type UserIdentityMapper interface {
type Client interface {
GetId() string
ValidateSecret(secret string) bool
GetSecret() string
GetRedirectUri() string
GetUserData() interface{}
}
@@ -40,28 +40,34 @@ func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
// AuthenticateRequest authenticates the request using presented client certificates
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
if req.TLS == nil {
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
return nil, false, nil
}
// Use intermediates, if provided
optsCopy := a.opts
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
optsCopy.Intermediates = x509.NewCertPool()
for _, intermediate := range req.TLS.PeerCertificates[1:] {
optsCopy.Intermediates.AddCert(intermediate)
}
}
chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
if err != nil {
return nil, false, err
}
var errlist []error
for _, cert := range req.TLS.PeerCertificates {
chains, err := cert.Verify(a.opts)
for _, chain := range chains {
user, ok, err := a.user.User(chain)
if err != nil {
errlist = append(errlist, err)
continue
}
for _, chain := range chains {
user, ok, err := a.user.User(chain)
if err != nil {
errlist = append(errlist, err)
continue
}
if ok {
return user, ok, err
}
if ok {
return user, ok, err
}
}
return nil, false, kerrors.NewAggregate(errlist)
@@ -81,25 +87,28 @@ func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCom
return &Verifier{opts, auth, allowedCommonNames}
}
// AuthenticateRequest verifies the presented client certificates, then delegates to the wrapped auth
// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
if req.TLS == nil {
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
return nil, false, nil
}
var errlist []error
for _, cert := range req.TLS.PeerCertificates {
if _, err := cert.Verify(a.opts); err != nil {
errlist = append(errlist, err)
continue
// Use intermediates, if provided
optsCopy := a.opts
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
optsCopy.Intermediates = x509.NewCertPool()
for _, intermediate := range req.TLS.PeerCertificates[1:] {
optsCopy.Intermediates.AddCert(intermediate)
}
if err := a.verifySubject(cert.Subject); err != nil {
errlist = append(errlist, err)
continue
}
return a.auth.AuthenticateRequest(req)
}
return nil, false, kerrors.NewAggregate(errlist)
if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
return nil, false, err
}
if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
return nil, false, err
}
return a.auth.AuthenticateRequest(req)
}
func (a *Verifier) verifySubject(subject pkix.Name) error {
+1
View File
@@ -39,6 +39,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&RoleList{},
&SelfSubjectRulesReview{},
&SubjectRulesReview{},
&ResourceAccessReview{},
&SubjectAccessReview{},
&LocalResourceAccessReview{},
@@ -9,6 +9,7 @@ const (
NodeMetricsResource = "nodes/metrics"
NodeStatsResource = "nodes/stats"
NodeSpecResource = "nodes/spec"
NodeLogResource = "nodes/log"
RestrictedEndpointsResource = "endpoints/restricted"
+22
View File
@@ -50,6 +50,7 @@ var DiscoveryRule = PolicyRule{
"/apis", "/apis/*",
"/oapi", "/oapi/*",
"/osapi", "/osapi/", // these cannot be removed until we can drop support for pre 3.1 clients
"/.well-known", "/.well-known/*",
),
}
@@ -159,6 +160,27 @@ type SelfSubjectRulesReviewSpec struct {
Scopes []string
}
// SubjectRulesReview is a resource you can create to determine which actions another user can perform in a namespace
type SubjectRulesReview struct {
unversioned.TypeMeta
// Spec adds information about how to conduct the check
Spec SubjectRulesReviewSpec
// Status is completed by the server to tell which permissions you have
Status SubjectRulesReviewStatus
}
// SubjectRulesReviewSpec adds information about how to conduct the check
type SubjectRulesReviewSpec struct {
// User is optional. At least one of User and Groups must be specified.
User string
// Groups is optional. Groups is the list of groups to which the User belongs. At least one of User and Groups must be specified.
Groups []string
// Scopes to use for the evaluation. Empty means "use the unscoped (full) permissions of the user/groups".
Scopes []string
}
// SubjectRulesReviewStatus is contains the result of a rules check
type SubjectRulesReviewStatus struct {
// Rules is the list of rules (no particular sort) that are allowed for the subject
@@ -48,6 +48,8 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SelfSubjectRulesReviewSpec, InType: reflect.TypeOf(&SelfSubjectRulesReviewSpec{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SubjectAccessReview, InType: reflect.TypeOf(&SubjectAccessReview{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SubjectAccessReviewResponse, InType: reflect.TypeOf(&SubjectAccessReviewResponse{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SubjectRulesReview, InType: reflect.TypeOf(&SubjectRulesReview{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SubjectRulesReviewSpec, InType: reflect.TypeOf(&SubjectRulesReviewSpec{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SubjectRulesReviewStatus, InType: reflect.TypeOf(&SubjectRulesReviewStatus{})},
)
}
@@ -669,6 +671,44 @@ func DeepCopy_api_SubjectAccessReviewResponse(in interface{}, out interface{}, c
}
}
func DeepCopy_api_SubjectRulesReview(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*SubjectRulesReview)
out := out.(*SubjectRulesReview)
out.TypeMeta = in.TypeMeta
if err := DeepCopy_api_SubjectRulesReviewSpec(&in.Spec, &out.Spec, c); err != nil {
return err
}
if err := DeepCopy_api_SubjectRulesReviewStatus(&in.Status, &out.Status, c); err != nil {
return err
}
return nil
}
}
func DeepCopy_api_SubjectRulesReviewSpec(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*SubjectRulesReviewSpec)
out := out.(*SubjectRulesReviewSpec)
out.User = in.User
if in.Groups != nil {
in, out := &in.Groups, &out.Groups
*out = make([]string, len(*in))
copy(*out, *in)
} else {
out.Groups = nil
}
if in.Scopes != nil {
in, out := &in.Scopes, &out.Scopes
*out = make([]string, len(*in))
copy(*out, *in)
} else {
out.Scopes = nil
}
return nil
}
}
func DeepCopy_api_SubjectRulesReviewStatus(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*SubjectRulesReviewStatus)
@@ -1,60 +0,0 @@
package reaper
import (
"time"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/kubectl"
"github.com/openshift/origin/pkg/client"
)
func NewClusterRoleReaper(roleClient client.ClusterRolesInterface, clusterBindingClient client.ClusterRoleBindingsInterface, bindingClient client.RoleBindingsNamespacer) kubectl.Reaper {
return &ClusterRoleReaper{
roleClient: roleClient,
clusterBindingClient: clusterBindingClient,
bindingClient: bindingClient,
}
}
type ClusterRoleReaper struct {
roleClient client.ClusterRolesInterface
clusterBindingClient client.ClusterRoleBindingsInterface
bindingClient client.RoleBindingsNamespacer
}
// Stop on a reaper is actually used for deletion. In this case, we'll delete referencing clusterroleclusterBindings
// then delete the clusterrole.
func (r *ClusterRoleReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error {
clusterBindings, err := r.clusterBindingClient.ClusterRoleBindings().List(kapi.ListOptions{})
if err != nil {
return err
}
for _, clusterBinding := range clusterBindings.Items {
if clusterBinding.RoleRef.Name == name {
if err := r.clusterBindingClient.ClusterRoleBindings().Delete(clusterBinding.Name); err != nil && !kerrors.IsNotFound(err) {
glog.Infof("Cannot delete clusterrolebinding/%s: %v", clusterBinding.Name, err)
}
}
}
namespacedBindings, err := r.bindingClient.RoleBindings(kapi.NamespaceNone).List(kapi.ListOptions{})
if err != nil {
return err
}
for _, namespacedBinding := range namespacedBindings.Items {
if namespacedBinding.RoleRef.Namespace == kapi.NamespaceNone && namespacedBinding.RoleRef.Name == name {
if err := r.bindingClient.RoleBindings(namespacedBinding.Namespace).Delete(namespacedBinding.Name); err != nil && !kerrors.IsNotFound(err) {
glog.Infof("Cannot delete rolebinding/%s in %s: %v", namespacedBinding.Name, namespacedBinding.Namespace, err)
}
}
}
if err := r.roleClient.ClusterRoles().Delete(name); err != nil && !kerrors.IsNotFound(err) {
return err
}
return nil
}
-47
View File
@@ -1,47 +0,0 @@
package reaper
import (
"time"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/kubectl"
"github.com/openshift/origin/pkg/client"
)
func NewRoleReaper(roleClient client.RolesNamespacer, bindingClient client.RoleBindingsNamespacer) kubectl.Reaper {
return &RoleReaper{
roleClient: roleClient,
bindingClient: bindingClient,
}
}
type RoleReaper struct {
roleClient client.RolesNamespacer
bindingClient client.RoleBindingsNamespacer
}
// Stop on a reaper is actually used for deletion. In this case, we'll delete referencing rolebindings
// then delete the role.
func (r *RoleReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error {
bindings, err := r.bindingClient.RoleBindings(namespace).List(kapi.ListOptions{})
if err != nil {
return err
}
for _, binding := range bindings.Items {
if binding.RoleRef.Namespace == namespace && binding.RoleRef.Name == name {
if err := r.bindingClient.RoleBindings(namespace).Delete(binding.Name); err != nil && !kerrors.IsNotFound(err) {
glog.Infof("Cannot delete rolebinding/%s: %v", binding.Name, err)
}
}
}
if err := r.roleClient.Roles(namespace).Delete(name); err != nil && !kerrors.IsNotFound(err) {
return err
}
return nil
}
+54 -12
View File
@@ -19,6 +19,12 @@ const (
BuildCloneAnnotation = "openshift.io/build.clone-of"
// BuildPodNameAnnotation is an annotation whose value is the name of the pod running this build
BuildPodNameAnnotation = "openshift.io/build.pod-name"
// BuildJenkinsStatusJSONAnnotation is an annotation holding the Jenkins status information
BuildJenkinsStatusJSONAnnotation = "openshift.io/jenkins-status-json"
// BuildJenkinsLogURLAnnotation is an annotation holding a link to the Jenkins build console log
BuildJenkinsLogURLAnnotation = "openshift.io/jenkins-log-url"
// BuildJenkinsBuildURIAnnotation is an annotation holding a link to the Jenkins build
BuildJenkinsBuildURIAnnotation = "openshift.io/jenkins-build-uri"
// BuildLabel is the key of a Pod label whose value is the Name of a Build which is run.
// NOTE: The value for this label may not contain the entire Build name because it will be
// truncated to maximum label length.
@@ -106,8 +112,22 @@ type CommonSpec struct {
// be active on a node before the system actively tries to terminate the
// build; value must be positive integer.
CompletionDeadlineSeconds *int64
// NodeSelector is a selector which must be true for the build pod to fit on a node
// If nil, it can be overridden by default build nodeselector values for the cluster.
// If set to an empty map or a map with any values, default build nodeselector values
// are ignored.
NodeSelector map[string]string
}
const (
BuildTriggerCauseManualMsg = "Manually triggered"
BuildTriggerCauseConfigMsg = "Build configuration change"
BuildTriggerCauseImageMsg = "Image change"
BuildTriggerCauseGithubMsg = "GitHub WebHook"
BuildTriggerCauseGenericMsg = "Generic WebHook"
)
// BuildTriggerCause holds information about a triggered build. It is used for
// displaying build trigger data for each build and build configuration in oc
// describe. It is also used to describe which triggers led to the most recent
@@ -240,32 +260,32 @@ const (
// StatusReasonCannotCreateBuildPodSpec is an error condition when the build
// strategy cannot create a build pod spec.
StatusReasonCannotCreateBuildPodSpec = "CannotCreateBuildPodSpec"
StatusReasonCannotCreateBuildPodSpec StatusReason = "CannotCreateBuildPodSpec"
// StatusReasonCannotCreateBuildPod is an error condition when a build pod
// cannot be created.
StatusReasonCannotCreateBuildPod = "CannotCreateBuildPod"
StatusReasonCannotCreateBuildPod StatusReason = "CannotCreateBuildPod"
// StatusReasonInvalidOutputReference is an error condition when the build
// output is an invalid reference.
StatusReasonInvalidOutputReference = "InvalidOutputReference"
StatusReasonInvalidOutputReference StatusReason = "InvalidOutputReference"
// StatusReasonCancelBuildFailed is an error condition when cancelling a build
// fails.
StatusReasonCancelBuildFailed = "CancelBuildFailed"
StatusReasonCancelBuildFailed StatusReason = "CancelBuildFailed"
// StatusReasonBuildPodDeleted is an error condition when the build pod is
// deleted before build completion.
StatusReasonBuildPodDeleted = "BuildPodDeleted"
StatusReasonBuildPodDeleted StatusReason = "BuildPodDeleted"
// StatusReasonExceededRetryTimeout is an error condition when the build has
// not completed and retrying the build times out.
StatusReasonExceededRetryTimeout = "ExceededRetryTimeout"
StatusReasonExceededRetryTimeout StatusReason = "ExceededRetryTimeout"
// StatusReasonMissingPushSecret indicates that the build is missing required
// secret for pushing the output image.
// The build will stay in the pending state until the secret is created, or the build times out.
StatusReasonMissingPushSecret = "MissingPushSecret"
StatusReasonMissingPushSecret StatusReason = "MissingPushSecret"
)
// BuildSource is the input used for the build.
@@ -387,6 +407,18 @@ type GitSourceRevision struct {
Message string
}
// ProxyConfig defines what proxies to use for an operation
type ProxyConfig struct {
// HTTPProxy is a proxy used to reach the git repository over http
HTTPProxy *string
// HTTPSProxy is a proxy used to reach the git repository over https
HTTPSProxy *string
// NoProxy is the list of domains for which the proxy should not be used
NoProxy *string
}
// GitBuildSource defines the parameters of a Git SCM
type GitBuildSource struct {
// URI points to the source that will be built. The structure of the source
@@ -396,11 +428,8 @@ type GitBuildSource struct {
// Ref is the branch/tag/ref to build.
Ref string
// HTTPProxy is a proxy used to reach the git repository over http
HTTPProxy *string
// HTTPSProxy is a proxy used to reach the git repository over https
HTTPSProxy *string
// ProxyConfig defines the proxies to use for the git clone operation
ProxyConfig
}
// SourceControlUser defines the identity of a user of source control
@@ -646,6 +675,19 @@ type BuildOutput struct {
// up the authentication for executing the Docker push to authentication
// enabled Docker Registry (or Docker Hub).
PushSecret *kapi.LocalObjectReference
// ImageLabels define a list of labels that are applied to the resulting image. If there
// are multiple labels with the same name then the last one in the list is used.
ImageLabels []ImageLabel
}
// ImageLabel represents a label applied to the resulting image.
type ImageLabel struct {
// Name defines the name of the label. It must have non-zero length.
Name string
// Value defines the literal value of the label.
Value string
}
// BuildConfig is a template which can be used to create new builds.
+61 -13
View File
@@ -51,9 +51,11 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_GitSourceRevision, InType: reflect.TypeOf(&GitSourceRevision{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ImageChangeCause, InType: reflect.TypeOf(&ImageChangeCause{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ImageChangeTrigger, InType: reflect.TypeOf(&ImageChangeTrigger{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ImageLabel, InType: reflect.TypeOf(&ImageLabel{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ImageSource, InType: reflect.TypeOf(&ImageSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ImageSourcePath, InType: reflect.TypeOf(&ImageSourcePath{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_JenkinsPipelineBuildStrategy, InType: reflect.TypeOf(&JenkinsPipelineBuildStrategy{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ProxyConfig, InType: reflect.TypeOf(&ProxyConfig{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SecretBuildSource, InType: reflect.TypeOf(&SecretBuildSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SecretSpec, InType: reflect.TypeOf(&SecretSpec{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_SourceBuildStrategy, InType: reflect.TypeOf(&SourceBuildStrategy{})},
@@ -275,6 +277,15 @@ func DeepCopy_api_BuildOutput(in interface{}, out interface{}, c *conversion.Clo
} else {
out.PushSecret = nil
}
if in.ImageLabels != nil {
in, out := &in.ImageLabels, &out.ImageLabels
*out = make([]ImageLabel, len(*in))
for i := range *in {
(*out)[i] = (*in)[i]
}
} else {
out.ImageLabels = nil
}
return nil
}
}
@@ -635,6 +646,15 @@ func DeepCopy_api_CommonSpec(in interface{}, out interface{}, c *conversion.Clon
} else {
out.CompletionDeadlineSeconds = nil
}
if in.NodeSelector != nil {
in, out := &in.NodeSelector, &out.NodeSelector
*out = make(map[string]string)
for key, val := range *in {
(*out)[key] = val
}
} else {
out.NodeSelector = nil
}
return nil
}
}
@@ -766,19 +786,8 @@ func DeepCopy_api_GitBuildSource(in interface{}, out interface{}, c *conversion.
out := out.(*GitBuildSource)
out.URI = in.URI
out.Ref = in.Ref
if in.HTTPProxy != nil {
in, out := &in.HTTPProxy, &out.HTTPProxy
*out = new(string)
**out = **in
} else {
out.HTTPProxy = nil
}
if in.HTTPSProxy != nil {
in, out := &in.HTTPSProxy, &out.HTTPSProxy
*out = new(string)
**out = **in
} else {
out.HTTPSProxy = nil
if err := DeepCopy_api_ProxyConfig(&in.ProxyConfig, &out.ProxyConfig, c); err != nil {
return err
}
return nil
}
@@ -881,6 +890,16 @@ func DeepCopy_api_ImageChangeTrigger(in interface{}, out interface{}, c *convers
}
}
func DeepCopy_api_ImageLabel(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*ImageLabel)
out := out.(*ImageLabel)
out.Name = in.Name
out.Value = in.Value
return nil
}
}
func DeepCopy_api_ImageSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*ImageSource)
@@ -926,6 +945,35 @@ func DeepCopy_api_JenkinsPipelineBuildStrategy(in interface{}, out interface{},
}
}
func DeepCopy_api_ProxyConfig(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*ProxyConfig)
out := out.(*ProxyConfig)
if in.HTTPProxy != nil {
in, out := &in.HTTPProxy, &out.HTTPProxy
*out = new(string)
**out = **in
} else {
out.HTTPProxy = nil
}
if in.HTTPSProxy != nil {
in, out := &in.HTTPSProxy, &out.HTTPSProxy
*out = new(string)
**out = **in
} else {
out.HTTPSProxy = nil
}
if in.NoProxy != nil {
in, out := &in.NoProxy, &out.NoProxy
*out = new(string)
**out = **in
} else {
out.NoProxy = nil
}
return nil
}
}
func DeepCopy_api_SecretBuildSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*SecretBuildSource)
-109
View File
@@ -1,109 +0,0 @@
package client
import (
buildapi "github.com/openshift/origin/pkg/build/api"
osclient "github.com/openshift/origin/pkg/client"
kapi "k8s.io/kubernetes/pkg/api"
)
// BuildConfigGetter provides methods for getting BuildConfigs
type BuildConfigGetter interface {
Get(namespace, name string) (*buildapi.BuildConfig, error)
}
// BuildConfigUpdater provides methods for updating BuildConfigs
type BuildConfigUpdater interface {
Update(buildConfig *buildapi.BuildConfig) error
}
// OSClientBuildConfigClient delegates get and update operations to the OpenShift client interface
type OSClientBuildConfigClient struct {
Client osclient.Interface
}
// NewOSClientBuildConfigClient creates a new build config client that uses an openshift client to create and get BuildConfigs
func NewOSClientBuildConfigClient(client osclient.Interface) *OSClientBuildConfigClient {
return &OSClientBuildConfigClient{Client: client}
}
// Get returns a BuildConfig using the OpenShift client.
func (c OSClientBuildConfigClient) Get(namespace, name string) (*buildapi.BuildConfig, error) {
return c.Client.BuildConfigs(namespace).Get(name)
}
// Update updates a BuildConfig using the OpenShift client.
func (c OSClientBuildConfigClient) Update(buildConfig *buildapi.BuildConfig) error {
_, err := c.Client.BuildConfigs(buildConfig.Namespace).Update(buildConfig)
return err
}
// BuildUpdater provides methods for updating existing Builds.
type BuildUpdater interface {
Update(namespace string, build *buildapi.Build) error
}
// BuildLister provides methods for listing the Builds.
type BuildLister interface {
List(namespace string, opts kapi.ListOptions) (*buildapi.BuildList, error)
}
// OSClientBuildClient deletes build create and update operations to the OpenShift client interface
type OSClientBuildClient struct {
Client osclient.Interface
}
// NewOSClientBuildClient creates a new build client that uses an openshift client to update builds
func NewOSClientBuildClient(client osclient.Interface) *OSClientBuildClient {
return &OSClientBuildClient{Client: client}
}
// Update updates builds using the OpenShift client.
func (c OSClientBuildClient) Update(namespace string, build *buildapi.Build) error {
_, e := c.Client.Builds(namespace).Update(build)
return e
}
// List lists the builds using the OpenShift client.
func (c OSClientBuildClient) List(namespace string, opts kapi.ListOptions) (*buildapi.BuildList, error) {
return c.Client.Builds(namespace).List(opts)
}
// BuildCloner provides methods for cloning builds
type BuildCloner interface {
Clone(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error)
}
// OSClientBuildClonerClient creates a new build client that uses an openshift client to clone builds
type OSClientBuildClonerClient struct {
Client osclient.Interface
}
// NewOSClientBuildClonerClient creates a new build client that uses an openshift client to clone builds
func NewOSClientBuildClonerClient(client osclient.Interface) *OSClientBuildClonerClient {
return &OSClientBuildClonerClient{Client: client}
}
// Clone generates new build for given build name
func (c OSClientBuildClonerClient) Clone(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error) {
return c.Client.Builds(namespace).Clone(request)
}
// BuildConfigInstantiator provides methods for instantiating builds from build configs
type BuildConfigInstantiator interface {
Instantiate(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error)
}
// OSClientBuildConfigInstantiatorClient creates a new build client that uses an openshift client to create builds
type OSClientBuildConfigInstantiatorClient struct {
Client osclient.Interface
}
// NewOSClientBuildConfigInstantiatorClient creates a new build client that uses an openshift client to create builds
func NewOSClientBuildConfigInstantiatorClient(client osclient.Interface) *OSClientBuildConfigInstantiatorClient {
return &OSClientBuildConfigInstantiatorClient{Client: client}
}
// Instantiate generates new build for given buildConfig
func (c OSClientBuildConfigInstantiatorClient) Instantiate(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error) {
return c.Client.BuildConfigs(namespace).Instantiate(request)
}
-2
View File
@@ -1,2 +0,0 @@
// Package cmd provides command helpers for builds
package cmd
-150
View File
@@ -1,150 +0,0 @@
package cmd
import (
"sort"
"strings"
"time"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
ktypes "k8s.io/kubernetes/pkg/types"
kutilerrors "k8s.io/kubernetes/pkg/util/errors"
buildapi "github.com/openshift/origin/pkg/build/api"
buildutil "github.com/openshift/origin/pkg/build/util"
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/util"
)
// NewBuildConfigReaper returns a new reaper for buildConfigs
func NewBuildConfigReaper(oc *client.Client) kubectl.Reaper {
return &BuildConfigReaper{oc: oc, pollInterval: kubectl.Interval, timeout: kubectl.Timeout}
}
// BuildConfigReaper implements the Reaper interface for buildConfigs
type BuildConfigReaper struct {
oc client.Interface
pollInterval, timeout time.Duration
}
// Stop deletes the build configuration and all of the associated builds.
func (reaper *BuildConfigReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error {
_, err := reaper.oc.BuildConfigs(namespace).Get(name)
if err != nil {
return err
}
var bcPotentialBuilds []buildapi.Build
// Collect builds related to the config.
builds, err := reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelector(name)})
if err != nil {
return err
}
bcPotentialBuilds = append(bcPotentialBuilds, builds.Items...)
// Collect deprecated builds related to the config.
// TODO: Delete this block after BuildConfigLabelDeprecated is removed.
builds, err = reaper.oc.Builds(namespace).List(kapi.ListOptions{LabelSelector: buildutil.BuildConfigSelectorDeprecated(name)})
if err != nil {
return err
}
bcPotentialBuilds = append(bcPotentialBuilds, builds.Items...)
// A map of builds associated with this build configuration
bcBuilds := make(map[ktypes.UID]buildapi.Build)
// Because of name length limits in the BuildConfigSelector, annotations are used to ensure
// reliable selection of associated builds.
for _, build := range bcPotentialBuilds {
if build.Annotations != nil {
if bcName, ok := build.Annotations[buildapi.BuildConfigAnnotation]; ok {
// The annotation, if present, has the full build config name.
if bcName != name {
// If the name does not match exactly, the build is not truly associated with the build configuration
continue
}
}
}
// Note that if there is no annotation, this is a deprecated build spec
// and we choose to include it in the deletion having matched only the BuildConfigSelectorDeprecated
// Use a map to union the lists returned by the contemporary & deprecated build queries
// (there will be overlap between the lists, and we only want to try to delete each build once)
bcBuilds[build.UID] = build
}
// If there are builds associated with this build configuration, pause it before attempting the deletion
if len(bcBuilds) > 0 {
// Add paused annotation to the build config pending the deletion
err = unversioned.RetryOnConflict(unversioned.DefaultRetry, func() error {
bc, err := reaper.oc.BuildConfigs(namespace).Get(name)
if err != nil {
return err
}
// Ignore if the annotation already exists
if strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true" {
return nil
}
// Set the annotation and update
if err := util.AddObjectAnnotations(bc, map[string]string{buildapi.BuildConfigPausedAnnotation: "true"}); err != nil {
return err
}
_, err = reaper.oc.BuildConfigs(namespace).Update(bc)
return err
})
if err != nil {
return err
}
}
// Warn the user if the BuildConfig won't get deleted after this point.
bcDeleted := false
defer func() {
if !bcDeleted {
glog.Warningf("BuildConfig %s/%s will not be deleted because not all associated builds could be deleted. You can try re-running the command or removing them manually", namespace, name)
}
}()
// For the benefit of test cases, sort the UIDs so that the deletion order is deterministic
buildUIDs := make([]string, 0, len(bcBuilds))
for buildUID := range bcBuilds {
buildUIDs = append(buildUIDs, string(buildUID))
}
sort.Strings(buildUIDs)
errList := []error{}
for _, buildUID := range buildUIDs {
build := bcBuilds[ktypes.UID(buildUID)]
if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil {
glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err)
if !kerrors.IsNotFound(err) {
errList = append(errList, err)
}
}
}
// Aggregate all errors
if len(errList) > 0 {
return kutilerrors.NewAggregate(errList)
}
if err := reaper.oc.BuildConfigs(namespace).Delete(name); err != nil {
return err
}
bcDeleted = true
return nil
}
-351
View File
@@ -1,351 +0,0 @@
package analysis
import (
"fmt"
"strings"
"time"
"github.com/gonum/graph"
"github.com/gonum/graph/topo"
"k8s.io/kubernetes/pkg/api/unversioned"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildapi "github.com/openshift/origin/pkg/build/api"
buildedges "github.com/openshift/origin/pkg/build/graph"
buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
imageapi "github.com/openshift/origin/pkg/image/api"
imageedges "github.com/openshift/origin/pkg/image/graph"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)
const (
TagNotAvailableWarning = "ImageStreamTagNotAvailable"
LatestBuildFailedErr = "LatestBuildFailed"
MissingRequiredRegistryErr = "MissingRequiredRegistry"
MissingOutputImageStreamErr = "MissingOutputImageStream"
CyclicBuildConfigWarning = "CyclicBuildConfig"
MissingImageStreamTagWarning = "MissingImageStreamTag"
MissingImageStreamImageWarning = "MissingImageStreamImage"
)
// FindUnpushableBuildConfigs checks all build configs that will output to an IST backed by an ImageStream and checks to make sure their builds can push.
func FindUnpushableBuildConfigs(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
// note, unlike with Inputs, ImageStreamImage is not a valid type for build output
bc:
for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
for _, istNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(istNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
if !imageStreamNode.IsFound {
markers = append(markers, osgraph.Marker{
Node: bcNode,
RelatedNodes: []graph.Node{istNode},
Severity: osgraph.ErrorSeverity,
Key: MissingOutputImageStreamErr,
Message: fmt.Sprintf("%s is pushing to %s, but the image stream for that tag does not exist.",
f.ResourceName(bcNode), f.ResourceName(istNode)),
})
continue
}
if len(imageStreamNode.Status.DockerImageRepository) == 0 {
markers = append(markers, osgraph.Marker{
Node: bcNode,
RelatedNodes: []graph.Node{istNode},
Severity: osgraph.ErrorSeverity,
Key: MissingRequiredRegistryErr,
Message: fmt.Sprintf("%s is pushing to %s, but the administrator has not configured the integrated Docker registry.",
f.ResourceName(bcNode), f.ResourceName(istNode)),
Suggestion: osgraph.Suggestion("oc adm registry -h"),
})
continue bc
}
}
}
}
return markers
}
// FindMissingInputImageStreams checks all build configs and confirms that their From element exists
//
// Precedence of failures:
// 1. A build config's input points to an image stream that does not exist
// 2. A build config's input uses an image stream tag reference in an existing image stream, but no images within the image stream have that tag assigned
// 3. A build config's input uses an image stream image reference in an exisiting image stream, but no images within the image stream have the supplied image hexadecimal ID
func FindMissingInputImageStreams(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
for _, bcInputNode := range g.PredecessorNodesByEdgeKind(bcNode, buildedges.BuildInputImageEdgeKind) {
switch bcInputNode.(type) {
case *imagegraph.ImageStreamTagNode:
for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(bcInputNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
// note, BuildConfig.Spec.BuildSpec.Strategy.[Docker|Source|Custom]Stragegy.From Input of ImageStream has been converted to ImageStreamTag on the vX to api conversion
// prior to our reaching this point in the code; so there is not need to check for that type vs. ImageStreamTag or ImageStreamImage;
tagNode, _ := bcInputNode.(*imagegraph.ImageStreamTagNode)
imageStream := imageStreamNode.Object().(*imageapi.ImageStream)
if _, ok := imageStream.Status.Tags[tagNode.ImageTag()]; !ok {
markers = append(markers, getImageStreamTagMarker(g, f, bcInputNode, imageStreamNode, tagNode, bcNode))
}
}
case *imagegraph.ImageStreamImageNode:
for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(bcInputNode, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
imageNode, _ := bcInputNode.(*imagegraph.ImageStreamImageNode)
imageStream := imageStreamNode.Object().(*imageapi.ImageStream)
found, imageID := validImageStreamImage(imageNode, imageStream)
if !found {
markers = append(markers, getImageStreamImageMarker(g, f, bcNode, bcInputNode, imageStreamNode, imageNode, imageStream, imageID))
}
}
}
}
}
return markers
}
// FindCircularBuilds checks all build configs for cycles
func FindCircularBuilds(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
// Filter out all but ImageStreamTag and BuildConfig nodes
nodeFn := osgraph.NodesOfKind(imagegraph.ImageStreamTagNodeKind, buildgraph.BuildConfigNodeKind)
// Filter out all but BuildInputImage and BuildOutput edges
edgeFn := osgraph.EdgesOfKind(buildedges.BuildInputImageEdgeKind, buildedges.BuildOutputEdgeKind)
// Create desired subgraph
sub := g.Subgraph(nodeFn, edgeFn)
markers := []osgraph.Marker{}
// Check for cycles
for _, cycle := range topo.CyclesIn(sub) {
nodeNames := []string{}
for _, node := range cycle {
nodeNames = append(nodeNames, f.ResourceName(node))
}
markers = append(markers, osgraph.Marker{
Node: cycle[0],
RelatedNodes: cycle,
Severity: osgraph.WarningSeverity,
Key: CyclicBuildConfigWarning,
Message: fmt.Sprintf("Cycle detected in build configurations: %s", strings.Join(nodeNames, " -> ")),
})
}
return markers
}
// multiBCStartBuildSuggestion builds the `oc start-build` suggestion string with multiple build configs
func multiBCStartBuildSuggestion(bcNodes []*buildgraph.BuildConfigNode) string {
var ret string
if len(bcNodes) > 1 {
ret = "Run one of the following commands: "
}
for i, bcNode := range bcNodes {
// use of f.ResourceName(bcNode) will produce a string like oc start-build BuildConfig|example/ruby-hello-world
ret = ret + fmt.Sprintf("oc start-build %s", bcNode.BuildConfig.GetName())
if i < (len(bcNodes) - 1) {
ret = ret + ", "
}
}
return ret
}
// bcNodesToRelatedNodes takes an array of BuildConfigNode's and returns an array of graph.Node's for the Marker.RelatedNodes field
func bcNodesToRelatedNodes(bcNodes []*buildgraph.BuildConfigNode) []graph.Node {
relatedNodes := []graph.Node{}
for _, bcNode := range bcNodes {
relatedNodes = append(relatedNodes, graph.Node(bcNode))
}
return relatedNodes
}
// findPendingTagMarkers is the guts behind FindPendingTags .... break out some of the content and reduce some indentation
func findPendingTagMarkers(istNode *imagegraph.ImageStreamTagNode, g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
buildFound := false
bcNodes := buildedges.BuildConfigsForTag(g, graph.Node(istNode))
for _, bcNode := range bcNodes {
latestBuild := buildedges.GetLatestBuild(g, bcNode)
// A build config points to the non existent tag but no current build exists.
if latestBuild == nil {
continue
}
buildFound = true
// A build config points to the non existent tag but something is going on with
// the latest build.
// TODO: Handle other build phases.
switch latestBuild.Build.Status.Phase {
case buildapi.BuildPhaseCancelled:
// TODO: Add a warning here.
case buildapi.BuildPhaseError:
// TODO: Add a warning here.
case buildapi.BuildPhaseComplete:
// We should never hit this. The output of our build is missing but the build is complete.
// Most probably the user has messed up?
case buildapi.BuildPhaseFailed:
// Since the tag hasn't been populated yet, we assume there hasn't been a successful
// build so far.
markers = append(markers, osgraph.Marker{
Node: graph.Node(latestBuild),
RelatedNodes: []graph.Node{graph.Node(istNode), graph.Node(bcNode)},
Severity: osgraph.ErrorSeverity,
Key: LatestBuildFailedErr,
Message: fmt.Sprintf("%s has failed.", f.ResourceName(latestBuild)),
Suggestion: osgraph.Suggestion(fmt.Sprintf("Inspect the build failure with 'oc logs -f bc/%s'", bcNode.BuildConfig.GetName())),
})
default:
// Do nothing when latest build is new, pending, or running.
}
}
// if no current builds exist for any of the build configs, append marker for that
// but ignore ISTs which have no build configs
if !buildFound && len(bcNodes) > 0 {
markers = append(markers, osgraph.Marker{
Node: graph.Node(istNode),
RelatedNodes: bcNodesToRelatedNodes(bcNodes),
Severity: osgraph.WarningSeverity,
Key: TagNotAvailableWarning,
Message: fmt.Sprintf("%s needs to be imported or created by a build.", f.ResourceName(istNode)),
Suggestion: osgraph.Suggestion(multiBCStartBuildSuggestion(bcNodes)),
})
}
return markers
}
// FindPendingTags inspects all imageStreamTags that serve as outputs to builds.
//
// Precedence of failures:
// 1. A build config points to the non existent tag but no current build exists.
// 2. A build config points to the non existent tag but the latest build has failed.
func FindPendingTags(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastIstNode := range g.NodesByKind(imagegraph.ImageStreamTagNodeKind) {
istNode := uncastIstNode.(*imagegraph.ImageStreamTagNode)
if !istNode.Found() {
markers = append(markers, findPendingTagMarkers(istNode, g, f)...)
}
}
return markers
}
// getImageStreamTagMarker will return the appropriate marker for when a BuildConfig is missing its input ImageStreamTag
func getImageStreamTagMarker(g osgraph.Graph, f osgraph.Namer, bcInputNode graph.Node, imageStreamNode graph.Node, tagNode *imagegraph.ImageStreamTagNode, bcNode graph.Node) osgraph.Marker {
return osgraph.Marker{
Node: bcNode,
RelatedNodes: []graph.Node{bcInputNode,
imageStreamNode},
Severity: osgraph.WarningSeverity,
Key: MissingImageStreamImageWarning,
Message: fmt.Sprintf("%s builds from %s, but the image stream tag does not exist.", f.ResourceName(bcNode), f.ResourceName(bcInputNode)),
Suggestion: getImageStreamTagSuggestion(g, f, tagNode),
}
}
// getImageStreamTagSuggestion will return the appropriate marker Suggestion for when a BuildConfig is missing its input ImageStreamTag; in particular,
// it will determine whether or not another BuildConfig can produce the aforementioned ImageStreamTag
func getImageStreamTagSuggestion(g osgraph.Graph, f osgraph.Namer, tagNode *imagegraph.ImageStreamTagNode) osgraph.Suggestion {
bcs := []string{}
for _, bcNode := range g.PredecessorNodesByEdgeKind(tagNode, buildedges.BuildOutputEdgeKind) {
bcs = append(bcs, f.ResourceName(bcNode))
}
if len(bcs) == 1 {
return osgraph.Suggestion(fmt.Sprintf("oc start-build %s", bcs[0]))
}
if len(bcs) > 0 {
return osgraph.Suggestion(fmt.Sprintf("`oc start-build` with one of these: %s.", strings.Join(bcs[:], ",")))
}
return osgraph.Suggestion(fmt.Sprintf("%s needs to be imported.", f.ResourceName(tagNode)))
}
// getImageStreamImageMarker will return the appropriate marker for when a BuildConfig is missing its input ImageStreamImage
func getImageStreamImageMarker(g osgraph.Graph, f osgraph.Namer, bcNode graph.Node, bcInputNode graph.Node, imageStreamNode graph.Node, imageNode *imagegraph.ImageStreamImageNode, imageStream *imageapi.ImageStream, imageID string) osgraph.Marker {
return osgraph.Marker{
Node: bcNode,
RelatedNodes: []graph.Node{bcInputNode,
imageStreamNode},
Severity: osgraph.WarningSeverity,
Key: MissingImageStreamImageWarning,
Message: fmt.Sprintf("%s builds from %s, but the image stream image does not exist.", f.ResourceName(bcNode), f.ResourceName(bcInputNode)),
Suggestion: getImageStreamImageSuggestion(imageID, imageStream),
}
}
// getImageStreamImageSuggestion will return the appropriate marker Suggestion for when a BuildConfig is missing its input ImageStreamImage
func getImageStreamImageSuggestion(imageID string, imageStream *imageapi.ImageStream) osgraph.Suggestion {
// check the images stream to see if any import images are in flight or have failed
annotation, ok := imageStream.Annotations[imageapi.DockerImageRepositoryCheckAnnotation]
if !ok {
return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` where `--from` specifies an image with hexadecimal ID %s", imageStream.GetName(), imageID))
}
if checkTime, err := time.Parse(time.RFC3339, annotation); err == nil {
// this time based annotation is set by pkg/image/controller/controller.go whenever import/tag operations are performed; unless
// in the midst of an import/tag operation, it stays set and serves as a timestamp for when the last operation occurred;
// so we will check if the image stream has been updated "recently";
// in case it is a slow link to the remote repo, see if if the check annotation occurred within the last 5 minutes; if so, consider that as potentially "in progress"
compareTime := checkTime.Add(5 * time.Minute)
currentTime, _ := time.Parse(time.RFC3339, unversioned.Now().UTC().Format(time.RFC3339))
if compareTime.Before(currentTime) {
return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` where `--from` specifies an image with hexadecimal ID %s", imageStream.GetName(), imageID))
}
return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` with hexadecimal ID %s possibly in progress", imageStream.GetName(), imageID))
}
return osgraph.Suggestion(fmt.Sprintf("Possible error occurred with `oc import-image %s --from=` with hexadecimal ID %s; inspect images stream annotations", imageStream.GetName(), imageID))
}
// validImageStreamImage will cycle through the imageStream.Status.Tags.[]TagEvent.DockerImageReference and determine whether an image with the hexadecimal image id
// associated with an ImageStreamImage reference in fact exists in a given ImageStream; on return, this method returns a true if does exist, and as well as the hexadecimal image
// id from the ImageStreamImage
func validImageStreamImage(imageNode *imagegraph.ImageStreamImageNode, imageStream *imageapi.ImageStream) (bool, string) {
dockerImageReference, err := imageapi.ParseDockerImageReference(imageNode.Name)
if err == nil {
for _, tagEventList := range imageStream.Status.Tags {
for _, tagEvent := range tagEventList.Items {
if strings.Contains(tagEvent.DockerImageReference, dockerImageReference.ID) {
return true, dockerImageReference.ID
}
}
}
return false, dockerImageReference.ID
}
return false, ""
}
-133
View File
@@ -1,133 +0,0 @@
package graph
import (
"github.com/gonum/graph"
kapi "k8s.io/kubernetes/pkg/api"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildapi "github.com/openshift/origin/pkg/build/api"
buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
buildutil "github.com/openshift/origin/pkg/build/util"
imageapi "github.com/openshift/origin/pkg/image/api"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)
const (
// BuildTriggerImageEdgeKind is an edge from an ImageStream to a BuildConfig that
// represents a trigger connection. Changes to the ImageStream will trigger a new build
// from the BuildConfig.
BuildTriggerImageEdgeKind = "BuildTriggerImage"
// BuildInputImageEdgeKind is an edge from an ImageStream to a BuildConfig, where the
// ImageStream is the source image for the build (builder in S2I builds, FROM in Docker builds,
// custom builder in Custom builds). The same ImageStream can also have a trigger
// relationship with the BuildConfig, but not necessarily.
BuildInputImageEdgeKind = "BuildInputImage"
// BuildOutputEdgeKind is an edge from a BuildConfig to an ImageStream. The ImageStream will hold
// the ouptut of the Builds created with that BuildConfig.
BuildOutputEdgeKind = "BuildOutput"
// BuildInputEdgeKind is an edge from a source repository to a BuildConfig. The source repository is the
// input source for the build.
BuildInputEdgeKind = "BuildInput"
// BuildEdgeKind goes from a BuildConfigNode to a BuildNode and indicates that the buildConfig owns the build
BuildEdgeKind = "Build"
)
// AddBuildEdges adds edges that connect a BuildConfig to Builds to the given graph
func AddBuildEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) {
for _, n := range g.(graph.Graph).Nodes() {
if buildNode, ok := n.(*buildgraph.BuildNode); ok {
if buildNode.Build.Namespace != node.BuildConfig.Namespace {
continue
}
if belongsToBuildConfig(node.BuildConfig, buildNode.Build) {
g.AddEdge(node, buildNode, BuildEdgeKind)
}
}
}
}
// AddAllBuildEdges adds build edges to all BuildConfig nodes in the given graph
func AddAllBuildEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if bcNode, ok := node.(*buildgraph.BuildConfigNode); ok {
AddBuildEdges(g, bcNode)
}
}
}
func imageRefNode(g osgraph.MutableUniqueGraph, ref *kapi.ObjectReference, bc *buildapi.BuildConfig) graph.Node {
if ref == nil {
return nil
}
switch ref.Kind {
case "DockerImage":
if ref, err := imageapi.ParseDockerImageReference(ref.Name); err == nil {
tag := ref.Tag
ref.Tag = ""
return imagegraph.EnsureDockerRepositoryNode(g, ref.String(), tag)
}
case "ImageStream":
return imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(ref.Namespace, bc.Namespace), ref.Name, imageapi.DefaultImageTag))
case "ImageStreamTag":
return imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta2(defaultNamespace(ref.Namespace, bc.Namespace), ref.Name))
case "ImageStreamImage":
return imagegraph.FindOrCreateSyntheticImageStreamImageNode(g, imagegraph.MakeImageStreamImageObjectMeta(defaultNamespace(ref.Namespace, bc.Namespace), ref.Name))
}
return nil
}
// AddOutputEdges links the build config to its output image node.
func AddOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) {
if node.BuildConfig.Spec.Output.To == nil {
return
}
out := imageRefNode(g, node.BuildConfig.Spec.Output.To, node.BuildConfig)
g.AddEdge(node, out, BuildOutputEdgeKind)
}
// AddInputEdges links the build config to its input image and source nodes.
func AddInputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) {
if in := buildgraph.EnsureSourceRepositoryNode(g, node.BuildConfig.Spec.Source); in != nil {
g.AddEdge(in, node, BuildInputEdgeKind)
}
inputImage := buildutil.GetInputReference(node.BuildConfig.Spec.Strategy)
if input := imageRefNode(g, inputImage, node.BuildConfig); input != nil {
g.AddEdge(input, node, BuildInputImageEdgeKind)
}
}
// AddTriggerEdges links the build config to its trigger input image nodes.
func AddTriggerEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) {
for _, trigger := range node.BuildConfig.Spec.Triggers {
if trigger.Type != buildapi.ImageChangeBuildTriggerType {
continue
}
from := trigger.ImageChange.From
if trigger.ImageChange.From == nil {
from = buildutil.GetInputReference(node.BuildConfig.Spec.Strategy)
}
triggerNode := imageRefNode(g, from, node.BuildConfig)
g.AddEdge(triggerNode, node, BuildTriggerImageEdgeKind)
}
}
// AddInputOutputEdges links the build config to other nodes for the images and source repositories it depends on.
func AddInputOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) *buildgraph.BuildConfigNode {
AddInputEdges(g, node)
AddTriggerEdges(g, node)
AddOutputEdges(g, node)
return node
}
// AddAllInputOutputEdges adds input and output edges for all BuildConfigs in the given graph
func AddAllInputOutputEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if bcNode, ok := node.(*buildgraph.BuildConfigNode); ok {
AddInputOutputEdges(g, bcNode)
}
}
}
-111
View File
@@ -1,111 +0,0 @@
package graph
import (
"sort"
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildapi "github.com/openshift/origin/pkg/build/api"
buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
)
// RelevantBuilds returns the lastSuccessful build, lastUnsuccessful build, and a list of active builds
func RelevantBuilds(g osgraph.Graph, bcNode *buildgraph.BuildConfigNode) (*buildgraph.BuildNode, *buildgraph.BuildNode, []*buildgraph.BuildNode) {
var (
lastSuccessfulBuild *buildgraph.BuildNode
lastUnsuccessfulBuild *buildgraph.BuildNode
)
activeBuilds := []*buildgraph.BuildNode{}
allBuilds := []*buildgraph.BuildNode{}
uncastBuilds := g.SuccessorNodesByEdgeKind(bcNode, BuildEdgeKind)
for i := range uncastBuilds {
buildNode := uncastBuilds[i].(*buildgraph.BuildNode)
if belongsToBuildConfig(bcNode.BuildConfig, buildNode.Build) {
allBuilds = append(allBuilds, buildNode)
}
}
if len(allBuilds) == 0 {
return nil, nil, []*buildgraph.BuildNode{}
}
sort.Sort(RecentBuildReferences(allBuilds))
for i := range allBuilds {
switch allBuilds[i].Build.Status.Phase {
case buildapi.BuildPhaseComplete:
if lastSuccessfulBuild == nil {
lastSuccessfulBuild = allBuilds[i]
}
case buildapi.BuildPhaseFailed, buildapi.BuildPhaseCancelled, buildapi.BuildPhaseError:
if lastUnsuccessfulBuild == nil {
lastUnsuccessfulBuild = allBuilds[i]
}
default:
activeBuilds = append(activeBuilds, allBuilds[i])
}
}
return lastSuccessfulBuild, lastUnsuccessfulBuild, activeBuilds
}
func belongsToBuildConfig(config *buildapi.BuildConfig, b *buildapi.Build) bool {
if b.Labels == nil {
return false
}
if b.Annotations != nil && b.Annotations[buildapi.BuildConfigAnnotation] == config.Name {
return true
}
if b.Labels[buildapi.BuildConfigLabel] == config.Name {
return true
}
if b.Labels[buildapi.BuildConfigLabelDeprecated] == config.Name {
return true
}
return false
}
type RecentBuildReferences []*buildgraph.BuildNode
func (m RecentBuildReferences) Len() int { return len(m) }
func (m RecentBuildReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m RecentBuildReferences) Less(i, j int) bool {
return m[i].Build.CreationTimestamp.After(m[j].Build.CreationTimestamp.Time)
}
func defaultNamespace(value, defaultValue string) string {
if len(value) == 0 {
return defaultValue
}
return value
}
// BuildConfigsForTag returns the buildConfig that points to the provided imageStreamTag.
func BuildConfigsForTag(g osgraph.Graph, istag graph.Node) []*buildgraph.BuildConfigNode {
bcs := []*buildgraph.BuildConfigNode{}
for _, bcNode := range g.PredecessorNodesByEdgeKind(istag, BuildOutputEdgeKind) {
bcs = append(bcs, bcNode.(*buildgraph.BuildConfigNode))
}
return bcs
}
// GetLatestBuild returns the latest build for the provided buildConfig.
func GetLatestBuild(g osgraph.Graph, bc graph.Node) *buildgraph.BuildNode {
builds := g.SuccessorNodesByEdgeKind(bc, BuildEdgeKind)
if len(builds) == 0 {
return nil
}
latestBuild := builds[0].(*buildgraph.BuildNode)
for _, buildNode := range builds[1:] {
if build, ok := buildNode.(*buildgraph.BuildNode); ok {
if latestBuild.Build.CreationTimestamp.Before(build.Build.CreationTimestamp) {
latestBuild = build
}
}
}
return latestBuild
}
-47
View File
@@ -1,47 +0,0 @@
package nodes
import (
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildapi "github.com/openshift/origin/pkg/build/api"
)
// EnsureBuildConfigNode adds a graph node for the specific build config if it does not exist
func EnsureBuildConfigNode(g osgraph.MutableUniqueGraph, config *buildapi.BuildConfig) *BuildConfigNode {
return osgraph.EnsureUnique(
g,
BuildConfigNodeName(config),
func(node osgraph.Node) graph.Node {
return &BuildConfigNode{
Node: node,
BuildConfig: config,
}
},
).(*BuildConfigNode)
}
// EnsureSourceRepositoryNode adds the specific BuildSource to the graph if it does not already exist.
func EnsureSourceRepositoryNode(g osgraph.MutableUniqueGraph, source buildapi.BuildSource) *SourceRepositoryNode {
switch {
case source.Git != nil:
default:
return nil
}
return osgraph.EnsureUnique(g,
SourceRepositoryNodeName(source),
func(node osgraph.Node) graph.Node {
return &SourceRepositoryNode{node, source}
},
).(*SourceRepositoryNode)
}
// EnsureBuildNode adds a graph node for the build if it does not already exist.
func EnsureBuildNode(g osgraph.MutableUniqueGraph, build *buildapi.Build) *BuildNode {
return osgraph.EnsureUnique(g,
BuildNodeName(build),
func(node osgraph.Node) graph.Node {
return &BuildNode{node, build}
},
).(*BuildNode)
}
-90
View File
@@ -1,90 +0,0 @@
package nodes
import (
"fmt"
"reflect"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildapi "github.com/openshift/origin/pkg/build/api"
)
var (
BuildConfigNodeKind = reflect.TypeOf(buildapi.BuildConfig{}).Name()
BuildNodeKind = reflect.TypeOf(buildapi.Build{}).Name()
// non-api types
SourceRepositoryNodeKind = reflect.TypeOf(buildapi.BuildSource{}).Name()
)
func BuildConfigNodeName(o *buildapi.BuildConfig) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(BuildConfigNodeKind, o)
}
type BuildConfigNode struct {
osgraph.Node
BuildConfig *buildapi.BuildConfig
}
func (n BuildConfigNode) Object() interface{} {
return n.BuildConfig
}
func (n BuildConfigNode) String() string {
return string(BuildConfigNodeName(n.BuildConfig))
}
func (n BuildConfigNode) UniqueName() osgraph.UniqueName {
return BuildConfigNodeName(n.BuildConfig)
}
func (*BuildConfigNode) Kind() string {
return BuildConfigNodeKind
}
func SourceRepositoryNodeName(source buildapi.BuildSource) osgraph.UniqueName {
switch {
case source.Git != nil:
sourceType, uri, ref := "git", source.Git.URI, source.Git.Ref
return osgraph.UniqueName(fmt.Sprintf("%s|%s|%s#%s", SourceRepositoryNodeKind, sourceType, uri, ref))
default:
panic(fmt.Sprintf("invalid build source: %v", source))
}
}
type SourceRepositoryNode struct {
osgraph.Node
Source buildapi.BuildSource
}
func (n SourceRepositoryNode) String() string {
return string(SourceRepositoryNodeName(n.Source))
}
func (SourceRepositoryNode) Kind() string {
return SourceRepositoryNodeKind
}
func BuildNodeName(o *buildapi.Build) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(BuildNodeKind, o)
}
type BuildNode struct {
osgraph.Node
Build *buildapi.Build
}
func (n BuildNode) Object() interface{} {
return n.Build
}
func (n BuildNode) String() string {
return string(BuildNodeName(n.Build))
}
func (n BuildNode) UniqueName() osgraph.UniqueName {
return BuildNodeName(n.Build)
}
func (*BuildNode) Kind() string {
return BuildNodeKind
}
-3
View File
@@ -1,3 +0,0 @@
// Package util contains common functions that are used
// by the rest of the OpenShift build system.
package util
-169
View File
@@ -1,169 +0,0 @@
package util
import (
"fmt"
"strconv"
"strings"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/labels"
"github.com/golang/glog"
buildapi "github.com/openshift/origin/pkg/build/api"
buildclient "github.com/openshift/origin/pkg/build/client"
)
const (
// NoBuildLogsMessage reports that no build logs are available
NoBuildLogsMessage = "No logs are available."
)
// GetBuildName returns name of the build pod.
func GetBuildName(pod *kapi.Pod) string {
if pod == nil {
return ""
}
return pod.Annotations[buildapi.BuildAnnotation]
}
// GetInputReference returns the From ObjectReference associated with the
// BuildStrategy.
func GetInputReference(strategy buildapi.BuildStrategy) *kapi.ObjectReference {
switch {
case strategy.SourceStrategy != nil:
return &strategy.SourceStrategy.From
case strategy.DockerStrategy != nil:
return strategy.DockerStrategy.From
case strategy.CustomStrategy != nil:
return &strategy.CustomStrategy.From
default:
return nil
}
}
// NameFromImageStream returns a concatenated name representing an ImageStream[Tag/Image]
// reference. If the reference does not contain a Namespace, the namespace parameter
// is used instead.
func NameFromImageStream(namespace string, ref *kapi.ObjectReference, tag string) string {
var ret string
if ref.Namespace == "" {
ret = namespace
} else {
ret = ref.Namespace
}
ret = ret + "/" + ref.Name
if tag != "" && strings.Index(ref.Name, ":") == -1 && strings.Index(ref.Name, "@") == -1 {
ret = ret + ":" + tag
}
return ret
}
// IsBuildComplete returns whether the provided build is complete or not
func IsBuildComplete(build *buildapi.Build) bool {
return build.Status.Phase != buildapi.BuildPhaseRunning && build.Status.Phase != buildapi.BuildPhasePending && build.Status.Phase != buildapi.BuildPhaseNew
}
// IsPaused returns true if the provided BuildConfig is paused and cannot be used to create a new Build
func IsPaused(bc *buildapi.BuildConfig) bool {
return strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true"
}
// BuildNumber returns the given build number.
func BuildNumber(build *buildapi.Build) (int64, error) {
annotations := build.GetAnnotations()
if stringNumber, ok := annotations[buildapi.BuildNumberAnnotation]; ok {
return strconv.ParseInt(stringNumber, 10, 64)
}
return 0, fmt.Errorf("build %s/%s does not have %s annotation", build.Namespace, build.Name, buildapi.BuildNumberAnnotation)
}
// BuildRunPolicy returns the scheduling policy for the build based on the
// "queued" label.
func BuildRunPolicy(build *buildapi.Build) buildapi.BuildRunPolicy {
labels := build.GetLabels()
if value, found := labels[buildapi.BuildRunPolicyLabel]; found {
switch value {
case "Parallel":
return buildapi.BuildRunPolicyParallel
case "Serial":
return buildapi.BuildRunPolicySerial
case "SerialLatestOnly":
return buildapi.BuildRunPolicySerialLatestOnly
}
}
glog.V(5).Infof("Build %s/%s does not have start policy label set, using default (Serial)")
return buildapi.BuildRunPolicySerial
}
// BuildNameForConfigVersion returns the name of the version-th build
// for the config that has the provided name.
func BuildNameForConfigVersion(name string, version int) string {
return fmt.Sprintf("%s-%d", name, version)
}
// BuildConfigSelector returns a label Selector which can be used to find all
// builds for a BuildConfig.
func BuildConfigSelector(name string) labels.Selector {
return labels.Set{buildapi.BuildConfigLabel: buildapi.LabelValue(name)}.AsSelector()
}
// BuildConfigSelectorDeprecated returns a label Selector which can be used to find
// all builds for a BuildConfig that use the deprecated labels.
func BuildConfigSelectorDeprecated(name string) labels.Selector {
return labels.Set{buildapi.BuildConfigLabelDeprecated: name}.AsSelector()
}
type buildFilter func(buildapi.Build) bool
// BuildConfigBuilds return a list of builds for the given build config.
// Optionally you can specify a filter function to select only builds that
// matches your criteria.
func BuildConfigBuilds(c buildclient.BuildLister, namespace, name string, filterFunc buildFilter) (*buildapi.BuildList, error) {
result, err := c.List(namespace, kapi.ListOptions{
LabelSelector: BuildConfigSelector(name),
})
if err != nil {
return nil, err
}
if filterFunc == nil {
return result, nil
}
filteredList := &buildapi.BuildList{TypeMeta: result.TypeMeta, ListMeta: result.ListMeta}
for _, b := range result.Items {
if filterFunc(b) {
filteredList.Items = append(filteredList.Items, b)
}
}
return filteredList, nil
}
// ConfigNameForBuild returns the name of the build config from a
// build name.
func ConfigNameForBuild(build *buildapi.Build) string {
if build == nil {
return ""
}
if build.Annotations != nil {
if _, exists := build.Annotations[buildapi.BuildConfigAnnotation]; exists {
return build.Annotations[buildapi.BuildConfigAnnotation]
}
}
if _, exists := build.Labels[buildapi.BuildConfigLabel]; exists {
return build.Labels[buildapi.BuildConfigLabel]
}
return build.Labels[buildapi.BuildConfigLabelDeprecated]
}
// VersionForBuild returns the version from the provided build name.
// If no version can be found, 0 is returned to indicate no version.
func VersionForBuild(build *buildapi.Build) int {
if build == nil {
return 0
}
versionString := build.Annotations[buildapi.BuildNumberAnnotation]
version, err := strconv.Atoi(versionString)
if err != nil {
return 0
}
return version
}
+5
View File
@@ -48,6 +48,7 @@ type Interface interface {
SubjectAccessReviews
LocalSubjectAccessReviewsNamespacer
SelfSubjectRulesReviewsNamespacer
SubjectRulesReviewsNamespacer
TemplatesNamespacer
TemplateConfigsNamespacer
OAuthClientsInterface
@@ -245,6 +246,10 @@ func (c *Client) SelfSubjectRulesReviews(namespace string) SelfSubjectRulesRevie
return newSelfSubjectRulesReviews(c, namespace)
}
func (c *Client) SubjectRulesReviews(namespace string) SubjectRulesReviewInterface {
return newSubjectRulesReviews(c, namespace)
}
func (c *Client) OAuthClients() OAuthClientInterface {
return newOAuthClients(c)
}
+13
View File
@@ -30,6 +30,7 @@ type DeploymentConfigInterface interface {
GetScale(name string) (*extensions.Scale, error)
UpdateScale(scale *extensions.Scale) (*extensions.Scale, error)
UpdateStatus(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
Instantiate(request *deployapi.DeploymentRequest) (*deployapi.DeploymentConfig, error)
}
// deploymentConfigs implements DeploymentConfigsNamespacer interface
@@ -155,6 +156,18 @@ func (c *deploymentConfigs) UpdateStatus(deploymentConfig *deployapi.DeploymentC
return
}
// Instantiate instantiates a new build from build config returning new object or an error
func (c *deploymentConfigs) Instantiate(request *deployapi.DeploymentRequest) (*deployapi.DeploymentConfig, error) {
result := &deployapi.DeploymentConfig{}
resp := c.r.Post().Namespace(c.ns).Resource("deploymentConfigs").Name(request.Name).SubResource("instantiate").Body(request).Do()
var statusCode int
if resp.StatusCode(&statusCode); statusCode == 204 {
return nil, nil
}
err := resp.Into(result)
return result, err
}
type updateConfigFunc func(d *deployapi.DeploymentConfig)
// UpdateConfigWithRetries will try to update a deployment config and ignore any update conflicts.
-7
View File
@@ -68,13 +68,6 @@ func (c *imageStreams) Get(name string) (result *imageapi.ImageStream, err error
return
}
// GetByNamespace returns information about a particular image stream in a particular namespace and error if one occurs.
func (c *imageStreams) GetByNamespace(namespace, name string) (result *imageapi.ImageStream, err error) {
result = &imageapi.ImageStream{}
c.r.Get().Namespace(namespace).Resource("imageStreams").Name(name).Do().Into(result)
return
}
// Create create a new image stream. Returns the server's representation of the image stream and error if one occurs.
func (c *imageStreams) Create(stream *imageapi.ImageStream) (result *imageapi.ImageStream, err error) {
result = &imageapi.ImageStream{}
+7
View File
@@ -17,6 +17,7 @@ type OAuthClientInterface interface {
Get(name string) (*oauthapi.OAuthClient, error)
Delete(name string) error
Watch(opts kapi.ListOptions) (watch.Interface, error)
Update(client *oauthapi.OAuthClient) (*oauthapi.OAuthClient, error)
}
type oauthClients struct {
@@ -55,3 +56,9 @@ func (c *oauthClients) Delete(name string) (err error) {
func (c *oauthClients) Watch(opts kapi.ListOptions) (watch.Interface, error) {
return c.r.Get().Prefix("watch").Resource("oAuthClients").VersionedParams(&opts, kapi.ParameterCodec).Watch()
}
func (c *oauthClients) Update(client *oauthapi.OAuthClient) (result *oauthapi.OAuthClient, err error) {
result = &oauthapi.OAuthClient{}
err = c.r.Put().Resource("oAuthClients").Name(client.Name).Body(client).Do().Into(result)
return
}
+32
View File
@@ -0,0 +1,32 @@
package client
import (
authorizationapi "github.com/openshift/origin/pkg/authorization/api"
)
type SubjectRulesReviewsNamespacer interface {
SubjectRulesReviews(namespace string) SubjectRulesReviewInterface
}
type SubjectRulesReviewInterface interface {
Create(*authorizationapi.SubjectRulesReview) (*authorizationapi.SubjectRulesReview, error)
}
type subjectRulesReviews struct {
r *Client
ns string
}
func newSubjectRulesReviews(c *Client, namespace string) *subjectRulesReviews {
return &subjectRulesReviews{
r: c,
ns: namespace,
}
}
func (c *subjectRulesReviews) Create(subjectRulesReview *authorizationapi.SubjectRulesReview) (result *authorizationapi.SubjectRulesReview, err error) {
result = &authorizationapi.SubjectRulesReview{}
err = c.r.Post().Namespace(c.ns).Resource("subjectRulesReviews").Body(subjectRulesReview).Do().Into(result)
return
}
+25 -11
View File
@@ -6,6 +6,7 @@ import (
"reflect"
"strings"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/restclient"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/third_party/forked/golang/netutil"
@@ -34,11 +35,7 @@ func GetClusterNicknameFromURL(apiServerLocation string) (string, error) {
// GetUserNicknameFromConfig returns "username(as known by the server)/GetClusterNicknameFromConfig". This allows tab completion for switching users to
// work easily and obviously.
func GetUserNicknameFromConfig(clientCfg *restclient.Config) (string, error) {
client, err := osclient.New(clientCfg)
if err != nil {
return "", err
}
userInfo, err := client.Users().Get("~")
userPartOfNick, err := getUserPartOfNickname(clientCfg)
if err != nil {
return "", err
}
@@ -48,7 +45,7 @@ func GetUserNicknameFromConfig(clientCfg *restclient.Config) (string, error) {
return "", err
}
return userInfo.Name + "/" + clusterNick, nil
return userPartOfNick + "/" + clusterNick, nil
}
func GetUserNicknameFromCert(clusterNick string, chain ...*x509.Certificate) (string, error) {
@@ -60,15 +57,32 @@ func GetUserNicknameFromCert(clusterNick string, chain ...*x509.Certificate) (st
return userInfo.GetName() + "/" + clusterNick, nil
}
// GetContextNicknameFromConfig returns "namespace/GetClusterNicknameFromConfig/username(as known by the server)". This allows tab completion for switching projects/context
// to work easily. First tab is the most selective on project. Second stanza in the next most selective on cluster name. The chances of a user trying having
// one projects on a single server that they want to operate against with two identities is low, so username is last.
func GetContextNicknameFromConfig(namespace string, clientCfg *restclient.Config) (string, error) {
func getUserPartOfNickname(clientCfg *restclient.Config) (string, error) {
client, err := osclient.New(clientCfg)
if err != nil {
return "", err
}
userInfo, err := client.Users().Get("~")
if kerrors.IsNotFound(err) {
// if we're talking to kube (or likely talking to kube), take a best guess consistent with login
switch {
case len(clientCfg.BearerToken) > 0:
userInfo.Name = clientCfg.BearerToken
case len(clientCfg.Username) > 0:
userInfo.Name = clientCfg.Username
}
} else if err != nil {
return "", err
}
return userInfo.Name, nil
}
// GetContextNicknameFromConfig returns "namespace/GetClusterNicknameFromConfig/username(as known by the server)". This allows tab completion for switching projects/context
// to work easily. First tab is the most selective on project. Second stanza in the next most selective on cluster name. The chances of a user trying having
// one projects on a single server that they want to operate against with two identities is low, so username is last.
func GetContextNicknameFromConfig(namespace string, clientCfg *restclient.Config) (string, error) {
userPartOfNick, err := getUserPartOfNickname(clientCfg)
if err != nil {
return "", err
}
@@ -78,7 +92,7 @@ func GetContextNicknameFromConfig(namespace string, clientCfg *restclient.Config
return "", err
}
return namespace + "/" + clusterNick + "/" + userInfo.Name, nil
return namespace + "/" + clusterNick + "/" + userPartOfNick, nil
}
func GetContextNickname(namespace, clusterNick, userNick string) string {
@@ -1,319 +0,0 @@
package describe
import (
"fmt"
"sort"
"strings"
"github.com/golang/glog"
"github.com/gonum/graph"
"github.com/gonum/graph/encoding/dot"
"github.com/gonum/graph/path"
kapi "k8s.io/kubernetes/pkg/api"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildedges "github.com/openshift/origin/pkg/build/graph"
buildanalysis "github.com/openshift/origin/pkg/build/graph/analysis"
buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
"github.com/openshift/origin/pkg/client"
imageapi "github.com/openshift/origin/pkg/image/api"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
dotutil "github.com/openshift/origin/pkg/util/dot"
"github.com/openshift/origin/pkg/util/parallel"
)
// NotFoundErr is returned when the imageStreamTag (ist) of interest cannot
// be found in the graph. This doesn't mean though that the IST does not
// exist. A user may have an image stream without a build configuration
// pointing at it. In that case, the IST of interest simply doesn't have
// other dependant ists
type NotFoundErr string
func (e NotFoundErr) Error() string {
return fmt.Sprintf("couldn't find image stream tag: %q", string(e))
}
// ChainDescriber generates extended information about a chain of
// dependencies of an image stream
type ChainDescriber struct {
c client.BuildConfigsNamespacer
namespaces sets.String
outputFormat string
namer osgraph.Namer
}
// NewChainDescriber returns a new ChainDescriber
func NewChainDescriber(c client.BuildConfigsNamespacer, namespaces sets.String, out string) *ChainDescriber {
return &ChainDescriber{c: c, namespaces: namespaces, outputFormat: out, namer: namespacedFormatter{hideNamespace: true}}
}
// MakeGraph will create the graph of all build configurations and the image streams
// they point to via image change triggers in the provided namespace(s)
func (d *ChainDescriber) MakeGraph() (osgraph.Graph, error) {
g := osgraph.New()
loaders := []GraphLoader{}
for namespace := range d.namespaces {
glog.V(4).Infof("Loading build configurations from %q", namespace)
loaders = append(loaders, &bcLoader{namespace: namespace, lister: d.c})
}
loadingFuncs := []func() error{}
for _, loader := range loaders {
loadingFuncs = append(loadingFuncs, loader.Load)
}
if errs := parallel.Run(loadingFuncs...); len(errs) > 0 {
return g, utilerrors.NewAggregate(errs)
}
for _, loader := range loaders {
loader.AddToGraph(g)
}
buildedges.AddAllInputOutputEdges(g)
return g, nil
}
// Describe returns the output of the graph starting from the provided
// image stream tag (name:tag) in namespace. Namespace is needed here
// because image stream tags with the same name can be found across
// different namespaces.
func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag, includeInputImages, reverse bool) (string, error) {
g, err := d.MakeGraph()
if err != nil {
return "", err
}
// Retrieve the imageStreamTag node of interest
istNode := g.Find(imagegraph.ImageStreamTagNodeName(ist))
if istNode == nil {
return "", NotFoundErr(fmt.Sprintf("%q", ist.Name))
}
markers := buildanalysis.FindCircularBuilds(g, d.namer)
if len(markers) > 0 {
for _, marker := range markers {
if strings.Contains(marker.Message, ist.Name) {
return marker.Message, nil
}
}
}
buildInputEdgeKinds := []string{buildedges.BuildTriggerImageEdgeKind}
if includeInputImages {
buildInputEdgeKinds = append(buildInputEdgeKinds, buildedges.BuildInputImageEdgeKind)
}
// Partition down to the subgraph containing the imagestreamtag of interest
var partitioned osgraph.Graph
if reverse {
partitioned = partitionReverse(g, istNode, buildInputEdgeKinds)
} else {
partitioned = partition(g, istNode, buildInputEdgeKinds)
}
switch strings.ToLower(d.outputFormat) {
case "dot":
data, err := dot.Marshal(partitioned, dotutil.Quote(ist.Name), "", " ", false)
if err != nil {
return "", err
}
return string(data), nil
case "":
return d.humanReadableOutput(partitioned, d.namer, istNode, reverse), nil
}
return "", fmt.Errorf("unknown specified format %q", d.outputFormat)
}
// partition the graph down to a subgraph starting from the given root
func partition(g osgraph.Graph, root graph.Node, buildInputEdgeKinds []string) osgraph.Graph {
// Filter out all but BuildConfig and ImageStreamTag nodes
nodeFn := osgraph.NodesOfKind(buildgraph.BuildConfigNodeKind, imagegraph.ImageStreamTagNodeKind)
// Filter out all but BuildInputImage and BuildOutput edges
edgeKinds := []string{}
edgeKinds = append(edgeKinds, buildInputEdgeKinds...)
edgeKinds = append(edgeKinds, buildedges.BuildOutputEdgeKind)
edgeFn := osgraph.EdgesOfKind(edgeKinds...)
sub := g.Subgraph(nodeFn, edgeFn)
// Filter out inbound edges to the IST of interest
edgeFn = osgraph.RemoveInboundEdges([]graph.Node{root})
sub = sub.Subgraph(nodeFn, edgeFn)
// Check all paths leading from the root node, collect any
// node found in them, and create the desired subgraph
desired := []graph.Node{root}
paths := path.DijkstraAllPaths(sub)
for _, node := range sub.Nodes() {
if node == root {
continue
}
path, _, _ := paths.Between(root, node)
if len(path) != 0 {
desired = append(desired, node)
}
}
return sub.SubgraphWithNodes(desired, osgraph.ExistingDirectEdge)
}
// partitionReverse the graph down to a subgraph starting from the given root
func partitionReverse(g osgraph.Graph, root graph.Node, buildInputEdgeKinds []string) osgraph.Graph {
// Filter out all but BuildConfig and ImageStreamTag nodes
nodeFn := osgraph.NodesOfKind(buildgraph.BuildConfigNodeKind, imagegraph.ImageStreamTagNodeKind)
// Filter out all but BuildInputImage and BuildOutput edges
edgeKinds := []string{}
edgeKinds = append(edgeKinds, buildInputEdgeKinds...)
edgeKinds = append(edgeKinds, buildedges.BuildOutputEdgeKind)
edgeFn := osgraph.EdgesOfKind(edgeKinds...)
sub := g.Subgraph(nodeFn, edgeFn)
// Filter out inbound edges to the IST of interest
edgeFn = osgraph.RemoveOutboundEdges([]graph.Node{root})
sub = sub.Subgraph(nodeFn, edgeFn)
// Check all paths leading from the root node, collect any
// node found in them, and create the desired subgraph
desired := []graph.Node{root}
paths := path.DijkstraAllPaths(sub)
for _, node := range sub.Nodes() {
if node == root {
continue
}
path, _, _ := paths.Between(node, root)
if len(path) != 0 {
desired = append(desired, node)
}
}
return sub.SubgraphWithNodes(desired, osgraph.ExistingDirectEdge)
}
// humanReadableOutput traverses the provided graph using DFS and outputs it
// in a human-readable format. It starts from the provided root, assuming it
// is an imageStreamTag node and continues to the rest of the graph handling
// only imageStreamTag and buildConfig nodes.
func (d *ChainDescriber) humanReadableOutput(g osgraph.Graph, f osgraph.Namer, root graph.Node, reverse bool) string {
if reverse {
g = g.EdgeSubgraph(osgraph.ReverseExistingDirectEdge)
}
var singleNamespace bool
if len(d.namespaces) == 1 && !d.namespaces.Has(kapi.NamespaceAll) {
singleNamespace = true
}
depth := map[graph.Node]int{
root: 0,
}
out := ""
dfs := &DepthFirst{
Visit: func(u, v graph.Node) {
depth[v] = depth[u] + 1
},
}
until := func(node graph.Node) bool {
var info string
switch t := node.(type) {
case *imagegraph.ImageStreamTagNode:
info = outputHelper(f.ResourceName(t), t.Namespace, singleNamespace)
case *buildgraph.BuildConfigNode:
info = outputHelper(f.ResourceName(t), t.BuildConfig.Namespace, singleNamespace)
default:
panic("this graph contains node kinds other than imageStreamTags and buildConfigs")
}
if depth[node] != 0 {
out += "\n"
}
out += fmt.Sprintf("%s", strings.Repeat("\t", depth[node]))
out += fmt.Sprintf("%s", info)
return false
}
dfs.Walk(g, root, until)
return out
}
// outputHelper returns resource/name in a single namespace, <namespace resource/name>
// in multiple namespaces
func outputHelper(info, namespace string, singleNamespace bool) string {
if singleNamespace {
return info
}
return fmt.Sprintf("<%s %s>", namespace, info)
}
// DepthFirst implements stateful depth-first graph traversal.
// Modifies behavior of visitor.DepthFirst to allow nodes to be visited multiple
// times as long as they're not in the current stack
type DepthFirst struct {
EdgeFilter func(graph.Edge) bool
Visit func(u, v graph.Node)
stack NodeStack
}
// Walk performs a depth-first traversal of the graph g starting from the given node
func (d *DepthFirst) Walk(g graph.Graph, from graph.Node, until func(graph.Node) bool) graph.Node {
return d.visit(g, from, until)
}
func (d *DepthFirst) visit(g graph.Graph, t graph.Node, until func(graph.Node) bool) graph.Node {
if until != nil && until(t) {
return t
}
d.stack.Push(t)
children := osgraph.ByID(g.From(t))
sort.Sort(children)
for _, n := range children {
if d.EdgeFilter != nil && !d.EdgeFilter(g.Edge(t, n)) {
continue
}
if d.visited(n.ID()) {
continue
}
if d.Visit != nil {
d.Visit(t, n)
}
result := d.visit(g, n, until)
if result != nil {
return result
}
}
d.stack.Pop()
return nil
}
func (d *DepthFirst) visited(id int) bool {
for _, n := range d.stack {
if n.ID() == id {
return true
}
}
return false
}
// NodeStack implements a LIFO stack of graph.Node.
// NodeStack is internal only in go 1.5.
type NodeStack []graph.Node
// Len returns the number of graph.Nodes on the stack.
func (s *NodeStack) Len() int { return len(*s) }
// Pop returns the last graph.Node on the stack and removes it
// from the stack.
func (s *NodeStack) Pop() graph.Node {
v := *s
v, n := v[:len(v)-1], v[len(v)-1]
*s = v
return n
}
// Push adds the node n to the stack at the last position.
func (s *NodeStack) Push(n graph.Node) { *s = append(*s, n) }
-417
View File
@@ -1,417 +0,0 @@
package describe
import (
"fmt"
"io"
"sort"
"strconv"
"strings"
"text/tabwriter"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
rcutils "k8s.io/kubernetes/pkg/controller/replication"
kctl "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/labels"
"github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deployedges "github.com/openshift/origin/pkg/deploy/graph"
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
deployutil "github.com/openshift/origin/pkg/deploy/util"
imageapi "github.com/openshift/origin/pkg/image/api"
)
const (
// maxDisplayDeployments is the number of deployments to show when describing
// deployment configuration.
maxDisplayDeployments = 3
// maxDisplayDeploymentsEvents is the number of events to display when
// describing the deployment configuration.
// TODO: Make the estimation of this number more sophisticated and make this
// number configurable via DescriberSettings
maxDisplayDeploymentsEvents = 8
)
// DeploymentConfigDescriber generates information about a DeploymentConfig
type DeploymentConfigDescriber struct {
osClient client.Interface
kubeClient kclient.Interface
config *deployapi.DeploymentConfig
}
// NewDeploymentConfigDescriber returns a new DeploymentConfigDescriber
func NewDeploymentConfigDescriber(client client.Interface, kclient kclient.Interface, config *deployapi.DeploymentConfig) *DeploymentConfigDescriber {
return &DeploymentConfigDescriber{
osClient: client,
kubeClient: kclient,
config: config,
}
}
// Describe returns the description of a DeploymentConfig
func (d *DeploymentConfigDescriber) Describe(namespace, name string, settings kctl.DescriberSettings) (string, error) {
var deploymentConfig *deployapi.DeploymentConfig
if d.config != nil {
// If a deployment config is already provided use that.
// This is used by `oc rollback --dry-run`.
deploymentConfig = d.config
} else {
var err error
deploymentConfig, err = d.osClient.DeploymentConfigs(namespace).Get(name)
if err != nil {
return "", err
}
}
return tabbedString(func(out *tabwriter.Writer) error {
formatMeta(out, deploymentConfig.ObjectMeta)
var (
deploymentsHistory []kapi.ReplicationController
activeDeploymentName string
)
if d.config == nil {
if rcs, err := d.kubeClient.ReplicationControllers(namespace).List(kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(deploymentConfig.Name)}); err == nil {
deploymentsHistory = rcs.Items
}
}
if deploymentConfig.Status.LatestVersion == 0 {
formatString(out, "Latest Version", "Not deployed")
} else {
formatString(out, "Latest Version", strconv.FormatInt(deploymentConfig.Status.LatestVersion, 10))
}
printDeploymentConfigSpec(d.kubeClient, *deploymentConfig, out)
fmt.Fprintln(out)
latestDeploymentName := deployutil.LatestDeploymentNameForConfig(deploymentConfig)
if activeDeployment := deployutil.ActiveDeployment(deploymentConfig, deploymentsHistory); activeDeployment != nil {
activeDeploymentName = activeDeployment.Name
}
var deployment *kapi.ReplicationController
isNotDeployed := len(deploymentsHistory) == 0
for _, item := range deploymentsHistory {
if item.Name == latestDeploymentName {
deployment = &item
}
}
if isNotDeployed {
formatString(out, "Latest Deployment", "<none>")
} else {
header := fmt.Sprintf("Deployment #%d (latest)", deployutil.DeploymentVersionFor(deployment))
// Show details if the current deployment is the active one or it is the
// initial deployment.
printDeploymentRc(deployment, d.kubeClient, out, header, (deployment.Name == activeDeploymentName) || len(deploymentsHistory) == 1)
}
// We don't show the deployment history when running `oc rollback --dry-run`.
if d.config == nil && !isNotDeployed {
sorted := deploymentsHistory
sort.Sort(sort.Reverse(rcutils.OverlappingControllers(sorted)))
counter := 1
for _, item := range sorted {
if item.Name != latestDeploymentName && deploymentConfig.Name == deployutil.DeploymentConfigNameFor(&item) {
header := fmt.Sprintf("Deployment #%d", deployutil.DeploymentVersionFor(&item))
printDeploymentRc(&item, d.kubeClient, out, header, item.Name == activeDeploymentName)
counter++
}
if counter == maxDisplayDeployments {
break
}
}
}
if settings.ShowEvents {
// Events
if events, err := d.kubeClient.Events(deploymentConfig.Namespace).Search(deploymentConfig); err == nil && events != nil {
latestDeploymentEvents := &kapi.EventList{Items: []kapi.Event{}}
for i := len(events.Items); i != 0 && i > len(events.Items)-maxDisplayDeploymentsEvents; i-- {
latestDeploymentEvents.Items = append(latestDeploymentEvents.Items, events.Items[i-1])
}
fmt.Fprintln(out)
kctl.DescribeEvents(latestDeploymentEvents, out)
}
}
return nil
})
}
func multilineStringArray(sep, indent string, args ...string) string {
for i, s := range args {
if strings.HasSuffix(s, "\n") {
s = strings.TrimSuffix(s, "\n")
}
if strings.Contains(s, "\n") {
s = "\n" + indent + strings.Join(strings.Split(s, "\n"), "\n"+indent)
}
args[i] = s
}
strings.TrimRight(args[len(args)-1], "\n ")
return strings.Join(args, " ")
}
func printStrategy(strategy deployapi.DeploymentStrategy, indent string, w *tabwriter.Writer) {
if strategy.CustomParams != nil {
if len(strategy.CustomParams.Image) == 0 {
fmt.Fprintf(w, "%sImage:\t%s\n", indent, "<default>")
} else {
fmt.Fprintf(w, "%sImage:\t%s\n", indent, strategy.CustomParams.Image)
}
if len(strategy.CustomParams.Environment) > 0 {
fmt.Fprintf(w, "%sEnvironment:\t%s\n", indent, formatLabels(convertEnv(strategy.CustomParams.Environment)))
}
if len(strategy.CustomParams.Command) > 0 {
fmt.Fprintf(w, "%sCommand:\t%v\n", indent, multilineStringArray(" ", "\t ", strategy.CustomParams.Command...))
}
}
if strategy.RecreateParams != nil {
pre := strategy.RecreateParams.Pre
mid := strategy.RecreateParams.Mid
post := strategy.RecreateParams.Post
if pre != nil {
printHook("Pre-deployment", pre, indent, w)
}
if mid != nil {
printHook("Mid-deployment", mid, indent, w)
}
if post != nil {
printHook("Post-deployment", post, indent, w)
}
}
if strategy.RollingParams != nil {
pre := strategy.RollingParams.Pre
post := strategy.RollingParams.Post
if pre != nil {
printHook("Pre-deployment", pre, indent, w)
}
if post != nil {
printHook("Post-deployment", post, indent, w)
}
}
}
func printHook(prefix string, hook *deployapi.LifecycleHook, indent string, w io.Writer) {
if hook.ExecNewPod != nil {
fmt.Fprintf(w, "%s%s hook (pod type, failure policy: %s):\n", indent, prefix, hook.FailurePolicy)
fmt.Fprintf(w, "%s Container:\t%s\n", indent, hook.ExecNewPod.ContainerName)
fmt.Fprintf(w, "%s Command:\t%v\n", indent, multilineStringArray(" ", "\t ", hook.ExecNewPod.Command...))
if len(hook.ExecNewPod.Env) > 0 {
fmt.Fprintf(w, "%s Env:\t%s\n", indent, formatLabels(convertEnv(hook.ExecNewPod.Env)))
}
}
if len(hook.TagImages) > 0 {
fmt.Fprintf(w, "%s%s hook (tag images, failure policy: %s):\n", indent, prefix, hook.FailurePolicy)
for _, image := range hook.TagImages {
fmt.Fprintf(w, "%s Tag:\tcontainer %s to %s %s %s\n", indent, image.ContainerName, image.To.Kind, image.To.Name, image.To.Namespace)
}
}
}
func printTriggers(triggers []deployapi.DeploymentTriggerPolicy, w *tabwriter.Writer) {
if len(triggers) == 0 {
formatString(w, "Triggers", "<none>")
return
}
labels := []string{}
for _, t := range triggers {
switch t.Type {
case deployapi.DeploymentTriggerOnConfigChange:
labels = append(labels, "Config")
case deployapi.DeploymentTriggerOnImageChange:
if len(t.ImageChangeParams.From.Name) > 0 {
name, tag, _ := imageapi.SplitImageStreamTag(t.ImageChangeParams.From.Name)
labels = append(labels, fmt.Sprintf("Image(%s@%s, auto=%v)", name, tag, t.ImageChangeParams.Automatic))
}
}
}
desc := strings.Join(labels, ", ")
formatString(w, "Triggers", desc)
}
func printDeploymentConfigSpec(kc kclient.Interface, dc deployapi.DeploymentConfig, w *tabwriter.Writer) error {
spec := dc.Spec
// Selector
formatString(w, "Selector", formatLabels(spec.Selector))
// Replicas
test := ""
if spec.Test {
test = " (test, will be scaled down between deployments)"
}
formatString(w, "Replicas", fmt.Sprintf("%d%s", spec.Replicas, test))
if spec.Paused {
formatString(w, "Paused", "yes")
}
// Autoscaling info
printAutoscalingInfo(deployapi.Resource("DeploymentConfig"), dc.Namespace, dc.Name, kc, w)
// Triggers
printTriggers(spec.Triggers, w)
// Strategy
formatString(w, "Strategy", spec.Strategy.Type)
printStrategy(spec.Strategy, " ", w)
if dc.Spec.MinReadySeconds > 0 {
formatString(w, "MinReadySeconds", fmt.Sprintf("%d", spec.MinReadySeconds))
}
// Pod template
fmt.Fprintf(w, "Template:\n")
kctl.DescribePodTemplate(spec.Template, w)
return nil
}
// TODO: Move this upstream
func printAutoscalingInfo(res unversioned.GroupResource, namespace, name string, kclient kclient.Interface, w *tabwriter.Writer) {
hpaList, err := kclient.Autoscaling().HorizontalPodAutoscalers(namespace).List(kapi.ListOptions{LabelSelector: labels.Everything()})
if err != nil {
return
}
scaledBy := []autoscaling.HorizontalPodAutoscaler{}
for _, hpa := range hpaList.Items {
if hpa.Spec.ScaleTargetRef.Name == name && hpa.Spec.ScaleTargetRef.Kind == res.String() {
scaledBy = append(scaledBy, hpa)
}
}
for _, hpa := range scaledBy {
cpuUtil := ""
if hpa.Spec.TargetCPUUtilizationPercentage != nil {
cpuUtil = fmt.Sprintf(", triggered at %d%% CPU usage", *hpa.Spec.TargetCPUUtilizationPercentage)
}
fmt.Fprintf(w, "Autoscaling:\tbetween %d and %d replicas%s\n", *hpa.Spec.MinReplicas, hpa.Spec.MaxReplicas, cpuUtil)
// TODO: Print a warning in case of multiple hpas.
// Related oc status PR: https://github.com/openshift/origin/pull/7799
break
}
}
func printDeploymentRc(deployment *kapi.ReplicationController, kubeClient kclient.Interface, w io.Writer, header string, verbose bool) error {
if len(header) > 0 {
fmt.Fprintf(w, "%v:\n", header)
}
if verbose {
fmt.Fprintf(w, "\tName:\t%s\n", deployment.Name)
}
timeAt := strings.ToLower(formatRelativeTime(deployment.CreationTimestamp.Time))
fmt.Fprintf(w, "\tCreated:\t%s ago\n", timeAt)
fmt.Fprintf(w, "\tStatus:\t%s\n", deployutil.DeploymentStatusFor(deployment))
fmt.Fprintf(w, "\tReplicas:\t%d current / %d desired\n", deployment.Status.Replicas, deployment.Spec.Replicas)
if verbose {
fmt.Fprintf(w, "\tSelector:\t%s\n", formatLabels(deployment.Spec.Selector))
fmt.Fprintf(w, "\tLabels:\t%s\n", formatLabels(deployment.Labels))
running, waiting, succeeded, failed, err := getPodStatusForDeployment(deployment, kubeClient)
if err != nil {
return err
}
fmt.Fprintf(w, "\tPods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed)
}
return nil
}
func getPodStatusForDeployment(deployment *kapi.ReplicationController, kubeClient kclient.Interface) (running, waiting, succeeded, failed int, err error) {
rcPods, err := kubeClient.Pods(deployment.Namespace).List(kapi.ListOptions{LabelSelector: labels.Set(deployment.Spec.Selector).AsSelector()})
if err != nil {
return
}
for _, pod := range rcPods.Items {
switch pod.Status.Phase {
case kapi.PodRunning:
running++
case kapi.PodPending:
waiting++
case kapi.PodSucceeded:
succeeded++
case kapi.PodFailed:
failed++
}
}
return
}
type LatestDeploymentsDescriber struct {
count int
osClient client.Interface
kubeClient kclient.Interface
}
// NewLatestDeploymentsDescriber lists the latest deployments limited to "count". In case count == -1, list back to the last successful.
func NewLatestDeploymentsDescriber(client client.Interface, kclient kclient.Interface, count int) *LatestDeploymentsDescriber {
return &LatestDeploymentsDescriber{
count: count,
osClient: client,
kubeClient: kclient,
}
}
// Describe returns the description of the latest deployments for a config
func (d *LatestDeploymentsDescriber) Describe(namespace, name string) (string, error) {
var f formatter
config, err := d.osClient.DeploymentConfigs(namespace).Get(name)
if err != nil {
return "", err
}
var deployments []kapi.ReplicationController
if d.count == -1 || d.count > 1 {
list, err := d.kubeClient.ReplicationControllers(namespace).List(kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(name)})
if err != nil && !kerrors.IsNotFound(err) {
return "", err
}
deployments = list.Items
} else {
deploymentName := deployutil.LatestDeploymentNameForConfig(config)
deployment, err := d.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName)
if err != nil && !kerrors.IsNotFound(err) {
return "", err
}
if deployment != nil {
deployments = []kapi.ReplicationController{*deployment}
}
}
g := graph.New()
dcNode := deploygraph.EnsureDeploymentConfigNode(g, config)
for i := range deployments {
kubegraph.EnsureReplicationControllerNode(g, &deployments[i])
}
deployedges.AddTriggerEdges(g, dcNode)
deployedges.AddDeploymentEdges(g, dcNode)
activeDeployment, inactiveDeployments := deployedges.RelevantDeployments(g, dcNode)
return tabbedString(func(out *tabwriter.Writer) error {
descriptions := describeDeployments(f, dcNode, activeDeployment, inactiveDeployments, nil, d.count)
for i, description := range descriptions {
descriptions[i] = fmt.Sprintf("%v %v", name, description)
}
printLines(out, "", 0, descriptions...)
return nil
})
}
File diff suppressed because it is too large Load Diff
-427
View File
@@ -1,427 +0,0 @@
package describe
import (
"bytes"
"fmt"
"regexp"
"strings"
"text/tabwriter"
"time"
units "github.com/docker/go-units"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/sets"
buildapi "github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/client"
imageapi "github.com/openshift/origin/pkg/image/api"
)
const emptyString = "<none>"
func tabbedString(f func(*tabwriter.Writer) error) (string, error) {
out := new(tabwriter.Writer)
buf := &bytes.Buffer{}
out.Init(buf, 0, 8, 1, '\t', 0)
err := f(out)
if err != nil {
return "", err
}
out.Flush()
str := string(buf.String())
return str, nil
}
func toString(v interface{}) string {
value := fmt.Sprintf("%v", v)
if len(value) == 0 {
value = emptyString
}
return value
}
func bold(v interface{}) string {
return "\033[1m" + toString(v) + "\033[0m"
}
func convertEnv(env []api.EnvVar) map[string]string {
result := make(map[string]string, len(env))
for _, e := range env {
result[e.Name] = toString(e.Value)
}
return result
}
func formatEnv(env api.EnvVar) string {
if env.ValueFrom != nil && env.ValueFrom.FieldRef != nil {
return fmt.Sprintf("%s=<%s>", env.Name, env.ValueFrom.FieldRef.FieldPath)
}
return fmt.Sprintf("%s=%s", env.Name, env.Value)
}
func formatString(out *tabwriter.Writer, label string, v interface{}) {
fmt.Fprintf(out, fmt.Sprintf("%s:\t%s\n", label, toString(v)))
}
func formatTime(out *tabwriter.Writer, label string, t time.Time) {
fmt.Fprintf(out, fmt.Sprintf("%s:\t%s ago\n", label, formatRelativeTime(t)))
}
func formatLabels(labelMap map[string]string) string {
return labels.Set(labelMap).String()
}
func extractAnnotations(annotations map[string]string, keys ...string) ([]string, map[string]string) {
extracted := make([]string, len(keys))
remaining := make(map[string]string)
for k, v := range annotations {
remaining[k] = v
}
for i, key := range keys {
extracted[i] = remaining[key]
delete(remaining, key)
}
return extracted, remaining
}
func formatMapStringString(out *tabwriter.Writer, label string, items map[string]string) {
keys := sets.NewString()
for k := range items {
keys.Insert(k)
}
if keys.Len() == 0 {
formatString(out, label, "")
return
}
for i, key := range keys.List() {
if i == 0 {
formatString(out, label, fmt.Sprintf("%s=%s", key, items[key]))
} else {
fmt.Fprintf(out, "%s\t%s=%s\n", "", key, items[key])
}
}
}
func formatAnnotations(out *tabwriter.Writer, m api.ObjectMeta, prefix string) {
values, annotations := extractAnnotations(m.Annotations, "description")
if len(values[0]) > 0 {
formatString(out, prefix+"Description", values[0])
}
formatMapStringString(out, prefix+"Annotations", annotations)
}
var timeNowFn = func() time.Time {
return time.Now()
}
// Receives a time.Duration and returns Docker go-utils'
// human-readable output
func formatToHumanDuration(dur time.Duration) string {
return units.HumanDuration(dur)
}
func formatRelativeTime(t time.Time) string {
return units.HumanDuration(timeNowFn().Sub(t))
}
// FormatRelativeTime converts a time field into a human readable age string (hours, minutes, days).
func FormatRelativeTime(t time.Time) string {
return formatRelativeTime(t)
}
func formatMeta(out *tabwriter.Writer, m api.ObjectMeta) {
formatString(out, "Name", m.Name)
formatString(out, "Namespace", m.Namespace)
if !m.CreationTimestamp.IsZero() {
formatTime(out, "Created", m.CreationTimestamp.Time)
}
formatMapStringString(out, "Labels", m.Labels)
formatAnnotations(out, m, "")
}
// DescribeWebhook holds the URL information about a webhook and for generic
// webhooks it tells us if we allow env variables.
type DescribeWebhook struct {
URL string
AllowEnv *bool
}
// webhookDescribe returns a map of webhook trigger types and its corresponding
// information.
func webHooksDescribe(triggers []buildapi.BuildTriggerPolicy, name, namespace string, cli client.BuildConfigsNamespacer) map[string][]DescribeWebhook {
result := map[string][]DescribeWebhook{}
for _, trigger := range triggers {
var webHookTrigger string
var allowEnv *bool
switch trigger.Type {
case buildapi.GitHubWebHookBuildTriggerType:
webHookTrigger = trigger.GitHubWebHook.Secret
case buildapi.GenericWebHookBuildTriggerType:
webHookTrigger = trigger.GenericWebHook.Secret
allowEnv = &trigger.GenericWebHook.AllowEnv
default:
continue
}
webHookDesc := result[string(trigger.Type)]
if len(webHookTrigger) == 0 {
continue
}
var urlStr string
url, err := cli.BuildConfigs(namespace).WebHookURL(name, &trigger)
if err != nil {
urlStr = fmt.Sprintf("<error: %s>", err.Error())
} else {
urlStr = url.String()
}
webHookDesc = append(webHookDesc,
DescribeWebhook{
URL: urlStr,
AllowEnv: allowEnv,
})
result[string(trigger.Type)] = webHookDesc
}
return result
}
var reLongImageID = regexp.MustCompile(`[a-f0-9]{60,}$`)
// shortenImagePullSpec returns a version of the pull spec intended for
// display, which may result in the image not being usable via cut-and-paste
// for users.
func shortenImagePullSpec(spec string) string {
if reLongImageID.MatchString(spec) {
return spec[:len(spec)-50]
}
return spec
}
func formatImageStreamTags(out *tabwriter.Writer, stream *imageapi.ImageStream) {
if len(stream.Status.Tags) == 0 && len(stream.Spec.Tags) == 0 {
fmt.Fprintf(out, "Tags:\t<none>\n")
return
}
now := timeNowFn()
images := make(map[string]string)
for tag, tags := range stream.Status.Tags {
for _, item := range tags.Items {
switch {
case len(item.Image) > 0:
if _, ok := images[item.Image]; !ok {
images[item.Image] = tag
}
case len(item.DockerImageReference) > 0:
if _, ok := images[item.DockerImageReference]; !ok {
images[item.Image] = item.DockerImageReference
}
}
}
}
sortedTags := []string{}
for k := range stream.Status.Tags {
sortedTags = append(sortedTags, k)
}
var localReferences sets.String
var referentialTags map[string]sets.String
for k := range stream.Spec.Tags {
if target, _, ok, multiple := imageapi.FollowTagReference(stream, k); ok && multiple {
if referentialTags == nil {
referentialTags = make(map[string]sets.String)
}
if localReferences == nil {
localReferences = sets.NewString()
}
localReferences.Insert(k)
v := referentialTags[target]
if v == nil {
v = sets.NewString()
referentialTags[target] = v
}
v.Insert(k)
}
if _, ok := stream.Status.Tags[k]; !ok {
sortedTags = append(sortedTags, k)
}
}
fmt.Fprintf(out, "Unique Images:\t%d\nTags:\t%d\n\n", len(images), len(sortedTags))
first := true
imageapi.PrioritizeTags(sortedTags)
for _, tag := range sortedTags {
if localReferences.Has(tag) {
continue
}
if first {
first = false
} else {
fmt.Fprintf(out, "\n")
}
taglist, _ := stream.Status.Tags[tag]
tagRef, hasSpecTag := stream.Spec.Tags[tag]
scheduled := false
insecure := false
importing := false
var name string
if hasSpecTag && tagRef.From != nil {
if len(tagRef.From.Namespace) > 0 && tagRef.From.Namespace != stream.Namespace {
name = fmt.Sprintf("%s/%s", tagRef.From.Namespace, tagRef.From.Name)
} else {
name = tagRef.From.Name
}
scheduled, insecure = tagRef.ImportPolicy.Scheduled, tagRef.ImportPolicy.Insecure
gen := imageapi.LatestObservedTagGeneration(stream, tag)
importing = !tagRef.Reference && tagRef.Generation != nil && *tagRef.Generation != gen
}
// updates whenever tag :5.2 is changed
// :latest (30 minutes ago) -> 102.205.358.453/foo/bar@sha256:abcde734
// error: last import failed 20 minutes ago
// updates automatically from index.docker.io/mysql/bar
// will use insecure HTTPS connections or HTTP
//
// MySQL 5.5
// ---------
// Describes a system for updating based on practical changes to a database system
// with some other data involved
//
// 20 minutes ago <import failed>
// Failed to locate the server in time
// 30 minutes ago 102.205.358.453/foo/bar@sha256:abcdef
// 1 hour ago 102.205.358.453/foo/bar@sha256:bfedfc
//var shortErrors []string
/*
var internalReference *imageapi.DockerImageReference
if value := stream.Status.DockerImageRepository; len(value) > 0 {
ref, err := imageapi.ParseDockerImageReference(value)
if err != nil {
internalReference = &ref
}
}
*/
if referentialTags[tag].Len() > 0 {
references := referentialTags[tag].List()
imageapi.PrioritizeTags(references)
fmt.Fprintf(out, "%s (%s)\n", tag, strings.Join(references, ", "))
} else {
fmt.Fprintf(out, "%s\n", tag)
}
switch {
case !hasSpecTag || tagRef.From == nil:
fmt.Fprintf(out, " pushed image\n")
case tagRef.From.Kind == "ImageStreamTag":
switch {
case tagRef.Reference:
fmt.Fprintf(out, " reference to %s\n", name)
case scheduled:
fmt.Fprintf(out, " updates automatically from %s\n", name)
default:
fmt.Fprintf(out, " tagged from %s\n", name)
}
case tagRef.From.Kind == "DockerImage":
switch {
case tagRef.Reference:
fmt.Fprintf(out, " reference to registry %s\n", name)
case scheduled:
fmt.Fprintf(out, " updates automatically from registry %s\n", name)
default:
fmt.Fprintf(out, " tagged from %s\n", name)
}
case tagRef.From.Kind == "ImageStreamImage":
switch {
case tagRef.Reference:
fmt.Fprintf(out, " reference to image %s\n", name)
default:
fmt.Fprintf(out, " tagged from %s\n", name)
}
default:
switch {
case tagRef.Reference:
fmt.Fprintf(out, " reference to %s %s\n", tagRef.From.Kind, name)
default:
fmt.Fprintf(out, " updates from %s %s\n", tagRef.From.Kind, name)
}
}
if insecure {
fmt.Fprintf(out, " will use insecure HTTPS or HTTP connections\n")
}
fmt.Fprintln(out)
extraOutput := false
if d := tagRef.Annotations["description"]; len(d) > 0 {
fmt.Fprintf(out, " %s\n", d)
extraOutput = true
}
if t := tagRef.Annotations["tags"]; len(t) > 0 {
fmt.Fprintf(out, " Tags: %s\n", strings.Join(strings.Split(t, ","), ", "))
extraOutput = true
}
if t := tagRef.Annotations["supports"]; len(t) > 0 {
fmt.Fprintf(out, " Supports: %s\n", strings.Join(strings.Split(t, ","), ", "))
extraOutput = true
}
if t := tagRef.Annotations["sampleRepo"]; len(t) > 0 {
fmt.Fprintf(out, " Example Repo: %s\n", t)
extraOutput = true
}
if extraOutput {
fmt.Fprintln(out)
}
if importing {
fmt.Fprintf(out, " ~ importing latest image ...\n")
}
for i := range taglist.Conditions {
condition := &taglist.Conditions[i]
switch condition.Type {
case imageapi.ImportSuccess:
if condition.Status == api.ConditionFalse {
d := now.Sub(condition.LastTransitionTime.Time)
fmt.Fprintf(out, " ! error: Import failed (%s): %s\n %s ago\n", condition.Reason, condition.Message, units.HumanDuration(d))
}
}
}
if len(taglist.Items) == 0 {
continue
}
for i, event := range taglist.Items {
d := now.Sub(event.Created.Time)
if i == 0 {
fmt.Fprintf(out, " * %s\n", event.DockerImageReference)
} else {
fmt.Fprintf(out, " %s\n", event.DockerImageReference)
}
ref, err := imageapi.ParseDockerImageReference(event.DockerImageReference)
id := event.Image
if len(id) > 0 && err == nil && ref.ID != id {
fmt.Fprintf(out, " %s ago\t%s\n", units.HumanDuration(d), id)
} else {
fmt.Fprintf(out, " %s ago\n", units.HumanDuration(d))
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-176
View File
@@ -1,176 +0,0 @@
package flagtypes
import (
"fmt"
"net"
"net/url"
"strconv"
"strings"
)
// urlPrefixes is the list of string prefix values that may indicate a URL
// is present.
var urlPrefixes = []string{"http://", "https://", "tcp://"}
// Addr is a flag type that attempts to load a host, IP, host:port, or
// URL value from a string argument. It tracks whether the value was set
// and allows the caller to provide defaults for the scheme and port.
type Addr struct {
// Specified by the caller
DefaultScheme string
DefaultPort int
AllowPrefix bool
// Provided will be true if Set is invoked
Provided bool
// Value is the exact value provided on the flag
Value string
// URL represents the user input. The Host field is guaranteed
// to be set if Provided is true
URL *url.URL
// Host is the hostname or IP portion of the user input
Host string
// IPv6Host is true if the hostname appears to be an IPv6 input
IPv6Host bool
// Port is the port portion of the user input. Will be 0 if no port was found
// and no default port could be established.
Port int
}
// Default creates a new Address with the value set
func (a Addr) Default() Addr {
if err := a.Set(a.Value); err != nil {
panic(err)
}
a.Provided = false
return a
}
// String returns the string representation of the Addr
func (a *Addr) String() string {
if a.URL == nil {
return a.Value
}
return a.URL.String()
}
// Set attempts to set a string value to an address
func (a *Addr) Set(value string) error {
scheme := a.DefaultScheme
if len(scheme) == 0 {
scheme = "tcp"
}
addr := &url.URL{
Scheme: scheme,
}
switch {
case a.isURL(value):
parsed, err := url.Parse(value)
if err != nil {
return fmt.Errorf("not a valid URL: %v", err)
}
if !a.AllowPrefix {
parsed.Path = ""
}
parsed.RawQuery = ""
parsed.Fragment = ""
if strings.Contains(parsed.Host, ":") {
host, port, err := net.SplitHostPort(parsed.Host)
if err != nil {
return fmt.Errorf("not a valid host:port: %v", err)
}
portNum, err := strconv.ParseUint(port, 10, 64)
if err != nil {
return fmt.Errorf("not a valid port: %v", err)
}
a.Host = host
a.Port = int(portNum)
} else {
port := 0
switch parsed.Scheme {
case "http":
port = 80
case "https":
port = 443
default:
return fmt.Errorf("no port specified")
}
a.Host = parsed.Host
a.Port = port
}
addr = parsed
case isIPv6Host(value):
a.Host = value
a.Port = a.DefaultPort
case strings.Contains(value, ":"):
host, port, err := net.SplitHostPort(value)
if err != nil {
return fmt.Errorf("not a valid host:port: %v", err)
}
portNum, err := strconv.ParseUint(port, 10, 64)
if err != nil {
return fmt.Errorf("not a valid port: %v", err)
}
a.Host = host
a.Port = int(portNum)
default:
port := a.DefaultPort
if port == 0 {
switch a.DefaultScheme {
case "http":
port = 80
case "https":
port = 443
default:
return fmt.Errorf("no port specified")
}
}
a.Host = value
a.Port = port
}
addr.Host = net.JoinHostPort(a.Host, strconv.FormatInt(int64(a.Port), 10))
if value != a.Value {
a.Provided = true
}
a.URL = addr
a.IPv6Host = isIPv6Host(a.Host)
a.Value = value
return nil
}
// Type returns a string representation of what kind of value this is
func (a *Addr) Type() string {
return "string"
}
// isURL returns true if the provided value appears to be a valid URL.
func (a *Addr) isURL(value string) bool {
prefixes := urlPrefixes
if a.DefaultScheme != "" {
prefixes = append(prefixes, fmt.Sprintf("%s://", a.DefaultScheme))
}
for _, p := range prefixes {
if strings.HasPrefix(value, p) {
return true
}
}
return false
}
// isIPv6Host returns true if the value appears to be an IPv6 host string (that does
// not include a port).
func isIPv6Host(value string) bool {
if strings.HasPrefix(value, "[") {
return false
}
return strings.Contains(value, "%") || strings.Count(value, ":") > 1
}
-3
View File
@@ -1,3 +0,0 @@
// Package flagtypes provides types that implement the pflags.Value interface for
// converting command line flags to objects.
package flagtypes
-30
View File
@@ -1,30 +0,0 @@
package flagtypes
import (
"flag"
"github.com/golang/glog"
"github.com/spf13/pflag"
)
// GLog binds the log flags from the default Google "flag" package into a pflag.FlagSet.
func GLog(flags *pflag.FlagSet) {
from := flag.CommandLine
if flag := from.Lookup("v"); flag != nil {
level := flag.Value.(*glog.Level)
levelPtr := (*int32)(level)
flags.Int32Var(levelPtr, "loglevel", 0, "Set the level of log output (0-10)")
}
if flag := from.Lookup("vmodule"); flag != nil {
value := flag.Value
flags.Var(pflagValue{value}, "logspec", "Set per module logging with file|pattern=LEVEL,...")
}
}
type pflagValue struct {
flag.Value
}
func (pflagValue) Type() string {
return "string"
}
-59
View File
@@ -1,59 +0,0 @@
package flagtypes
import (
"fmt"
"net"
"strings"
)
// lifted from kubernetes/pkg/util/net.go. same flags vs pflags problem as we had with StringList
// IP adapts net.IP for use as a flag.
type IP net.IP
func (ip IP) String() string {
return net.IP(ip).String()
}
func (ip *IP) Set(value string) error {
*ip = IP(net.ParseIP(strings.TrimSpace(value)))
if *ip == nil {
return fmt.Errorf("invalid IP address: '%s'", value)
}
return nil
}
// Type returns a string representation of what kind of argument this is
func (ip *IP) Type() string {
return "cmd.flagtypes.IP"
}
// IPNet adapts net.IPNet for use as a flag.
type IPNet net.IPNet
func DefaultIPNet(value string) IPNet {
ret := IPNet{}
if err := ret.Set(value); err != nil {
panic(err)
}
return ret
}
func (ipnet IPNet) String() string {
n := net.IPNet(ipnet)
return n.String()
}
func (ipnet *IPNet) Set(value string) error {
_, n, err := net.ParseCIDR(strings.TrimSpace(value))
if err != nil {
return err
}
*ipnet = IPNet(*n)
return nil
}
// Type returns a string representation of what kind of argument this is
func (ipnet *IPNet) Type() string {
return "cmd.flagtypes.IPNet"
}
@@ -1,136 +0,0 @@
package clientcmd
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/runtime"
)
// CachedDiscoveryClient implements the functions that discovery server-supported API groups,
// versions and resources.
type CachedDiscoveryClient struct {
discovery.DiscoveryInterface
// cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
cacheDirectory string
// ttl is how long the cache should be considered valid
ttl time.Duration
}
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) {
filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
cachedBytes, err := d.getCachedFile(filename)
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
if err == nil {
cachedResources := &unversioned.APIResourceList{}
if err := runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
glog.V(6).Infof("returning cached discovery info from %v", filename)
return cachedResources, nil
}
}
liveResources, err := d.DiscoveryInterface.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return liveResources, err
}
if err := d.writeCachedFile(filename, liveResources); err != nil {
glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
}
return liveResources, nil
}
// ServerResources returns the supported resources for all groups and versions.
func (d *CachedDiscoveryClient) ServerResources() (map[string]*unversioned.APIResourceList, error) {
apiGroups, err := d.ServerGroups()
if err != nil {
return nil, err
}
groupVersions := unversioned.ExtractGroupVersions(apiGroups)
result := map[string]*unversioned.APIResourceList{}
for _, groupVersion := range groupVersions {
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
if err != nil {
return nil, err
}
result[groupVersion] = resources
}
return result, nil
}
func (d *CachedDiscoveryClient) ServerGroups() (*unversioned.APIGroupList, error) {
filename := filepath.Join(d.cacheDirectory, "servergroups.json")
cachedBytes, err := d.getCachedFile(filename)
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
if err == nil {
cachedGroups := &unversioned.APIGroupList{}
if err := runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
glog.V(6).Infof("returning cached discovery info from %v", filename)
return cachedGroups, nil
}
}
liveGroups, err := d.DiscoveryInterface.ServerGroups()
if err != nil {
return liveGroups, err
}
if err := d.writeCachedFile(filename, liveGroups); err != nil {
glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
}
return liveGroups, nil
}
func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
fileInfo, err := file.Stat()
if err != nil {
return nil, err
}
if time.Now().After(fileInfo.ModTime().Add(d.ttl)) {
return nil, errors.New("cache expired")
}
// the cache is present and its valid. Try to read and use it.
cachedBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
return cachedBytes, nil
}
func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
return err
}
bytes, err := runtime.Encode(kapi.Codecs.LegacyCodec(), obj)
if err != nil {
return err
}
return ioutil.WriteFile(filename, bytes, 0755)
}
// NewCachedDiscoveryClient creates a new DiscoveryClient. cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
func NewCachedDiscoveryClient(delegate discovery.DiscoveryInterface, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
return &CachedDiscoveryClient{DiscoveryInterface: delegate, cacheDirectory: cacheDirectory, ttl: ttl}
}
-247
View File
@@ -1,247 +0,0 @@
package clientcmd
import (
"fmt"
"io/ioutil"
"strings"
"github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/restclient"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
osclient "github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/cmd/flagtypes"
"github.com/openshift/origin/pkg/cmd/util"
)
const ConfigSyntax = " --master=<addr>"
// Config contains all the necessary bits for client configuration
type Config struct {
// MasterAddr is the address the master can be reached on (host, host:port, or URL).
MasterAddr flagtypes.Addr
// KubernetesAddr is the address of the Kubernetes server (host, host:port, or URL).
// If omitted defaults to the master.
KubernetesAddr flagtypes.Addr
// CommonConfig is the shared base config for both the OpenShift config and Kubernetes config
CommonConfig restclient.Config
// Namespace is the namespace to act in
Namespace string
// If set, allow kubeconfig file loading
FromFile bool
// If true, no environment is loaded (for testing, primarily)
SkipEnv bool
clientConfig clientcmd.ClientConfig
}
// NewConfig returns a new configuration
func NewConfig() *Config {
return &Config{
MasterAddr: flagtypes.Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080, AllowPrefix: true}.Default(),
KubernetesAddr: flagtypes.Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080}.Default(),
CommonConfig: restclient.Config{},
}
}
// AnonymousClientConfig returns a copy of the given config with all user credentials (cert/key, bearer token, and username/password) removed
func AnonymousClientConfig(config *restclient.Config) restclient.Config {
// copy only known safe fields
// TODO: expose a copy method on the config that is "auth free"
return restclient.Config{
Host: config.Host,
APIPath: config.APIPath,
Prefix: config.Prefix,
ContentConfig: config.ContentConfig,
TLSClientConfig: restclient.TLSClientConfig{
CAFile: config.TLSClientConfig.CAFile,
CAData: config.TLSClientConfig.CAData,
},
RateLimiter: config.RateLimiter,
Insecure: config.Insecure,
UserAgent: config.UserAgent,
Transport: config.Transport,
WrapTransport: config.WrapTransport,
QPS: config.QPS,
Burst: config.Burst,
}
}
// BindClientConfigSecurityFlags adds flags for the supplied client config
func BindClientConfigSecurityFlags(config *restclient.Config, flags *pflag.FlagSet) {
flags.BoolVar(&config.Insecure, "insecure-skip-tls-verify", config.Insecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.")
flags.StringVar(&config.CertFile, "client-certificate", config.CertFile, "Path to a client certificate file for TLS.")
flags.StringVar(&config.KeyFile, "client-key", config.KeyFile, "Path to a client key file for TLS.")
flags.StringVar(&config.CAFile, "certificate-authority", config.CAFile, "Path to a cert. file for the certificate authority")
flags.StringVar(&config.BearerToken, "token", config.BearerToken, "If present, the bearer token for this request.")
}
// Bind binds configuration values to the passed flagset
func (cfg *Config) Bind(flags *pflag.FlagSet) {
flags.Var(&cfg.MasterAddr, "master", "The address the master can be reached on (host, host:port, or URL).")
flags.Var(&cfg.KubernetesAddr, "kubernetes", "The address of the Kubernetes server (host, host:port, or URL). If omitted defaults to the master.")
if cfg.FromFile {
cfg.clientConfig = DefaultClientConfig(flags)
} else {
BindClientConfigSecurityFlags(&cfg.CommonConfig, flags)
}
}
// BindToFile is used when this config will not be bound to flags, but should load the config file
// from disk if available.
func (cfg *Config) BindToFile() *Config {
cfg.clientConfig = DefaultClientConfig(pflag.NewFlagSet("empty", pflag.ContinueOnError))
return cfg
}
func EnvVars(host string, caData []byte, insecure bool, bearerTokenFile string) []api.EnvVar {
envvars := []api.EnvVar{
{Name: "KUBERNETES_MASTER", Value: host},
{Name: "OPENSHIFT_MASTER", Value: host},
}
if len(bearerTokenFile) > 0 {
envvars = append(envvars, api.EnvVar{Name: "BEARER_TOKEN_FILE", Value: bearerTokenFile})
}
if len(caData) > 0 {
envvars = append(envvars, api.EnvVar{Name: "OPENSHIFT_CA_DATA", Value: string(caData)})
} else if insecure {
envvars = append(envvars, api.EnvVar{Name: "OPENSHIFT_INSECURE", Value: "true"})
}
return envvars
}
func (cfg *Config) bindEnv() error {
// bypass loading from env
if cfg.SkipEnv {
return nil
}
var err error
// callers may not use the config file if they have specified a master directly, for backwards
// compatibility with components that used to use env, switch to service account token, and have
// config defined in env.
_, masterSet := util.GetEnv("OPENSHIFT_MASTER")
specifiedMaster := masterSet || cfg.MasterAddr.Provided
if cfg.clientConfig != nil && !specifiedMaster {
clientConfig, err := cfg.clientConfig.ClientConfig()
if err != nil {
return err
}
cfg.CommonConfig = *clientConfig
cfg.Namespace, _, err = cfg.clientConfig.Namespace()
if err != nil {
return err
}
if !cfg.MasterAddr.Provided {
cfg.MasterAddr.Set(cfg.CommonConfig.Host)
}
if !cfg.KubernetesAddr.Provided {
cfg.KubernetesAddr.Set(cfg.CommonConfig.Host)
}
return nil
}
// Legacy path - preserve env vars set on pods that previously were honored.
if value, ok := util.GetEnv("KUBERNETES_MASTER"); ok && !cfg.KubernetesAddr.Provided {
cfg.KubernetesAddr.Set(value)
}
if value, ok := util.GetEnv("OPENSHIFT_MASTER"); ok && !cfg.MasterAddr.Provided {
cfg.MasterAddr.Set(value)
}
if value, ok := util.GetEnv("BEARER_TOKEN"); ok && len(cfg.CommonConfig.BearerToken) == 0 {
cfg.CommonConfig.BearerToken = value
}
if value, ok := util.GetEnv("BEARER_TOKEN_FILE"); ok && len(cfg.CommonConfig.BearerToken) == 0 {
if tokenData, tokenErr := ioutil.ReadFile(value); tokenErr == nil {
cfg.CommonConfig.BearerToken = strings.TrimSpace(string(tokenData))
if len(cfg.CommonConfig.BearerToken) == 0 {
err = fmt.Errorf("BEARER_TOKEN_FILE %q was empty", value)
}
} else {
err = fmt.Errorf("Error reading BEARER_TOKEN_FILE %q: %v", value, tokenErr)
}
}
if value, ok := util.GetEnv("OPENSHIFT_CA_FILE"); ok && len(cfg.CommonConfig.CAFile) == 0 {
cfg.CommonConfig.CAFile = value
} else if value, ok := util.GetEnv("OPENSHIFT_CA_DATA"); ok && len(cfg.CommonConfig.CAData) == 0 {
cfg.CommonConfig.CAData = []byte(value)
}
if value, ok := util.GetEnv("OPENSHIFT_CERT_FILE"); ok && len(cfg.CommonConfig.CertFile) == 0 {
cfg.CommonConfig.CertFile = value
} else if value, ok := util.GetEnv("OPENSHIFT_CERT_DATA"); ok && len(cfg.CommonConfig.CertData) == 0 {
cfg.CommonConfig.CertData = []byte(value)
}
if value, ok := util.GetEnv("OPENSHIFT_KEY_FILE"); ok && len(cfg.CommonConfig.KeyFile) == 0 {
cfg.CommonConfig.KeyFile = value
} else if value, ok := util.GetEnv("OPENSHIFT_KEY_DATA"); ok && len(cfg.CommonConfig.KeyData) == 0 {
cfg.CommonConfig.KeyData = []byte(value)
}
if value, ok := util.GetEnv("OPENSHIFT_INSECURE"); ok && len(value) != 0 {
cfg.CommonConfig.Insecure = value == "true"
}
return err
}
// KubeConfig returns the Kubernetes configuration
func (cfg *Config) KubeConfig() *restclient.Config {
err := cfg.bindEnv()
if err != nil {
glog.Error(err)
}
kaddr := cfg.KubernetesAddr
if !kaddr.Provided {
kaddr = cfg.MasterAddr
}
kConfig := cfg.CommonConfig
kConfig.Host = kaddr.URL.String()
return &kConfig
}
// OpenShiftConfig returns the OpenShift configuration
func (cfg *Config) OpenShiftConfig() *restclient.Config {
err := cfg.bindEnv()
if err != nil {
glog.Error(err)
}
osConfig := cfg.CommonConfig
if len(osConfig.Host) == 0 || cfg.MasterAddr.Provided {
osConfig.Host = cfg.MasterAddr.String()
}
return &osConfig
}
// Clients returns an OpenShift and a Kubernetes client from a given configuration
func (cfg *Config) Clients() (osclient.Interface, kclient.Interface, error) {
cfg.bindEnv()
kubeClient, err := kclient.New(cfg.KubeConfig())
if err != nil {
return nil, nil, fmt.Errorf("Unable to configure Kubernetes client: %v", err)
}
osClient, err := osclient.New(cfg.OpenShiftConfig())
if err != nil {
return nil, nil, fmt.Errorf("Unable to configure Origin client: %v", err)
}
return osClient, kubeClient, nil
}
@@ -1,29 +0,0 @@
package clientcmd
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/openshift/origin/pkg/cmd/cli/config"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
)
func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
loadingRules := config.NewOpenShiftClientConfigLoadingRules()
flags.StringVar(&loadingRules.ExplicitPath, config.OpenShiftConfigFlagName, "", "Path to the config file to use for CLI requests.")
cobra.MarkFlagFilename(flags, config.OpenShiftConfigFlagName)
overrides := &clientcmd.ConfigOverrides{}
overrideFlags := clientcmd.RecommendedConfigOverrideFlags("")
overrideFlags.ContextOverrideFlags.Namespace.ShortName = "n"
overrideFlags.AuthOverrideFlags.Username.LongName = ""
overrideFlags.AuthOverrideFlags.Password.LongName = ""
clientcmd.BindOverrideFlags(overrides, flags, overrideFlags)
cobra.MarkFlagFilename(flags, overrideFlags.AuthOverrideFlags.ClientCertificate.LongName)
cobra.MarkFlagFilename(flags, overrideFlags.AuthOverrideFlags.ClientKey.LongName)
cobra.MarkFlagFilename(flags, overrideFlags.ClusterOverrideFlags.CertificateAuthority.LongName)
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
return clientConfig
}
-108
View File
@@ -1,108 +0,0 @@
package clientcmd
import (
"errors"
"fmt"
"strings"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
)
const (
unknownReason = iota
noServerFoundReason
certificateAuthorityUnknownReason
configurationInvalidReason
tlsOversizedRecordReason
certificateAuthorityUnknownMsg = "The server uses a certificate signed by unknown authority. You may need to use the --certificate-authority flag to provide the path to a certificate file for the certificate authority, or --insecure-skip-tls-verify to bypass the certificate check and use insecure connections."
notConfiguredMsg = `The client is not configured. You need to run the login command in order to create a default config for your server and credentials:
oc login
You can also run this command again providing the path to a config file directly, either through the --config flag of the KUBECONFIG environment variable.
`
tlsOversizedRecordMsg = `Unable to connect to %[2]s using TLS: %[1]s.
Ensure the specified server supports HTTPS.`
)
// GetPrettyMessageFor prettifys the message of the provided error
func GetPrettyMessageFor(err error) string {
return GetPrettyMessageForServer(err, "")
}
// GetPrettyMessageForServer prettifys the message of the provided error
func GetPrettyMessageForServer(err error, serverName string) string {
if err == nil {
return ""
}
reason := detectReason(err)
switch reason {
case noServerFoundReason:
return notConfiguredMsg
case certificateAuthorityUnknownReason:
return certificateAuthorityUnknownMsg
case tlsOversizedRecordReason:
if len(serverName) == 0 {
serverName = "server"
}
return fmt.Sprintf(tlsOversizedRecordMsg, err, serverName)
}
return err.Error()
}
// GetPrettyErrorFor prettifys the message of the provided error
func GetPrettyErrorFor(err error) error {
return GetPrettyErrorForServer(err, "")
}
// GetPrettyErrorForServer prettifys the message of the provided error
func GetPrettyErrorForServer(err error, serverName string) error {
return errors.New(GetPrettyMessageForServer(err, serverName))
}
// IsNoServerFound checks whether the provided error is a 'no server found' error or not
func IsNoServerFound(err error) bool {
return detectReason(err) == noServerFoundReason
}
// IsConfigurationInvalid checks whether the provided error is a 'invalid configuration' error or not
func IsConfigurationInvalid(err error) bool {
return detectReason(err) == configurationInvalidReason
}
// IsCertificateAuthorityUnknown checks whether the provided error is a 'certificate authority unknown' error or not
func IsCertificateAuthorityUnknown(err error) bool {
return detectReason(err) == certificateAuthorityUnknownReason
}
// IsForbidden checks whether the provided error is a 'forbidden' error or not
func IsForbidden(err error) bool {
return kerrors.IsForbidden(err)
}
// IsTLSOversizedRecord checks whether the provided error is a url.Error
// with "tls: oversized record received", which usually means TLS not supported.
func IsTLSOversizedRecord(err error) bool {
return detectReason(err) == tlsOversizedRecordReason
}
func detectReason(err error) int {
if err != nil {
switch {
case strings.Contains(err.Error(), "certificate signed by unknown authority"):
return certificateAuthorityUnknownReason
case strings.Contains(err.Error(), "no server defined"):
return noServerFoundReason
case clientcmd.IsConfigurationInvalid(err):
return configurationInvalidReason
case strings.Contains(err.Error(), "tls: oversized record received"):
return tlsOversizedRecordReason
}
}
return unknownReason
}
File diff suppressed because it is too large Load Diff
-116
View File
@@ -1,116 +0,0 @@
package clientcmd
import (
"fmt"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/restclient"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
)
// negotiateVersion queries the server's supported api versions to find a version that both client and server support.
// - If no version is provided, try registered client versions in order of preference.
// - If version is provided, but not default config (explicitly requested via
// commandline flag), and is unsupported by the server, print a warning to
// stderr and try client's registered versions in order of preference.
// - If version is config default, and the server does not support it, return an error.
func negotiateVersion(client *kclient.Client, config *restclient.Config, requestedGV *unversioned.GroupVersion, clientGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) {
// Ensure we have a client
var err error
if client == nil {
client, err = kclient.New(config)
if err != nil {
return nil, err
}
}
// Determine our preferred version
preferredGV := copyGroupVersion(requestedGV)
if preferredGV == nil {
preferredGV = copyGroupVersion(config.GroupVersion)
}
// Get server versions
serverGVs, err := serverAPIVersions(client, "/oapi")
if err != nil {
if errors.IsNotFound(err) {
glog.V(4).Infof("Server path /oapi was not found, returning the requested group version %v", preferredGV)
return preferredGV, nil
}
return nil, err
}
// Find a version we can all agree on
matchedGV, err := matchAPIVersion(preferredGV, clientGVs, serverGVs)
if err != nil {
return nil, err
}
// Enforce a match if the preferredGV is the config default
if config.GroupVersion != nil && (*preferredGV == *config.GroupVersion) && (*matchedGV != *config.GroupVersion) {
return nil, fmt.Errorf("server does not support API version %q", config.GroupVersion.String())
}
return matchedGV, err
}
// serverAPIVersions fetches the server versions available from the groupless API at the given prefix
func serverAPIVersions(c *kclient.Client, grouplessPrefix string) ([]unversioned.GroupVersion, error) {
// Get versions doc
var v unversioned.APIVersions
if err := c.Get().AbsPath(grouplessPrefix).Do().Into(&v); err != nil {
return []unversioned.GroupVersion{}, err
}
// Convert to GroupVersion structs
serverAPIVersions := []unversioned.GroupVersion{}
for _, version := range v.Versions {
gv, err := unversioned.ParseGroupVersion(version)
if err != nil {
return []unversioned.GroupVersion{}, err
}
serverAPIVersions = append(serverAPIVersions, gv)
}
return serverAPIVersions, nil
}
func matchAPIVersion(preferredGV *unversioned.GroupVersion, clientGVs []unversioned.GroupVersion, serverGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) {
// If version explicitly requested verify that both client and server support it.
// If server does not support warn, but try to negotiate a lower version.
if preferredGV != nil {
if !containsGroupVersion(clientGVs, *preferredGV) {
return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", preferredGV, clientGVs)
}
if containsGroupVersion(serverGVs, *preferredGV) {
return preferredGV, nil
}
}
for _, clientGV := range clientGVs {
if containsGroupVersion(serverGVs, clientGV) {
t := clientGV
return &t, nil
}
}
return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v", serverGVs, clientGVs)
}
func copyGroupVersion(version *unversioned.GroupVersion) *unversioned.GroupVersion {
if version == nil {
return nil
}
versionCopy := *version
return &versionCopy
}
func containsGroupVersion(versions []unversioned.GroupVersion, version unversioned.GroupVersion) bool {
for _, v := range versions {
if v == version {
return true
}
}
return false
}
@@ -1,141 +0,0 @@
package clientcmd
import (
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/typed/discovery"
)
// ShortcutExpander is a RESTMapper that can be used for OpenShift resources. It expands the resource first, then invokes the wrapped
type ShortcutExpander struct {
RESTMapper meta.RESTMapper
All []string
}
var _ meta.RESTMapper = &ShortcutExpander{}
func NewShortcutExpander(discoveryClient discovery.DiscoveryInterface, delegate meta.RESTMapper) ShortcutExpander {
defaultMapper := ShortcutExpander{RESTMapper: delegate}
// this assumes that legacy kube versions and legacy origin versions are the same, probably fair
apiResources, err := discoveryClient.ServerResources()
if err != nil {
return defaultMapper
}
availableResources := []unversioned.GroupVersionResource{}
for groupVersionString, resourceList := range apiResources {
currVersion, err := unversioned.ParseGroupVersion(groupVersionString)
if err != nil {
return defaultMapper
}
for _, resource := range resourceList.APIResources {
availableResources = append(availableResources, currVersion.WithResource(resource.Name))
}
}
availableAll := []string{}
for _, requestedResource := range userResources {
for _, availableResource := range availableResources {
if requestedResource == availableResource.Resource {
availableAll = append(availableAll, requestedResource)
break
}
}
}
return ShortcutExpander{All: availableAll, RESTMapper: delegate}
}
func (e ShortcutExpander) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) {
return e.RESTMapper.KindFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) {
return e.RESTMapper.KindsFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) {
return e.RESTMapper.ResourcesFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) {
return e.RESTMapper.ResourceFor(expandResourceShortcut(resource))
}
func (e ShortcutExpander) ResourceSingularizer(resource string) (string, error) {
return e.RESTMapper.ResourceSingularizer(expandResourceShortcut(unversioned.GroupVersionResource{Resource: resource}).Resource)
}
func (e ShortcutExpander) RESTMapping(gk unversioned.GroupKind, versions ...string) (*meta.RESTMapping, error) {
return e.RESTMapper.RESTMapping(gk, versions...)
}
func (e ShortcutExpander) RESTMappings(gk unversioned.GroupKind) ([]*meta.RESTMapping, error) {
return e.RESTMapper.RESTMappings(gk)
}
// userResources are the resource names that apply to the primary, user facing resources used by
// client tools. They are in deletion-first order - dependent resources should be last.
var userResources = []string{
"buildconfigs", "builds",
"imagestreams",
"deploymentconfigs", "replicationcontrollers",
"routes", "services",
"pods",
}
// AliasesForResource returns whether a resource has an alias or not
func (e ShortcutExpander) AliasesForResource(resource string) ([]string, bool) {
aliases := map[string][]string{
"all": userResources,
}
if len(e.All) != 0 {
aliases["all"] = e.All
}
if res, ok := aliases[resource]; ok {
return res, true
}
return e.RESTMapper.AliasesForResource(expandResourceShortcut(unversioned.GroupVersionResource{Resource: resource}).Resource)
}
// shortForms is the list of short names to their expanded names
var shortForms = map[string]string{
"dc": "deploymentconfigs",
"bc": "buildconfigs",
"is": "imagestreams",
"istag": "imagestreamtags",
"isimage": "imagestreamimages",
"sa": "serviceaccounts",
"pv": "persistentvolumes",
"pvc": "persistentvolumeclaims",
"clusterquota": "clusterresourcequota",
}
// expandResourceShortcut will return the expanded version of resource
// (something that a pkg/api/meta.RESTMapper can understand), if it is
// indeed a shortcut. Otherwise, will return resource unmodified.
func expandResourceShortcut(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource {
if expanded, ok := shortForms[resource.Resource]; ok {
resource.Resource = expanded
return resource
}
return resource
}
// resourceShortFormFor looks up for a short form of resource names.
func resourceShortFormFor(resource string) (string, bool) {
var alias string
exists := false
for k, val := range shortForms {
if val == resource {
alias = k
exists = true
break
}
}
return alias, exists
}
+11 -15
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"path/filepath"
"regexp"
"strings"
"github.com/spf13/cobra"
@@ -20,6 +21,8 @@ import (
// ErrExit is a marker interface for cli commands indicating that the response has been processed
var ErrExit = fmt.Errorf("exit directly")
var commaSepVarsPattern = regexp.MustCompile(".*=.*,.*=.*")
// ReplaceCommandName recursively processes the examples in a given command to change a hardcoded
// command name (like 'kubectl' to the appropriate target name). It returns c.
func ReplaceCommandName(from, to string, c *cobra.Command) *cobra.Command {
@@ -30,21 +33,6 @@ func ReplaceCommandName(from, to string, c *cobra.Command) *cobra.Command {
return c
}
// RequireNoArguments exits with a usage error if extra arguments are provided.
func RequireNoArguments(c *cobra.Command, args []string) {
if len(args) > 0 {
kcmdutil.CheckErr(kcmdutil.UsageError(c, fmt.Sprintf(`unknown command "%s"`, strings.Join(args, " "))))
}
}
func DefaultSubCommandRun(out io.Writer) func(c *cobra.Command, args []string) {
return func(c *cobra.Command, args []string) {
c.SetOutput(out)
RequireNoArguments(c, args)
c.Help()
}
}
// GetDisplayFilename returns the absolute path of the filename as long as there was no error, otherwise it returns the filename as-is
func GetDisplayFilename(filename string) string {
if absName, err := filepath.Abs(filename); err == nil {
@@ -163,3 +151,11 @@ func VersionedPrintObject(fn func(*cobra.Command, meta.RESTMapper, runtime.Objec
return fn(c, mapper, obj, out)
}
}
func WarnAboutCommaSeparation(errout io.Writer, values []string, flag string) {
for _, value := range values {
if commaSepVarsPattern.MatchString(value) {
fmt.Fprintf(errout, "warning: %s no longer accepts comma-separated lists of values. %q will be treated as a single key-value pair.\n", flag, value)
}
}
}
-122
View File
@@ -1,122 +0,0 @@
package util
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"github.com/docker/docker/pkg/term"
"github.com/golang/glog"
kterm "k8s.io/kubernetes/pkg/util/term"
)
// PromptForString takes an io.Reader and prompts for user input if it's a terminal, returning the result.
func PromptForString(r io.Reader, w io.Writer, format string, a ...interface{}) string {
if w == nil {
w = os.Stdout
}
fmt.Fprintf(w, format, a...)
return readInput(r)
}
// PromptForPasswordString prompts for user input by disabling echo in terminal, useful for password prompt.
func PromptForPasswordString(r io.Reader, w io.Writer, format string, a ...interface{}) string {
if w == nil {
w = os.Stdout
}
if file, ok := r.(*os.File); ok {
inFd := file.Fd()
if term.IsTerminal(inFd) {
oldState, err := term.SaveState(inFd)
if err != nil {
glog.V(3).Infof("Unable to save terminal state")
return PromptForString(r, w, format, a...)
}
fmt.Fprintf(w, format, a...)
term.DisableEcho(inFd, oldState)
input := readInput(r)
defer term.RestoreTerminal(inFd, oldState)
fmt.Fprintf(w, "\n")
return input
}
glog.V(3).Infof("Stdin is not a terminal")
return PromptForString(r, w, format, a...)
}
return PromptForString(r, w, format, a...)
}
// PromptForBool prompts for user input of a boolean value. The accepted values are:
// yes, y, true, t, 1 (not case sensitive)
// no, n, false, f, 0 (not case sensitive)
// A valid answer is mandatory so it will keep asking until an answer is provided.
func PromptForBool(r io.Reader, w io.Writer, format string, a ...interface{}) bool {
if w == nil {
w = os.Stdout
}
str := PromptForString(r, w, format, a...)
switch strings.ToLower(str) {
case "1", "t", "true", "y", "yes":
return true
case "0", "f", "false", "n", "no":
return false
}
fmt.Println("Please enter 'yes' or 'no'.")
return PromptForBool(r, w, format, a...)
}
// PromptForStringWithDefault prompts for user input but take a default in case nothing is provided.
func PromptForStringWithDefault(r io.Reader, w io.Writer, def string, format string, a ...interface{}) string {
if w == nil {
w = os.Stdout
}
s := PromptForString(r, w, format, a...)
if len(s) == 0 {
return def
}
return s
}
func readInput(r io.Reader) string {
if kterm.IsTerminal(r) {
return readInputFromTerminal(r)
}
return readInputFromReader(r)
}
func readInputFromTerminal(r io.Reader) string {
reader := bufio.NewReader(r)
result, _ := reader.ReadString('\n')
return strings.TrimRight(result, "\r\n")
}
func readInputFromReader(r io.Reader) string {
var result string
fmt.Fscan(r, &result)
return result
}
// IsTerminalReader returns whether the passed io.Reader is a terminal or not
func IsTerminalReader(r io.Reader) bool {
file, ok := r.(*os.File)
return ok && term.IsTerminal(file.Fd())
}
// IsTerminalWriter returns whether the passed io.Writer is a terminal or not
func IsTerminalWriter(w io.Writer) bool {
file, ok := w.(*os.File)
return ok && term.IsTerminal(file.Fd())
}
+7
View File
@@ -49,6 +49,13 @@ func ScaleFromConfig(dc *DeploymentConfig) *extensions.Scale {
}
}
// RequestForConfig builds a new deployment request for a deployment config.
func RequestForConfig(dc *DeploymentConfig) *DeploymentRequest {
return &DeploymentRequest{
Name: dc.Name,
}
}
// TemplateImage is a structure for helping a caller iterate over a PodSpec
type TemplateImage struct {
Image string
+2
View File
@@ -32,6 +32,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&DeploymentConfig{},
&DeploymentConfigList{},
&DeploymentConfigRollback{},
&DeploymentRequest{},
&DeploymentLog{},
&DeploymentLogOptions{},
)
@@ -41,5 +42,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
func (obj *DeploymentConfig) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentConfigList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentConfigRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentRequest) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentLog) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentLogOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
+251 -208
View File
@@ -6,181 +6,7 @@ import (
"k8s.io/kubernetes/pkg/util/intstr"
)
// DeploymentStatus describes the possible states a deployment can be in.
type DeploymentStatus string
const (
// DeploymentStatusNew means the deployment has been accepted but not yet acted upon.
DeploymentStatusNew DeploymentStatus = "New"
// DeploymentStatusPending means the deployment been handed over to a deployment strategy,
// but the strategy has not yet declared the deployment to be running.
DeploymentStatusPending DeploymentStatus = "Pending"
// DeploymentStatusRunning means the deployment strategy has reported the deployment as
// being in-progress.
DeploymentStatusRunning DeploymentStatus = "Running"
// DeploymentStatusComplete means the deployment finished without an error.
DeploymentStatusComplete DeploymentStatus = "Complete"
// DeploymentStatusFailed means the deployment finished with an error.
DeploymentStatusFailed DeploymentStatus = "Failed"
)
// DeploymentStrategy describes how to perform a deployment.
type DeploymentStrategy struct {
// Type is the name of a deployment strategy.
Type DeploymentStrategyType
// RecreateParams are the input to the Recreate deployment strategy.
RecreateParams *RecreateDeploymentStrategyParams
// RollingParams are the input to the Rolling deployment strategy.
RollingParams *RollingDeploymentStrategyParams
// CustomParams are the input to the Custom deployment strategy, and may also
// be specified for the Recreate and Rolling strategies to customize the execution
// process that runs the deployment.
CustomParams *CustomDeploymentStrategyParams
// Resources contains resource requirements to execute the deployment
Resources kapi.ResourceRequirements
// Labels is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
Labels map[string]string
// Annotations is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
Annotations map[string]string
}
// DeploymentStrategyType refers to a specific DeploymentStrategy implementation.
type DeploymentStrategyType string
const (
// DeploymentStrategyTypeRecreate is a simple strategy suitable as a default.
DeploymentStrategyTypeRecreate DeploymentStrategyType = "Recreate"
// DeploymentStrategyTypeCustom is a user defined strategy. It is optional to set.
DeploymentStrategyTypeCustom DeploymentStrategyType = "Custom"
// DeploymentStrategyTypeRolling uses the Kubernetes RollingUpdater.
DeploymentStrategyTypeRolling DeploymentStrategyType = "Rolling"
)
// CustomDeploymentStrategyParams are the input to the Custom deployment strategy.
type CustomDeploymentStrategyParams struct {
// Image specifies a Docker image which can carry out a deployment.
Image string
// Environment holds the environment which will be given to the container for Image.
Environment []kapi.EnvVar
// Command is optional and overrides CMD in the container Image.
Command []string
}
// RecreateDeploymentStrategyParams are the input to the Recreate deployment
// strategy.
type RecreateDeploymentStrategyParams struct {
// TimeoutSeconds is the time to wait for updates before giving up. If the
// value is nil, a default will be used.
TimeoutSeconds *int64
// Pre is a lifecycle hook which is executed before the strategy manipulates
// the deployment. All LifecycleHookFailurePolicy values are supported.
Pre *LifecycleHook
// Mid is a lifecycle hook which is executed while the deployment is scaled down to zero before the first new
// pod is created. All LifecycleHookFailurePolicy values are supported.
Mid *LifecycleHook
// Post is a lifecycle hook which is executed after the strategy has
// finished all deployment logic.
Post *LifecycleHook
}
// LifecycleHook defines a specific deployment lifecycle action. Only one type of action may be specified at any time.
type LifecycleHook struct {
// FailurePolicy specifies what action to take if the hook fails.
FailurePolicy LifecycleHookFailurePolicy
// ExecNewPod specifies the options for a lifecycle hook backed by a pod.
ExecNewPod *ExecNewPodHook
// TagImages instructs the deployer to tag the current image referenced under a container onto an image stream tag if the deployment succeeds.
TagImages []TagImageHook
}
// LifecycleHookFailurePolicy describes possibles actions to take if a hook fails.
type LifecycleHookFailurePolicy string
const (
// LifecycleHookFailurePolicyRetry means retry the hook until it succeeds.
LifecycleHookFailurePolicyRetry LifecycleHookFailurePolicy = "Retry"
// LifecycleHookFailurePolicyAbort means abort the deployment (if possible).
LifecycleHookFailurePolicyAbort LifecycleHookFailurePolicy = "Abort"
// LifecycleHookFailurePolicyIgnore means ignore failure and continue the deployment.
LifecycleHookFailurePolicyIgnore LifecycleHookFailurePolicy = "Ignore"
)
// ExecNewPodHook is a hook implementation which runs a command in a new pod
// based on the specified container which is assumed to be part of the
// deployment template.
type ExecNewPodHook struct {
// Command is the action command and its arguments.
Command []string
// Env is a set of environment variables to supply to the hook pod's container.
Env []kapi.EnvVar
// ContainerName is the name of a container in the deployment pod template
// whose Docker image will be used for the hook pod's container.
ContainerName string
// Volumes is a list of named volumes from the pod template which should be
// copied to the hook pod.
Volumes []string
}
// TagImageHook is a request to tag the image in a particular container onto an ImageStreamTag.
type TagImageHook struct {
// ContainerName is the name of a container in the deployment config whose image value will be used as the source of the tag
ContainerName string
// To is the target ImageStreamTag to set the image of
To kapi.ObjectReference
}
// RollingDeploymentStrategyParams are the input to the Rolling deployment
// strategy.
type RollingDeploymentStrategyParams struct {
// UpdatePeriodSeconds is the time to wait between individual pod updates.
// If the value is nil, a default will be used.
UpdatePeriodSeconds *int64
// IntervalSeconds is the time to wait between polling deployment status
// after update. If the value is nil, a default will be used.
IntervalSeconds *int64
// TimeoutSeconds is the time to wait for updates before giving up. If the
// value is nil, a default will be used.
TimeoutSeconds *int64
// The maximum number of pods that can be unavailable during the update.
// Value can be an absolute number (ex: 5) or a percentage of total pods at the start of update (ex: 10%).
// Absolute number is calculated from percentage by rounding up.
// This can not be 0 if MaxSurge is 0.
// By default, a fixed value of 1 is used.
// Example: when this is set to 30%, the old RC can be scaled down by 30%
// immediately when the rolling update starts. Once new pods are ready, old RC
// can be scaled down further, followed by scaling up the new RC, ensuring
// that at least 70% of original number of pods are available at all times
// during the update.
MaxUnavailable intstr.IntOrString
// The maximum number of pods that can be scheduled above the original number of
// pods.
// Value can be an absolute number (ex: 5) or a percentage of total pods at
// the start of the update (ex: 10%). This can not be 0 if MaxUnavailable is 0.
// Absolute number is calculated from percentage by rounding up.
// By default, a value of 1 is used.
// Example: when this is set to 30%, the new RC can be scaled up by 30%
// immediately when the rolling update starts. Once old pods have been killed,
// new RC can be scaled up further, ensuring that total number of pods running
// at any time during the update is atmost 130% of original pods.
MaxSurge intstr.IntOrString
// UpdatePercent is the percentage of replicas to scale up or down each
// interval. If nil, one replica will be scaled up and down each interval.
// If negative, the scale order will be down/up instead of up/down.
// DEPRECATED: Use MaxUnavailable/MaxSurge instead.
UpdatePercent *int32
// Pre is a lifecycle hook which is executed before the deployment process
// begins. All LifecycleHookFailurePolicy values are supported.
Pre *LifecycleHook
// Post is a lifecycle hook which is executed after the strategy has
// finished all deployment logic.
Post *LifecycleHook
}
// These constants represent defaults used in the deployment process.
const (
// DefaultRollingTimeoutSeconds is the default TimeoutSeconds for RollingDeploymentStrategyParams.
DefaultRollingTimeoutSeconds int64 = 10 * 60
@@ -188,6 +14,10 @@ const (
DefaultRollingIntervalSeconds int64 = 1
// DefaultRollingUpdatePeriodSeconds is the default PeriodSeconds for RollingDeploymentStrategyParams.
DefaultRollingUpdatePeriodSeconds int64 = 1
// MaxDeploymentDurationSeconds represents the maximum duration that a deployment is allowed to run.
// This is set as the default value for ActiveDeadlineSeconds for the deployer pod.
// Currently set to 6 hours.
MaxDeploymentDurationSeconds int64 = 21600
)
// These constants represent keys used for correlating objects related to deployments.
@@ -253,6 +83,13 @@ const (
PostHookPodSuffix = "hook-post"
)
// These constants represent values used in deployment annotations.
const (
// DeploymentCancelledAnnotationValue represents the value for the DeploymentCancelledAnnotation
// annotation that signifies that the deployment should be cancelled
DeploymentCancelledAnnotationValue = "true"
)
// These constants represent the various reasons for cancelling a deployment
// or for a deployment being placed in a failed state
const (
@@ -262,18 +99,23 @@ const (
DeploymentFailedDeployerPodNoLongerExists = "deployer pod no longer exists"
)
// MaxDeploymentDurationSeconds represents the maximum duration that a deployment is allowed to run
// This is set as the default value for ActiveDeadlineSeconds for the deployer pod
// Currently set to 6 hours
const MaxDeploymentDurationSeconds int64 = 21600
// DeploymentStatus describes the possible states a deployment can be in.
type DeploymentStatus string
// DeploymentCancelledAnnotationValue represents the value for the DeploymentCancelledAnnotation
// annotation that signifies that the deployment should be cancelled
const DeploymentCancelledAnnotationValue = "true"
// DeploymentInstantiatedAnnotationValue represents the value for the DeploymentInstantiatedAnnotation
// annotation that signifies that the deployment should be instantiated.
const DeploymentInstantiatedAnnotationValue = "true"
const (
// DeploymentStatusNew means the deployment has been accepted but not yet acted upon.
DeploymentStatusNew DeploymentStatus = "New"
// DeploymentStatusPending means the deployment been handed over to a deployment strategy,
// but the strategy has not yet declared the deployment to be running.
DeploymentStatusPending DeploymentStatus = "Pending"
// DeploymentStatusRunning means the deployment strategy has reported the deployment as
// being in-progress.
DeploymentStatusRunning DeploymentStatus = "Running"
// DeploymentStatusComplete means the deployment finished without an error.
DeploymentStatusComplete DeploymentStatus = "Complete"
// DeploymentStatusFailed means the deployment finished with an error.
DeploymentStatusFailed DeploymentStatus = "Failed"
)
// +genclient=true
@@ -331,25 +173,162 @@ type DeploymentConfigSpec struct {
Template *kapi.PodTemplateSpec
}
// DeploymentConfigStatus represents the current deployment state.
type DeploymentConfigStatus struct {
// LatestVersion is used to determine whether the current deployment associated with a deployment
// config is out of sync.
LatestVersion int64
// ObservedGeneration is the most recent generation observed by the deployment config controller.
ObservedGeneration int64
// Replicas is the total number of pods targeted by this deployment config.
Replicas int32
// UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config
// that have the desired template spec.
UpdatedReplicas int32
// AvailableReplicas is the total number of available pods targeted by this deployment config.
AvailableReplicas int32
// UnavailableReplicas is the total number of unavailable pods targeted by this deployment config.
UnavailableReplicas int32
// Details are the reasons for the update to this deployment config.
// This could be based on a change made by the user or caused by an automatic trigger
Details *DeploymentDetails
// DeploymentStrategy describes how to perform a deployment.
type DeploymentStrategy struct {
// Type is the name of a deployment strategy.
Type DeploymentStrategyType
// CustomParams are the input to the Custom deployment strategy, and may also
// be specified for the Recreate and Rolling strategies to customize the execution
// process that runs the deployment.
CustomParams *CustomDeploymentStrategyParams
// RecreateParams are the input to the Recreate deployment strategy.
RecreateParams *RecreateDeploymentStrategyParams
// RollingParams are the input to the Rolling deployment strategy.
RollingParams *RollingDeploymentStrategyParams
// Resources contains resource requirements to execute the deployment and any hooks.
Resources kapi.ResourceRequirements
// Labels is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
Labels map[string]string
// Annotations is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
Annotations map[string]string
}
// DeploymentStrategyType refers to a specific DeploymentStrategy implementation.
type DeploymentStrategyType string
const (
// DeploymentStrategyTypeRecreate is a simple strategy suitable as a default.
DeploymentStrategyTypeRecreate DeploymentStrategyType = "Recreate"
// DeploymentStrategyTypeCustom is a user defined strategy.
DeploymentStrategyTypeCustom DeploymentStrategyType = "Custom"
// DeploymentStrategyTypeRolling uses the Kubernetes RollingUpdater.
DeploymentStrategyTypeRolling DeploymentStrategyType = "Rolling"
)
// CustomDeploymentStrategyParams are the input to the Custom deployment strategy.
type CustomDeploymentStrategyParams struct {
// Image specifies a Docker image which can carry out a deployment.
Image string
// Environment holds the environment which will be given to the container for Image.
Environment []kapi.EnvVar
// Command is optional and overrides CMD in the container Image.
Command []string
}
// RecreateDeploymentStrategyParams are the input to the Recreate deployment
// strategy.
type RecreateDeploymentStrategyParams struct {
// TimeoutSeconds is the time to wait for updates before giving up. If the
// value is nil, a default will be used.
TimeoutSeconds *int64
// Pre is a lifecycle hook which is executed before the strategy manipulates
// the deployment. All LifecycleHookFailurePolicy values are supported.
Pre *LifecycleHook
// Mid is a lifecycle hook which is executed while the deployment is scaled down to zero before the first new
// pod is created. All LifecycleHookFailurePolicy values are supported.
Mid *LifecycleHook
// Post is a lifecycle hook which is executed after the strategy has
// finished all deployment logic. All LifecycleHookFailurePolicy values are supported.
Post *LifecycleHook
}
// RollingDeploymentStrategyParams are the input to the Rolling deployment
// strategy.
type RollingDeploymentStrategyParams struct {
// UpdatePeriodSeconds is the time to wait between individual pod updates.
// If the value is nil, a default will be used.
UpdatePeriodSeconds *int64
// IntervalSeconds is the time to wait between polling deployment status
// after update. If the value is nil, a default will be used.
IntervalSeconds *int64
// TimeoutSeconds is the time to wait for updates before giving up. If the
// value is nil, a default will be used.
TimeoutSeconds *int64
// MaxUnavailable is the maximum number of pods that can be unavailable
// during the update. Value can be an absolute number (ex: 5) or a
// percentage of total pods at the start of update (ex: 10%). Absolute
// number is calculated from percentage by rounding up.
//
// This cannot be 0 if MaxSurge is 0. By default, 25% is used.
//
// Example: when this is set to 30%, the old RC can be scaled down by 30%
// immediately when the rolling update starts. Once new pods are ready, old
// RC can be scaled down further, followed by scaling up the new RC,
// ensuring that at least 70% of original number of pods are available at
// all times during the update.
MaxUnavailable intstr.IntOrString
// MaxSurge is the maximum number of pods that can be scheduled above the
// original number of pods. Value can be an absolute number (ex: 5) or a
// percentage of total pods at the start of the update (ex: 10%). Absolute
// number is calculated from percentage by rounding up.
//
// This cannot be 0 if MaxUnavailable is 0. By default, 25% is used.
//
// Example: when this is set to 30%, the new RC can be scaled up by 30%
// immediately when the rolling update starts. Once old pods have been
// killed, new RC can be scaled up further, ensuring that total number of
// pods running at any time during the update is atmost 130% of original
// pods.
MaxSurge intstr.IntOrString
// Pre is a lifecycle hook which is executed before the deployment process
// begins. All LifecycleHookFailurePolicy values are supported.
Pre *LifecycleHook
// Post is a lifecycle hook which is executed after the strategy has
// finished all deployment logic. All LifecycleHookFailurePolicy values
// are supported.
Post *LifecycleHook
}
// LifecycleHook defines a specific deployment lifecycle action. Only one type of action may be specified at any time.
type LifecycleHook struct {
// FailurePolicy specifies what action to take if the hook fails.
FailurePolicy LifecycleHookFailurePolicy
// ExecNewPod specifies the options for a lifecycle hook backed by a pod.
ExecNewPod *ExecNewPodHook
// TagImages instructs the deployer to tag the current image referenced under a container onto an image stream tag.
TagImages []TagImageHook
}
// LifecycleHookFailurePolicy describes possibles actions to take if a hook fails.
type LifecycleHookFailurePolicy string
const (
// LifecycleHookFailurePolicyRetry means retry the hook until it succeeds.
LifecycleHookFailurePolicyRetry LifecycleHookFailurePolicy = "Retry"
// LifecycleHookFailurePolicyAbort means abort the deployment.
LifecycleHookFailurePolicyAbort LifecycleHookFailurePolicy = "Abort"
// LifecycleHookFailurePolicyIgnore means ignore failure and continue the deployment.
LifecycleHookFailurePolicyIgnore LifecycleHookFailurePolicy = "Ignore"
)
// ExecNewPodHook is a hook implementation which runs a command in a new pod
// based on the specified container which is assumed to be part of the
// deployment template.
type ExecNewPodHook struct {
// Command is the action command and its arguments.
Command []string
// Env is a set of environment variables to supply to the hook pod's container.
Env []kapi.EnvVar
// ContainerName is the name of a container in the deployment pod template
// whose Docker image will be used for the hook pod's container.
ContainerName string
// Volumes is a list of named volumes from the pod template which should be
// copied to the hook pod. Volumes names not found in pod spec are ignored.
// An empty list means no volumes will be copied.
Volumes []string
}
// TagImageHook is a request to tag the image in a particular container onto an ImageStreamTag.
type TagImageHook struct {
// ContainerName is the name of a container in the deployment config whose image value will be used as the source of the tag. If there is only a single
// container this value will be defaulted to the name of that container.
ContainerName string
// To is the target ImageStreamTag to set the container's image onto.
To kapi.ObjectReference
}
// DeploymentTriggerPolicy describes a policy for a single trigger that results in a new deployment.
@@ -377,9 +356,7 @@ const (
// DeploymentTriggerImageChangeParams represents the parameters to the ImageChange trigger.
type DeploymentTriggerImageChangeParams struct {
// Automatic means that the detection of a new tag value should result in an image update
// inside the pod template. Deployment configs that haven't been deployed yet will always
// have their images updated. Deployment configs that have been deployed at least once, will
// have their images updated only if this is set to true.
// inside the pod template.
Automatic bool
// ContainerNames is used to restrict tag updates to the specified set of container names in a pod.
ContainerNames []string
@@ -391,6 +368,29 @@ type DeploymentTriggerImageChangeParams struct {
LastTriggeredImage string
}
// DeploymentConfigStatus represents the current deployment state.
type DeploymentConfigStatus struct {
// LatestVersion is used to determine whether the current deployment associated with a deployment
// config is out of sync.
LatestVersion int64
// ObservedGeneration is the most recent generation observed by the deployment config controller.
ObservedGeneration int64
// Replicas is the total number of pods targeted by this deployment config.
Replicas int32
// UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config
// that have the desired template spec.
UpdatedReplicas int32
// AvailableReplicas is the total number of available pods targeted by this deployment config.
AvailableReplicas int32
// UnavailableReplicas is the total number of unavailable pods targeted by this deployment config.
UnavailableReplicas int32
// Details are the reasons for the update to this deployment config.
// This could be based on a change made by the user or caused by an automatic trigger
Details *DeploymentDetails
// Conditions represents the latest available observations of a deployment config's current state.
Conditions []DeploymentCondition
}
// DeploymentDetails captures information about the causes of a deployment.
type DeploymentDetails struct {
// Message is the user specified change message, if this deployment was triggered manually by the user
@@ -414,6 +414,37 @@ type DeploymentCauseImageTrigger struct {
From kapi.ObjectReference
}
type DeploymentConditionType string
// These are valid conditions of a deployment config.
const (
// DeploymentAvailable means the deployment config is available, ie. at least the minimum available
// replicas required are up and running for at least minReadySeconds.
DeploymentAvailable DeploymentConditionType = "Available"
// DeploymentProgressing means the deployment config is progressing. Progress for a deployment
// config is considered when a new replica set is created or adopted, and when new pods scale up or
// old pods scale down. Progress is not estimated for paused deployment configs, when the deployment
// config needs to rollback, or when progressDeadlineSeconds is not specified.
DeploymentProgressing DeploymentConditionType = "Progressing"
// DeploymentReplicaFailure is added in a deployment config when one of its pods
// fails to be created or deleted.
DeploymentReplicaFailure DeploymentConditionType = "ReplicaFailure"
)
// DeploymentCondition describes the state of a deployment config at a certain point.
type DeploymentCondition struct {
// Type of deployment condition.
Type DeploymentConditionType
// Status of the condition, one of True, False, Unknown.
Status kapi.ConditionStatus
// The last time the condition transitioned from one status to another.
LastTransitionTime unversioned.Time
// The reason for the condition's last transition.
Reason string
// A human readable message indicating details about the transition.
Message string
}
// DeploymentConfigList is a collection of deployment configs.
type DeploymentConfigList struct {
unversioned.TypeMeta
@@ -450,6 +481,18 @@ type DeploymentConfigRollbackSpec struct {
IncludeStrategy bool
}
// DeploymentRequest is a request to a deployment config for a new deployment.
type DeploymentRequest struct {
unversioned.TypeMeta
// Name of the deployment config for requesting a new deployment.
Name string
// Latest will update the deployment config with the latest state from all triggers.
Latest bool
// Force will try to force a new deployment to run. If the deployment config is paused,
// then setting this to true will return an Invalid error.
Force bool
}
// DeploymentLog represents the logs for a deployment
type DeploymentLog struct {
unversioned.TypeMeta
+8 -27
View File
@@ -1,8 +1,6 @@
package v1
import (
"fmt"
"math"
"reflect"
"strings"
@@ -60,7 +58,6 @@ func Convert_v1_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategy
out.UpdatePeriodSeconds = in.UpdatePeriodSeconds
out.IntervalSeconds = in.IntervalSeconds
out.TimeoutSeconds = in.TimeoutSeconds
out.UpdatePercent = in.UpdatePercent
if in.Pre != nil {
if err := s.Convert(&in.Pre, &out.Pre, 0); err != nil {
@@ -72,18 +69,12 @@ func Convert_v1_RollingDeploymentStrategyParams_To_api_RollingDeploymentStrategy
return err
}
}
if in.UpdatePercent != nil {
pct := intstr.FromString(fmt.Sprintf("%d%%", int(math.Abs(float64(*in.UpdatePercent)))))
if *in.UpdatePercent > 0 {
out.MaxSurge = pct
} else {
out.MaxUnavailable = pct
}
} else {
if in.MaxUnavailable != nil {
if err := s.Convert(in.MaxUnavailable, &out.MaxUnavailable, 0); err != nil {
return err
}
}
if in.MaxSurge != nil {
if err := s.Convert(in.MaxSurge, &out.MaxSurge, 0); err != nil {
return err
}
@@ -95,7 +86,6 @@ func Convert_api_RollingDeploymentStrategyParams_To_v1_RollingDeploymentStrategy
out.UpdatePeriodSeconds = in.UpdatePeriodSeconds
out.IntervalSeconds = in.IntervalSeconds
out.TimeoutSeconds = in.TimeoutSeconds
out.UpdatePercent = in.UpdatePercent
if in.Pre != nil {
if err := s.Convert(&in.Pre, &out.Pre, 0); err != nil {
@@ -114,20 +104,11 @@ func Convert_api_RollingDeploymentStrategyParams_To_v1_RollingDeploymentStrategy
if out.MaxSurge == nil {
out.MaxSurge = &intstr.IntOrString{}
}
if in.UpdatePercent != nil {
pct := intstr.FromString(fmt.Sprintf("%d%%", int(math.Abs(float64(*in.UpdatePercent)))))
if *in.UpdatePercent > 0 {
out.MaxSurge = &pct
} else {
out.MaxUnavailable = &pct
}
} else {
if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil {
return err
}
if err := s.Convert(&in.MaxSurge, out.MaxSurge, 0); err != nil {
return err
}
if err := s.Convert(&in.MaxUnavailable, out.MaxUnavailable, 0); err != nil {
return err
}
if err := s.Convert(&in.MaxSurge, out.MaxSurge, 0); err != nil {
return err
}
return nil
}
+19 -10
View File
@@ -71,6 +71,7 @@ func SetDefaults_RecreateDeploymentStrategyParams(obj *RecreateDeploymentStrateg
obj.TimeoutSeconds = mkintp(deployapi.DefaultRollingTimeoutSeconds)
}
}
func SetDefaults_RollingDeploymentStrategyParams(obj *RollingDeploymentStrategyParams) {
if obj.IntervalSeconds == nil {
obj.IntervalSeconds = mkintp(deployapi.DefaultRollingIntervalSeconds)
@@ -84,16 +85,24 @@ func SetDefaults_RollingDeploymentStrategyParams(obj *RollingDeploymentStrategyP
obj.TimeoutSeconds = mkintp(deployapi.DefaultRollingTimeoutSeconds)
}
if obj.UpdatePercent == nil {
// Apply defaults.
if obj.MaxUnavailable == nil {
maxUnavailable := intstr.FromString("25%")
obj.MaxUnavailable = &maxUnavailable
}
if obj.MaxSurge == nil {
maxSurge := intstr.FromString("25%")
obj.MaxSurge = &maxSurge
}
if obj.MaxUnavailable == nil && obj.MaxSurge == nil {
maxUnavailable := intstr.FromString("25%")
obj.MaxUnavailable = &maxUnavailable
maxSurge := intstr.FromString("25%")
obj.MaxSurge = &maxSurge
}
if obj.MaxUnavailable == nil && obj.MaxSurge != nil &&
(*obj.MaxSurge == intstr.FromInt(0) || *obj.MaxSurge == intstr.FromString("0%")) {
maxUnavailable := intstr.FromString("25%")
obj.MaxUnavailable = &maxUnavailable
}
if obj.MaxSurge == nil && obj.MaxUnavailable != nil &&
(*obj.MaxUnavailable == intstr.FromInt(0) || *obj.MaxUnavailable == intstr.FromString("0%")) {
maxSurge := intstr.FromString("25%")
obj.MaxSurge = &maxSurge
}
}
File diff suppressed because it is too large Load Diff
-410
View File
@@ -1,410 +0,0 @@
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
syntax = 'proto2';
package github.com.openshift.origin.pkg.deploy.api.v1;
import "k8s.io/kubernetes/pkg/api/unversioned/generated.proto";
import "k8s.io/kubernetes/pkg/api/v1/generated.proto";
import "k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.proto";
import "k8s.io/kubernetes/pkg/runtime/generated.proto";
import "k8s.io/kubernetes/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1";
// CustomDeploymentStrategyParams are the input to the Custom deployment strategy.
message CustomDeploymentStrategyParams {
// Image specifies a Docker image which can carry out a deployment.
optional string image = 1;
// Environment holds the environment which will be given to the container for Image.
repeated k8s.io.kubernetes.pkg.api.v1.EnvVar environment = 2;
// Command is optional and overrides CMD in the container Image.
repeated string command = 3;
}
// DeploymentCause captures information about a particular cause of a deployment.
message DeploymentCause {
// Type of the trigger that resulted in the creation of a new deployment
optional string type = 1;
// ImageTrigger contains the image trigger details, if this trigger was fired based on an image change
optional DeploymentCauseImageTrigger imageTrigger = 2;
}
// DeploymentCauseImageTrigger represents details about the cause of a deployment originating
// from an image change trigger
message DeploymentCauseImageTrigger {
// From is a reference to the changed object which triggered a deployment. The field may have
// the kinds DockerImage, ImageStreamTag, or ImageStreamImage.
optional k8s.io.kubernetes.pkg.api.v1.ObjectReference from = 1;
}
// DeploymentConfig represents a configuration for a single deployment (represented as a
// ReplicationController). It also contains details about changes which resulted in the current
// state of the DeploymentConfig. Each change to the DeploymentConfig which should result in
// a new deployment results in an increment of LatestVersion.
message DeploymentConfig {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// Spec represents a desired deployment state and how to deploy to it.
optional DeploymentConfigSpec spec = 2;
// Status represents the current deployment state.
optional DeploymentConfigStatus status = 3;
}
// DeploymentConfigList is a collection of deployment configs.
message DeploymentConfigList {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.unversioned.ListMeta metadata = 1;
// Items is a list of deployment configs
repeated DeploymentConfig items = 2;
}
// DeploymentConfigRollback provides the input to rollback generation.
message DeploymentConfigRollback {
// Name of the deployment config that will be rolled back.
optional string name = 1;
// UpdatedAnnotations is a set of new annotations that will be added in the deployment config.
map<string, string> updatedAnnotations = 2;
// Spec defines the options to rollback generation.
optional DeploymentConfigRollbackSpec spec = 3;
}
// DeploymentConfigRollbackSpec represents the options for rollback generation.
message DeploymentConfigRollbackSpec {
// From points to a ReplicationController which is a deployment.
optional k8s.io.kubernetes.pkg.api.v1.ObjectReference from = 1;
// Revision to rollback to. If set to 0, rollback to the last revision.
optional int64 revision = 2;
// IncludeTriggers specifies whether to include config Triggers.
optional bool includeTriggers = 3;
// IncludeTemplate specifies whether to include the PodTemplateSpec.
optional bool includeTemplate = 4;
// IncludeReplicationMeta specifies whether to include the replica count and selector.
optional bool includeReplicationMeta = 5;
// IncludeStrategy specifies whether to include the deployment Strategy.
optional bool includeStrategy = 6;
}
// DeploymentConfigSpec represents the desired state of the deployment.
message DeploymentConfigSpec {
// Strategy describes how a deployment is executed.
optional DeploymentStrategy strategy = 1;
// MinReadySeconds is the minimum number of seconds for which a newly created pod should
// be ready without any of its container crashing, for it to be considered available.
// Defaults to 0 (pod will be considered available as soon as it is ready)
optional int32 minReadySeconds = 9;
// Triggers determine how updates to a DeploymentConfig result in new deployments. If no triggers
// are defined, a new deployment can only occur as a result of an explicit client update to the
// DeploymentConfig with a new LatestVersion. If null, defaults to having a config change trigger.
optional DeploymentTriggerPolicies triggers = 2;
// Replicas is the number of desired replicas.
optional int32 replicas = 3;
// RevisionHistoryLimit is the number of old ReplicationControllers to retain to allow for rollbacks.
// This field is a pointer to allow for differentiation between an explicit zero and not specified.
optional int32 revisionHistoryLimit = 4;
// Test ensures that this deployment config will have zero replicas except while a deployment is running. This allows the
// deployment config to be used as a continuous deployment test - triggering on images, running the deployment, and then succeeding
// or failing. Post strategy hooks and After actions can be used to integrate successful deployment with an action.
optional bool test = 5;
// Paused indicates that the deployment config is paused resulting in no new deployments on template
// changes or changes in the template caused by other triggers.
optional bool paused = 6;
// Selector is a label query over pods that should match the Replicas count.
map<string, string> selector = 7;
// Template is the object that describes the pod that will be created if
// insufficient replicas are detected.
optional k8s.io.kubernetes.pkg.api.v1.PodTemplateSpec template = 8;
}
// DeploymentConfigStatus represents the current deployment state.
message DeploymentConfigStatus {
// LatestVersion is used to determine whether the current deployment associated with a deployment
// config is out of sync.
optional int64 latestVersion = 1;
// ObservedGeneration is the most recent generation observed by the deployment config controller.
optional int64 observedGeneration = 2;
// Replicas is the total number of pods targeted by this deployment config.
optional int32 replicas = 3;
// UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config
// that have the desired template spec.
optional int32 updatedReplicas = 4;
// AvailableReplicas is the total number of available pods targeted by this deployment config.
optional int32 availableReplicas = 5;
// UnavailableReplicas is the total number of unavailable pods targeted by this deployment config.
optional int32 unavailableReplicas = 6;
// Details are the reasons for the update to this deployment config.
// This could be based on a change made by the user or caused by an automatic trigger
optional DeploymentDetails details = 7;
}
// DeploymentDetails captures information about the causes of a deployment.
message DeploymentDetails {
// Message is the user specified change message, if this deployment was triggered manually by the user
optional string message = 1;
// Causes are extended data associated with all the causes for creating a new deployment
repeated DeploymentCause causes = 2;
}
// DeploymentLog represents the logs for a deployment
message DeploymentLog {
}
// DeploymentLogOptions is the REST options for a deployment log
message DeploymentLogOptions {
// The container for which to stream logs. Defaults to only container if there is one container in the pod.
optional string container = 1;
// Follow if true indicates that the build log should be streamed until
// the build terminates.
optional bool follow = 2;
// Return previous deployment logs. Defaults to false.
optional bool previous = 3;
// A relative time in seconds before the current time from which to show logs. If this value
// precedes the time a pod was started, only logs since the pod start will be returned.
// If this value is in the future, no logs will be returned.
// Only one of sinceSeconds or sinceTime may be specified.
optional int64 sinceSeconds = 4;
// An RFC3339 timestamp from which to show logs. If this value
// precedes the time a pod was started, only logs since the pod start will be returned.
// If this value is in the future, no logs will be returned.
// Only one of sinceSeconds or sinceTime may be specified.
optional k8s.io.kubernetes.pkg.api.unversioned.Time sinceTime = 5;
// If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line
// of log output. Defaults to false.
optional bool timestamps = 6;
// If set, the number of lines from the end of the logs to show. If not specified,
// logs are shown from the creation of the container or sinceSeconds or sinceTime
optional int64 tailLines = 7;
// If set, the number of bytes to read from the server before terminating the
// log output. This may not display a complete final line of logging, and may return
// slightly more or slightly less than the specified limit.
optional int64 limitBytes = 8;
// NoWait if true causes the call to return immediately even if the deployment
// is not available yet. Otherwise the server will wait until the deployment has started.
// TODO: Fix the tag to 'noWait' in v2
optional bool nowait = 9;
// Version of the deployment for which to view logs.
optional int64 version = 10;
}
// DeploymentStrategy describes how to perform a deployment.
message DeploymentStrategy {
// Type is the name of a deployment strategy.
optional string type = 1;
// CustomParams are the input to the Custom deployment strategy.
optional CustomDeploymentStrategyParams customParams = 2;
// RecreateParams are the input to the Recreate deployment strategy.
optional RecreateDeploymentStrategyParams recreateParams = 3;
// RollingParams are the input to the Rolling deployment strategy.
optional RollingDeploymentStrategyParams rollingParams = 4;
// Resources contains resource requirements to execute the deployment and any hooks
optional k8s.io.kubernetes.pkg.api.v1.ResourceRequirements resources = 5;
// Labels is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
map<string, string> labels = 6;
// Annotations is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
map<string, string> annotations = 7;
}
// DeploymentTriggerImageChangeParams represents the parameters to the ImageChange trigger.
message DeploymentTriggerImageChangeParams {
// Automatic means that the detection of a new tag value should result in an image update
// inside the pod template. Deployment configs that haven't been deployed yet will always
// have their images updated. Deployment configs that have been deployed at least once, will
// have their images updated only if this is set to true.
optional bool automatic = 1;
// ContainerNames is used to restrict tag updates to the specified set of container names in a pod.
repeated string containerNames = 2;
// From is a reference to an image stream tag to watch for changes. From.Name is the only
// required subfield - if From.Namespace is blank, the namespace of the current deployment
// trigger will be used.
optional k8s.io.kubernetes.pkg.api.v1.ObjectReference from = 3;
// LastTriggeredImage is the last image to be triggered.
optional string lastTriggeredImage = 4;
}
// DeploymentTriggerPolicies is a list of policies where nil values and different from empty arrays.
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
message DeploymentTriggerPolicies {
// items, if empty, will result in an empty slice
repeated DeploymentTriggerPolicy items = 1;
}
// DeploymentTriggerPolicy describes a policy for a single trigger that results in a new deployment.
message DeploymentTriggerPolicy {
// Type of the trigger
optional string type = 1;
// ImageChangeParams represents the parameters for the ImageChange trigger.
optional DeploymentTriggerImageChangeParams imageChangeParams = 2;
}
// ExecNewPodHook is a hook implementation which runs a command in a new pod
// based on the specified container which is assumed to be part of the
// deployment template.
message ExecNewPodHook {
// Command is the action command and its arguments.
repeated string command = 1;
// Env is a set of environment variables to supply to the hook pod's container.
repeated k8s.io.kubernetes.pkg.api.v1.EnvVar env = 2;
// ContainerName is the name of a container in the deployment pod template
// whose Docker image will be used for the hook pod's container.
optional string containerName = 3;
// Volumes is a list of named volumes from the pod template which should be
// copied to the hook pod. Volumes names not found in pod spec are ignored.
// An empty list means no volumes will be copied.
repeated string volumes = 4;
}
// LifecycleHook defines a specific deployment lifecycle action. Only one type of action may be specified at any time.
message LifecycleHook {
// FailurePolicy specifies what action to take if the hook fails.
optional string failurePolicy = 1;
// ExecNewPod specifies the options for a lifecycle hook backed by a pod.
optional ExecNewPodHook execNewPod = 2;
// TagImages instructs the deployer to tag the current image referenced under a container onto an image stream tag.
repeated TagImageHook tagImages = 3;
}
// RecreateDeploymentStrategyParams are the input to the Recreate deployment
// strategy.
message RecreateDeploymentStrategyParams {
// TimeoutSeconds is the time to wait for updates before giving up. If the
// value is nil, a default will be used.
optional int64 timeoutSeconds = 1;
// Pre is a lifecycle hook which is executed before the strategy manipulates
// the deployment. All LifecycleHookFailurePolicy values are supported.
optional LifecycleHook pre = 2;
// Mid is a lifecycle hook which is executed while the deployment is scaled down to zero before the first new
// pod is created. All LifecycleHookFailurePolicy values are supported.
optional LifecycleHook mid = 3;
// Post is a lifecycle hook which is executed after the strategy has
// finished all deployment logic. All LifecycleHookFailurePolicy values are supported.
optional LifecycleHook post = 4;
}
// RollingDeploymentStrategyParams are the input to the Rolling deployment
// strategy.
message RollingDeploymentStrategyParams {
// UpdatePeriodSeconds is the time to wait between individual pod updates.
// If the value is nil, a default will be used.
optional int64 updatePeriodSeconds = 1;
// IntervalSeconds is the time to wait between polling deployment status
// after update. If the value is nil, a default will be used.
optional int64 intervalSeconds = 2;
// TimeoutSeconds is the time to wait for updates before giving up. If the
// value is nil, a default will be used.
optional int64 timeoutSeconds = 3;
// MaxUnavailable is the maximum number of pods that can be unavailable
// during the update. Value can be an absolute number (ex: 5) or a
// percentage of total pods at the start of update (ex: 10%). Absolute
// number is calculated from percentage by rounding up.
//
// This cannot be 0 if MaxSurge is 0. By default, 25% is used.
//
// Example: when this is set to 30%, the old RC can be scaled down by 30%
// immediately when the rolling update starts. Once new pods are ready, old
// RC can be scaled down further, followed by scaling up the new RC,
// ensuring that at least 70% of original number of pods are available at
// all times during the update.
optional k8s.io.kubernetes.pkg.util.intstr.IntOrString maxUnavailable = 4;
// MaxSurge is the maximum number of pods that can be scheduled above the
// original number of pods. Value can be an absolute number (ex: 5) or a
// percentage of total pods at the start of the update (ex: 10%). Absolute
// number is calculated from percentage by rounding up.
//
// This cannot be 0 if MaxUnavailable is 0. By default, 25% is used.
//
// Example: when this is set to 30%, the new RC can be scaled up by 30%
// immediately when the rolling update starts. Once old pods have been
// killed, new RC can be scaled up further, ensuring that total number of
// pods running at any time during the update is atmost 130% of original
// pods.
optional k8s.io.kubernetes.pkg.util.intstr.IntOrString maxSurge = 5;
// UpdatePercent is the percentage of replicas to scale up or down each
// interval. If nil, one replica will be scaled up and down each interval.
// If negative, the scale order will be down/up instead of up/down.
// DEPRECATED: Use MaxUnavailable/MaxSurge instead.
optional int32 updatePercent = 6;
// Pre is a lifecycle hook which is executed before the deployment process
// begins. All LifecycleHookFailurePolicy values are supported.
optional LifecycleHook pre = 7;
// Post is a lifecycle hook which is executed after the strategy has
// finished all deployment logic. The LifecycleHookFailurePolicyAbort policy
// is NOT supported.
optional LifecycleHook post = 8;
}
// TagImageHook is a request to tag the image in a particular container onto an ImageStreamTag.
message TagImageHook {
// ContainerName is the name of a container in the deployment config whose image value will be used as the source of the tag. If there is only a single
// container this value will be defaulted to the name of that container.
optional string containerName = 1;
// To is the target ImageStreamTag to set the container's image onto.
optional k8s.io.kubernetes.pkg.api.v1.ObjectReference to = 2;
}
+2
View File
@@ -21,6 +21,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&DeploymentConfig{},
&DeploymentConfigList{},
&DeploymentConfigRollback{},
&DeploymentRequest{},
&DeploymentLog{},
&DeploymentLogOptions{},
)
@@ -30,5 +31,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
func (obj *DeploymentConfig) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentConfigList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentConfigRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentRequest) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentLog) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentLogOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
+30 -6
View File
@@ -35,8 +35,21 @@ func (DeploymentCauseImageTrigger) SwaggerDoc() map[string]string {
return map_DeploymentCauseImageTrigger
}
var map_DeploymentCondition = map[string]string{
"": "DeploymentCondition describes the state of a deployment config at a certain point.",
"type": "Type of deployment condition.",
"status": "Status of the condition, one of True, False, Unknown.",
"lastTransitionTime": "The last time the condition transitioned from one status to another.",
"reason": "The reason for the condition's last transition.",
"message": "A human readable message indicating details about the transition.",
}
func (DeploymentCondition) SwaggerDoc() map[string]string {
return map_DeploymentCondition
}
var map_DeploymentConfig = map[string]string{
"": "DeploymentConfig represents a configuration for a single deployment (represented as a ReplicationController). It also contains details about changes which resulted in the current state of the DeploymentConfig. Each change to the DeploymentConfig which should result in a new deployment results in an increment of LatestVersion.",
"": "Deployment Configs define the template for a pod and manages deploying new images or configuration changes. A single deployment configuration is usually analogous to a single micro-service. Can support many different deployment patterns, including full restart, customizable rolling updates, and fully custom behaviors, as well as pre- and post- deployment hooks. Each individual deployment is represented as a replication controller.\n\nA deployment is \"triggered\" when its configuration is changed or a tag in an Image Stream is changed. Triggers can be disabled to allow manual control over a deployment. The \"strategy\" determines how the deployment is carried out and may be changed at any time. The `latestVersion` field is updated when a new deployment is triggered by any means.",
"metadata": "Standard object's metadata.",
"spec": "Spec represents a desired deployment state and how to deploy to it.",
"status": "Status represents the current deployment state.",
@@ -107,6 +120,7 @@ var map_DeploymentConfigStatus = map[string]string{
"availableReplicas": "AvailableReplicas is the total number of available pods targeted by this deployment config.",
"unavailableReplicas": "UnavailableReplicas is the total number of unavailable pods targeted by this deployment config.",
"details": "Details are the reasons for the update to this deployment config. This could be based on a change made by the user or caused by an automatic trigger",
"conditions": "Conditions represents the latest available observations of a deployment config's current state.",
}
func (DeploymentConfigStatus) SwaggerDoc() map[string]string {
@@ -149,13 +163,24 @@ func (DeploymentLogOptions) SwaggerDoc() map[string]string {
return map_DeploymentLogOptions
}
var map_DeploymentRequest = map[string]string{
"": "DeploymentRequest is a request to a deployment config for a new deployment.",
"name": "Name of the deployment config for requesting a new deployment.",
"latest": "Latest will update the deployment config with the latest state from all triggers.",
"force": "Force will try to force a new deployment to run. If the deployment config is paused, then setting this to true will return an Invalid error.",
}
func (DeploymentRequest) SwaggerDoc() map[string]string {
return map_DeploymentRequest
}
var map_DeploymentStrategy = map[string]string{
"": "DeploymentStrategy describes how to perform a deployment.",
"type": "Type is the name of a deployment strategy.",
"customParams": "CustomParams are the input to the Custom deployment strategy.",
"customParams": "CustomParams are the input to the Custom deployment strategy, and may also be specified for the Recreate and Rolling strategies to customize the execution process that runs the deployment.",
"recreateParams": "RecreateParams are the input to the Recreate deployment strategy.",
"rollingParams": "RollingParams are the input to the Rolling deployment strategy.",
"resources": "Resources contains resource requirements to execute the deployment and any hooks",
"resources": "Resources contains resource requirements to execute the deployment and any hooks.",
"labels": "Labels is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.",
"annotations": "Annotations is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.",
}
@@ -166,7 +191,7 @@ func (DeploymentStrategy) SwaggerDoc() map[string]string {
var map_DeploymentTriggerImageChangeParams = map[string]string{
"": "DeploymentTriggerImageChangeParams represents the parameters to the ImageChange trigger.",
"automatic": "Automatic means that the detection of a new tag value should result in an image update inside the pod template. Deployment configs that haven't been deployed yet will always have their images updated. Deployment configs that have been deployed at least once, will have their images updated only if this is set to true.",
"automatic": "Automatic means that the detection of a new tag value should result in an image update inside the pod template.",
"containerNames": "ContainerNames is used to restrict tag updates to the specified set of container names in a pod.",
"from": "From is a reference to an image stream tag to watch for changes. From.Name is the only required subfield - if From.Namespace is blank, the namespace of the current deployment trigger will be used.",
"lastTriggeredImage": "LastTriggeredImage is the last image to be triggered.",
@@ -228,9 +253,8 @@ var map_RollingDeploymentStrategyParams = map[string]string{
"timeoutSeconds": "TimeoutSeconds is the time to wait for updates before giving up. If the value is nil, a default will be used.",
"maxUnavailable": "MaxUnavailable is the maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total pods at the start of update (ex: 10%). Absolute number is calculated from percentage by rounding up.\n\nThis cannot be 0 if MaxSurge is 0. By default, 25% is used.\n\nExample: when this is set to 30%, the old RC can be scaled down by 30% immediately when the rolling update starts. Once new pods are ready, old RC can be scaled down further, followed by scaling up the new RC, ensuring that at least 70% of original number of pods are available at all times during the update.",
"maxSurge": "MaxSurge is the maximum number of pods that can be scheduled above the original number of pods. Value can be an absolute number (ex: 5) or a percentage of total pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up.\n\nThis cannot be 0 if MaxUnavailable is 0. By default, 25% is used.\n\nExample: when this is set to 30%, the new RC can be scaled up by 30% immediately when the rolling update starts. Once old pods have been killed, new RC can be scaled up further, ensuring that total number of pods running at any time during the update is atmost 130% of original pods.",
"updatePercent": "UpdatePercent is the percentage of replicas to scale up or down each interval. If nil, one replica will be scaled up and down each interval. If negative, the scale order will be down/up instead of up/down. DEPRECATED: Use MaxUnavailable/MaxSurge instead.",
"pre": "Pre is a lifecycle hook which is executed before the deployment process begins. All LifecycleHookFailurePolicy values are supported.",
"post": "Post is a lifecycle hook which is executed after the strategy has finished all deployment logic. The LifecycleHookFailurePolicyAbort policy is NOT supported.",
"post": "Post is a lifecycle hook which is executed after the strategy has finished all deployment logic. All LifecycleHookFailurePolicy values are supported.",
}
func (RollingDeploymentStrategyParams) SwaggerDoc() map[string]string {
+177 -199
View File
@@ -8,37 +8,83 @@ import (
"k8s.io/kubernetes/pkg/util/intstr"
)
// DeploymentPhase describes the possible states a deployment can be in.
type DeploymentPhase string
// +genclient=true
const (
// DeploymentPhaseNew means the deployment has been accepted but not yet acted upon.
DeploymentPhaseNew DeploymentPhase = "New"
// DeploymentPhasePending means the deployment been handed over to a deployment strategy,
// but the strategy has not yet declared the deployment to be running.
DeploymentPhasePending DeploymentPhase = "Pending"
// DeploymentPhaseRunning means the deployment strategy has reported the deployment as
// being in-progress.
DeploymentPhaseRunning DeploymentPhase = "Running"
// DeploymentPhaseComplete means the deployment finished without an error.
DeploymentPhaseComplete DeploymentPhase = "Complete"
// DeploymentPhaseFailed means the deployment finished with an error.
DeploymentPhaseFailed DeploymentPhase = "Failed"
)
// Deployment Configs define the template for a pod and manages deploying new images or configuration changes.
// A single deployment configuration is usually analogous to a single micro-service. Can support many different
// deployment patterns, including full restart, customizable rolling updates, and fully custom behaviors, as
// well as pre- and post- deployment hooks. Each individual deployment is represented as a replication controller.
//
// A deployment is "triggered" when its configuration is changed or a tag in an Image Stream is changed.
// Triggers can be disabled to allow manual control over a deployment. The "strategy" determines how the deployment
// is carried out and may be changed at any time. The `latestVersion` field is updated when a new deployment
// is triggered by any means.
type DeploymentConfig struct {
unversioned.TypeMeta `json:",inline"`
// Standard object's metadata.
kapi.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec represents a desired deployment state and how to deploy to it.
Spec DeploymentConfigSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
// Status represents the current deployment state.
Status DeploymentConfigStatus `json:"status" protobuf:"bytes,3,opt,name=status"`
}
// DeploymentConfigSpec represents the desired state of the deployment.
type DeploymentConfigSpec struct {
// Strategy describes how a deployment is executed.
Strategy DeploymentStrategy `json:"strategy" protobuf:"bytes,1,opt,name=strategy"`
// MinReadySeconds is the minimum number of seconds for which a newly created pod should
// be ready without any of its container crashing, for it to be considered available.
// Defaults to 0 (pod will be considered available as soon as it is ready)
MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,9,opt,name=minReadySeconds"`
// Triggers determine how updates to a DeploymentConfig result in new deployments. If no triggers
// are defined, a new deployment can only occur as a result of an explicit client update to the
// DeploymentConfig with a new LatestVersion. If null, defaults to having a config change trigger.
Triggers DeploymentTriggerPolicies `json:"triggers" protobuf:"bytes,2,rep,name=triggers"`
// Replicas is the number of desired replicas.
Replicas int32 `json:"replicas" protobuf:"varint,3,opt,name=replicas"`
// RevisionHistoryLimit is the number of old ReplicationControllers to retain to allow for rollbacks.
// This field is a pointer to allow for differentiation between an explicit zero and not specified.
RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,4,opt,name=revisionHistoryLimit"`
// Test ensures that this deployment config will have zero replicas except while a deployment is running. This allows the
// deployment config to be used as a continuous deployment test - triggering on images, running the deployment, and then succeeding
// or failing. Post strategy hooks and After actions can be used to integrate successful deployment with an action.
Test bool `json:"test" protobuf:"varint,5,opt,name=test"`
// Paused indicates that the deployment config is paused resulting in no new deployments on template
// changes or changes in the template caused by other triggers.
Paused bool `json:"paused,omitempty" protobuf:"varint,6,opt,name=paused"`
// Selector is a label query over pods that should match the Replicas count.
Selector map[string]string `json:"selector,omitempty" protobuf:"bytes,7,rep,name=selector"`
// Template is the object that describes the pod that will be created if
// insufficient replicas are detected.
Template *kapi.PodTemplateSpec `json:"template,omitempty" protobuf:"bytes,8,opt,name=template"`
}
// DeploymentStrategy describes how to perform a deployment.
type DeploymentStrategy struct {
// Type is the name of a deployment strategy.
Type DeploymentStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type,casttype=DeploymentStrategyType"`
// CustomParams are the input to the Custom deployment strategy.
// CustomParams are the input to the Custom deployment strategy, and may also
// be specified for the Recreate and Rolling strategies to customize the execution
// process that runs the deployment.
CustomParams *CustomDeploymentStrategyParams `json:"customParams,omitempty" protobuf:"bytes,2,opt,name=customParams"`
// RecreateParams are the input to the Recreate deployment strategy.
RecreateParams *RecreateDeploymentStrategyParams `json:"recreateParams,omitempty" protobuf:"bytes,3,opt,name=recreateParams"`
// RollingParams are the input to the Rolling deployment strategy.
RollingParams *RollingDeploymentStrategyParams `json:"rollingParams,omitempty" protobuf:"bytes,4,opt,name=rollingParams"`
// Resources contains resource requirements to execute the deployment and any hooks
// Resources contains resource requirements to execute the deployment and any hooks.
Resources kapi.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,5,opt,name=resources"`
// Labels is a set of key, value pairs added to custom deployer and lifecycle pre/post hook pods.
Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,6,rep,name=labels"`
@@ -85,56 +131,6 @@ type RecreateDeploymentStrategyParams struct {
Post *LifecycleHook `json:"post,omitempty" protobuf:"bytes,4,opt,name=post"`
}
// LifecycleHook defines a specific deployment lifecycle action. Only one type of action may be specified at any time.
type LifecycleHook struct {
// FailurePolicy specifies what action to take if the hook fails.
FailurePolicy LifecycleHookFailurePolicy `json:"failurePolicy" protobuf:"bytes,1,opt,name=failurePolicy,casttype=LifecycleHookFailurePolicy"`
// ExecNewPod specifies the options for a lifecycle hook backed by a pod.
ExecNewPod *ExecNewPodHook `json:"execNewPod,omitempty" protobuf:"bytes,2,opt,name=execNewPod"`
// TagImages instructs the deployer to tag the current image referenced under a container onto an image stream tag.
TagImages []TagImageHook `json:"tagImages,omitempty" protobuf:"bytes,3,rep,name=tagImages"`
}
// LifecycleHookFailurePolicy describes possibles actions to take if a hook fails.
type LifecycleHookFailurePolicy string
const (
// LifecycleHookFailurePolicyRetry means retry the hook until it succeeds.
LifecycleHookFailurePolicyRetry LifecycleHookFailurePolicy = "Retry"
// LifecycleHookFailurePolicyAbort means abort the deployment (if possible).
LifecycleHookFailurePolicyAbort LifecycleHookFailurePolicy = "Abort"
// LifecycleHookFailurePolicyIgnore means ignore failure and continue the deployment.
LifecycleHookFailurePolicyIgnore LifecycleHookFailurePolicy = "Ignore"
)
// ExecNewPodHook is a hook implementation which runs a command in a new pod
// based on the specified container which is assumed to be part of the
// deployment template.
type ExecNewPodHook struct {
// Command is the action command and its arguments.
Command []string `json:"command" protobuf:"bytes,1,rep,name=command"`
// Env is a set of environment variables to supply to the hook pod's container.
Env []kapi.EnvVar `json:"env,omitempty" protobuf:"bytes,2,rep,name=env"`
// ContainerName is the name of a container in the deployment pod template
// whose Docker image will be used for the hook pod's container.
ContainerName string `json:"containerName" protobuf:"bytes,3,opt,name=containerName"`
// Volumes is a list of named volumes from the pod template which should be
// copied to the hook pod. Volumes names not found in pod spec are ignored.
// An empty list means no volumes will be copied.
Volumes []string `json:"volumes,omitempty" protobuf:"bytes,4,rep,name=volumes"`
}
// TagImageHook is a request to tag the image in a particular container onto an ImageStreamTag.
type TagImageHook struct {
// ContainerName is the name of a container in the deployment config whose image value will be used as the source of the tag. If there is only a single
// container this value will be defaulted to the name of that container.
ContainerName string `json:"containerName" protobuf:"bytes,1,opt,name=containerName"`
// To is the target ImageStreamTag to set the container's image onto.
To kapi.ObjectReference `json:"to" protobuf:"bytes,2,opt,name=to"`
}
// RollingDeploymentStrategyParams are the input to the Rolling deployment
// strategy.
type RollingDeploymentStrategyParams struct {
@@ -173,85 +169,63 @@ type RollingDeploymentStrategyParams struct {
// pods running at any time during the update is atmost 130% of original
// pods.
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,5,opt,name=maxSurge"`
// UpdatePercent is the percentage of replicas to scale up or down each
// interval. If nil, one replica will be scaled up and down each interval.
// If negative, the scale order will be down/up instead of up/down.
// DEPRECATED: Use MaxUnavailable/MaxSurge instead.
UpdatePercent *int32 `json:"updatePercent,omitempty" protobuf:"varint,6,opt,name=updatePercent"`
// Pre is a lifecycle hook which is executed before the deployment process
// begins. All LifecycleHookFailurePolicy values are supported.
Pre *LifecycleHook `json:"pre,omitempty" protobuf:"bytes,7,opt,name=pre"`
// Post is a lifecycle hook which is executed after the strategy has
// finished all deployment logic. The LifecycleHookFailurePolicyAbort policy
// is NOT supported.
// finished all deployment logic. All LifecycleHookFailurePolicy values
// are supported.
Post *LifecycleHook `json:"post,omitempty" protobuf:"bytes,8,opt,name=post"`
}
// These constants represent keys used for correlating objects related to deployments.
// LifecycleHook defines a specific deployment lifecycle action. Only one type of action may be specified at any time.
type LifecycleHook struct {
// FailurePolicy specifies what action to take if the hook fails.
FailurePolicy LifecycleHookFailurePolicy `json:"failurePolicy" protobuf:"bytes,1,opt,name=failurePolicy,casttype=LifecycleHookFailurePolicy"`
// ExecNewPod specifies the options for a lifecycle hook backed by a pod.
ExecNewPod *ExecNewPodHook `json:"execNewPod,omitempty" protobuf:"bytes,2,opt,name=execNewPod"`
// TagImages instructs the deployer to tag the current image referenced under a container onto an image stream tag.
TagImages []TagImageHook `json:"tagImages,omitempty" protobuf:"bytes,3,rep,name=tagImages"`
}
// LifecycleHookFailurePolicy describes possibles actions to take if a hook fails.
type LifecycleHookFailurePolicy string
const (
// DeploymentConfigAnnotation is an annotation name used to correlate a deployment with the
// DeploymentConfig on which the deployment is based.
DeploymentConfigAnnotation = "openshift.io/deployment-config.name"
// DeploymentAnnotation is an annotation on a deployer Pod. The annotation value is the name
// of the deployment (a ReplicationController) on which the deployer Pod acts.
DeploymentAnnotation = "openshift.io/deployment.name"
// DeploymentPodAnnotation is an annotation on a deployment (a ReplicationController). The
// annotation value is the name of the deployer Pod which will act upon the ReplicationController
// to implement the deployment behavior.
DeploymentPodAnnotation = "openshift.io/deployer-pod.name"
// DeploymentPodTypeLabel is a label with which contains a type of deployment pod.
DeploymentPodTypeLabel = "openshift.io/deployer-pod.type"
// DeployerPodForDeploymentLabel is a label which groups pods related to a
// deployment. The value is a deployment name. The deployer pod and hook pods
// created by the internal strategies will have this label. Custom
// strategies can apply this label to any pods they create, enabling
// platform-provided cancellation and garbage collection support.
DeployerPodForDeploymentLabel = "openshift.io/deployer-pod-for.name"
// DeploymentPhaseAnnotation is an annotation name used to retrieve the DeploymentPhase of
// a deployment.
DeploymentPhaseAnnotation = "openshift.io/deployment.phase"
// DeploymentEncodedConfigAnnotation is an annotation name used to retrieve specific encoded
// DeploymentConfig on which a given deployment is based.
DeploymentEncodedConfigAnnotation = "openshift.io/encoded-deployment-config"
// DeploymentVersionAnnotation is an annotation on a deployment (a ReplicationController). The
// annotation value is the LatestVersion value of the DeploymentConfig which was the basis for
// the deployment.
DeploymentVersionAnnotation = "openshift.io/deployment-config.latest-version"
// DeploymentLabel is the name of a label used to correlate a deployment with the Pod created
// to execute the deployment logic.
// TODO: This is a workaround for upstream's lack of annotation support on PodTemplate. Once
// annotations are available on PodTemplate, audit this constant with the goal of removing it.
DeploymentLabel = "deployment"
// DeploymentConfigLabel is the name of a label used to correlate a deployment with the
// DeploymentConfigs on which the deployment is based.
DeploymentConfigLabel = "deploymentconfig"
// DeploymentStatusReasonAnnotation represents the reason for deployment being in a given state
// Used for specifying the reason for cancellation or failure of a deployment
DeploymentStatusReasonAnnotation = "openshift.io/deployment.status-reason"
// DeploymentCancelledAnnotation indicates that the deployment has been cancelled
// The annotation value does not matter and its mere presence indicates cancellation
DeploymentCancelledAnnotation = "openshift.io/deployment.cancelled"
// DeploymentInstantiatedAnnotation indicates that the deployment has been instantiated.
// The annotation value does not matter and its mere presence indicates instantiation.
DeploymentInstantiatedAnnotation = "openshift.io/deployment.instantiated"
// LifecycleHookFailurePolicyRetry means retry the hook until it succeeds.
LifecycleHookFailurePolicyRetry LifecycleHookFailurePolicy = "Retry"
// LifecycleHookFailurePolicyAbort means abort the deployment.
LifecycleHookFailurePolicyAbort LifecycleHookFailurePolicy = "Abort"
// LifecycleHookFailurePolicyIgnore means ignore failure and continue the deployment.
LifecycleHookFailurePolicyIgnore LifecycleHookFailurePolicy = "Ignore"
)
// +genclient=true
// ExecNewPodHook is a hook implementation which runs a command in a new pod
// based on the specified container which is assumed to be part of the
// deployment template.
type ExecNewPodHook struct {
// Command is the action command and its arguments.
Command []string `json:"command" protobuf:"bytes,1,rep,name=command"`
// Env is a set of environment variables to supply to the hook pod's container.
Env []kapi.EnvVar `json:"env,omitempty" protobuf:"bytes,2,rep,name=env"`
// ContainerName is the name of a container in the deployment pod template
// whose Docker image will be used for the hook pod's container.
ContainerName string `json:"containerName" protobuf:"bytes,3,opt,name=containerName"`
// Volumes is a list of named volumes from the pod template which should be
// copied to the hook pod. Volumes names not found in pod spec are ignored.
// An empty list means no volumes will be copied.
Volumes []string `json:"volumes,omitempty" protobuf:"bytes,4,rep,name=volumes"`
}
// DeploymentConfig represents a configuration for a single deployment (represented as a
// ReplicationController). It also contains details about changes which resulted in the current
// state of the DeploymentConfig. Each change to the DeploymentConfig which should result in
// a new deployment results in an increment of LatestVersion.
type DeploymentConfig struct {
unversioned.TypeMeta `json:",inline"`
// Standard object's metadata.
kapi.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec represents a desired deployment state and how to deploy to it.
Spec DeploymentConfigSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
// Status represents the current deployment state.
Status DeploymentConfigStatus `json:"status" protobuf:"bytes,3,opt,name=status"`
// TagImageHook is a request to tag the image in a particular container onto an ImageStreamTag.
type TagImageHook struct {
// ContainerName is the name of a container in the deployment config whose image value will be used as the source of the tag. If there is only a single
// container this value will be defaulted to the name of that container.
ContainerName string `json:"containerName" protobuf:"bytes,1,opt,name=containerName"`
// To is the target ImageStreamTag to set the container's image onto.
To kapi.ObjectReference `json:"to" protobuf:"bytes,2,opt,name=to"`
}
// DeploymentTriggerPolicies is a list of policies where nil values and different from empty arrays.
@@ -263,66 +237,6 @@ func (t DeploymentTriggerPolicies) String() string {
return fmt.Sprintf("%v", []DeploymentTriggerPolicy(t))
}
// DeploymentConfigSpec represents the desired state of the deployment.
type DeploymentConfigSpec struct {
// Strategy describes how a deployment is executed.
Strategy DeploymentStrategy `json:"strategy" protobuf:"bytes,1,opt,name=strategy"`
// MinReadySeconds is the minimum number of seconds for which a newly created pod should
// be ready without any of its container crashing, for it to be considered available.
// Defaults to 0 (pod will be considered available as soon as it is ready)
MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,9,opt,name=minReadySeconds"`
// Triggers determine how updates to a DeploymentConfig result in new deployments. If no triggers
// are defined, a new deployment can only occur as a result of an explicit client update to the
// DeploymentConfig with a new LatestVersion. If null, defaults to having a config change trigger.
Triggers DeploymentTriggerPolicies `json:"triggers" protobuf:"bytes,2,rep,name=triggers"`
// Replicas is the number of desired replicas.
Replicas int32 `json:"replicas" protobuf:"varint,3,opt,name=replicas"`
// RevisionHistoryLimit is the number of old ReplicationControllers to retain to allow for rollbacks.
// This field is a pointer to allow for differentiation between an explicit zero and not specified.
RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,4,opt,name=revisionHistoryLimit"`
// Test ensures that this deployment config will have zero replicas except while a deployment is running. This allows the
// deployment config to be used as a continuous deployment test - triggering on images, running the deployment, and then succeeding
// or failing. Post strategy hooks and After actions can be used to integrate successful deployment with an action.
Test bool `json:"test" protobuf:"varint,5,opt,name=test"`
// Paused indicates that the deployment config is paused resulting in no new deployments on template
// changes or changes in the template caused by other triggers.
Paused bool `json:"paused,omitempty" protobuf:"varint,6,opt,name=paused"`
// Selector is a label query over pods that should match the Replicas count.
Selector map[string]string `json:"selector,omitempty" protobuf:"bytes,7,rep,name=selector"`
// Template is the object that describes the pod that will be created if
// insufficient replicas are detected.
Template *kapi.PodTemplateSpec `json:"template,omitempty" protobuf:"bytes,8,opt,name=template"`
}
// DeploymentConfigStatus represents the current deployment state.
type DeploymentConfigStatus struct {
// LatestVersion is used to determine whether the current deployment associated with a deployment
// config is out of sync.
LatestVersion int64 `json:"latestVersion,omitempty" protobuf:"varint,1,opt,name=latestVersion"`
// ObservedGeneration is the most recent generation observed by the deployment config controller.
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,2,opt,name=observedGeneration"`
// Replicas is the total number of pods targeted by this deployment config.
Replicas int32 `json:"replicas,omitempty" protobuf:"varint,3,opt,name=replicas"`
// UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config
// that have the desired template spec.
UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,4,opt,name=updatedReplicas"`
// AvailableReplicas is the total number of available pods targeted by this deployment config.
AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,5,opt,name=availableReplicas"`
// UnavailableReplicas is the total number of unavailable pods targeted by this deployment config.
UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,6,opt,name=unavailableReplicas"`
// Details are the reasons for the update to this deployment config.
// This could be based on a change made by the user or caused by an automatic trigger
Details *DeploymentDetails `json:"details,omitempty" protobuf:"bytes,7,opt,name=details"`
}
// DeploymentTriggerPolicy describes a policy for a single trigger that results in a new deployment.
type DeploymentTriggerPolicy struct {
// Type of the trigger
@@ -346,9 +260,7 @@ const (
// DeploymentTriggerImageChangeParams represents the parameters to the ImageChange trigger.
type DeploymentTriggerImageChangeParams struct {
// Automatic means that the detection of a new tag value should result in an image update
// inside the pod template. Deployment configs that haven't been deployed yet will always
// have their images updated. Deployment configs that have been deployed at least once, will
// have their images updated only if this is set to true.
// inside the pod template.
Automatic bool `json:"automatic,omitempty" protobuf:"varint,1,opt,name=automatic"`
// ContainerNames is used to restrict tag updates to the specified set of container names in a pod.
ContainerNames []string `json:"containerNames,omitempty" protobuf:"bytes,2,rep,name=containerNames"`
@@ -360,6 +272,29 @@ type DeploymentTriggerImageChangeParams struct {
LastTriggeredImage string `json:"lastTriggeredImage,omitempty" protobuf:"bytes,4,opt,name=lastTriggeredImage"`
}
// DeploymentConfigStatus represents the current deployment state.
type DeploymentConfigStatus struct {
// LatestVersion is used to determine whether the current deployment associated with a deployment
// config is out of sync.
LatestVersion int64 `json:"latestVersion,omitempty" protobuf:"varint,1,opt,name=latestVersion"`
// ObservedGeneration is the most recent generation observed by the deployment config controller.
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,2,opt,name=observedGeneration"`
// Replicas is the total number of pods targeted by this deployment config.
Replicas int32 `json:"replicas,omitempty" protobuf:"varint,3,opt,name=replicas"`
// UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config
// that have the desired template spec.
UpdatedReplicas int32 `json:"updatedReplicas,omitempty" protobuf:"varint,4,opt,name=updatedReplicas"`
// AvailableReplicas is the total number of available pods targeted by this deployment config.
AvailableReplicas int32 `json:"availableReplicas,omitempty" protobuf:"varint,5,opt,name=availableReplicas"`
// UnavailableReplicas is the total number of unavailable pods targeted by this deployment config.
UnavailableReplicas int32 `json:"unavailableReplicas,omitempty" protobuf:"varint,6,opt,name=unavailableReplicas"`
// Details are the reasons for the update to this deployment config.
// This could be based on a change made by the user or caused by an automatic trigger
Details *DeploymentDetails `json:"details,omitempty" protobuf:"bytes,7,opt,name=details"`
// Conditions represents the latest available observations of a deployment config's current state.
Conditions []DeploymentCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,8,rep,name=conditions"`
}
// DeploymentDetails captures information about the causes of a deployment.
type DeploymentDetails struct {
// Message is the user specified change message, if this deployment was triggered manually by the user
@@ -384,6 +319,37 @@ type DeploymentCauseImageTrigger struct {
From kapi.ObjectReference `json:"from" protobuf:"bytes,1,opt,name=from"`
}
type DeploymentConditionType string
// These are valid conditions of a deployment config.
const (
// DeploymentAvailable means the deployment config is available, ie. at least the minimum available
// replicas required are up and running for at least minReadySeconds.
DeploymentAvailable DeploymentConditionType = "Available"
// DeploymentProgressing means the deployment config is progressing. Progress for a deployment
// config is considered when a new replica set is created or adopted, and when new pods scale up or
// old pods scale down. Progress is not estimated for paused deployment configs, when the deployment
// config needs to rollback, or when progressDeadlineSeconds is not specified.
DeploymentProgressing DeploymentConditionType = "Progressing"
// DeploymentReplicaFailure is added in a deployment config when one of its pods
// fails to be created or deleted.
DeploymentReplicaFailure DeploymentConditionType = "ReplicaFailure"
)
// DeploymentCondition describes the state of a deployment config at a certain point.
type DeploymentCondition struct {
// Type of deployment condition.
Type DeploymentConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=DeploymentConditionType"`
// Status of the condition, one of True, False, Unknown.
Status kapi.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/kubernetes/pkg/api/v1.ConditionStatus"`
// The last time the condition transitioned from one status to another.
LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"`
// The reason for the condition's last transition.
Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
// A human readable message indicating details about the transition.
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
}
// DeploymentConfigList is a collection of deployment configs.
type DeploymentConfigList struct {
unversioned.TypeMeta `json:",inline"`
@@ -421,6 +387,18 @@ type DeploymentConfigRollbackSpec struct {
IncludeStrategy bool `json:"includeStrategy" protobuf:"varint,6,opt,name=includeStrategy"`
}
// DeploymentRequest is a request to a deployment config for a new deployment.
type DeploymentRequest struct {
unversioned.TypeMeta `json:",inline"`
// Name of the deployment config for requesting a new deployment.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// Latest will update the deployment config with the latest state from all triggers.
Latest bool `json:"latest" protobuf:"varint,2,opt,name=latest"`
// Force will try to force a new deployment to run. If the deployment config is paused,
// then setting this to true will return an Invalid error.
Force bool `json:"force" protobuf:"varint,3,opt,name=force"`
}
// DeploymentLog represents the logs for a deployment
type DeploymentLog struct {
unversioned.TypeMeta `json:",inline"`
@@ -26,6 +26,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_DeploymentCause_To_v1_DeploymentCause,
Convert_v1_DeploymentCauseImageTrigger_To_api_DeploymentCauseImageTrigger,
Convert_api_DeploymentCauseImageTrigger_To_v1_DeploymentCauseImageTrigger,
Convert_v1_DeploymentCondition_To_api_DeploymentCondition,
Convert_api_DeploymentCondition_To_v1_DeploymentCondition,
Convert_v1_DeploymentConfig_To_api_DeploymentConfig,
Convert_api_DeploymentConfig_To_v1_DeploymentConfig,
Convert_v1_DeploymentConfigList_To_api_DeploymentConfigList,
@@ -44,6 +46,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_DeploymentLog_To_v1_DeploymentLog,
Convert_v1_DeploymentLogOptions_To_api_DeploymentLogOptions,
Convert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions,
Convert_v1_DeploymentRequest_To_api_DeploymentRequest,
Convert_api_DeploymentRequest_To_v1_DeploymentRequest,
Convert_v1_DeploymentStrategy_To_api_DeploymentStrategy,
Convert_api_DeploymentStrategy_To_v1_DeploymentStrategy,
Convert_v1_DeploymentTriggerImageChangeParams_To_api_DeploymentTriggerImageChangeParams,
@@ -163,6 +167,36 @@ func Convert_api_DeploymentCauseImageTrigger_To_v1_DeploymentCauseImageTrigger(i
return autoConvert_api_DeploymentCauseImageTrigger_To_v1_DeploymentCauseImageTrigger(in, out, s)
}
func autoConvert_v1_DeploymentCondition_To_api_DeploymentCondition(in *DeploymentCondition, out *api.DeploymentCondition, s conversion.Scope) error {
out.Type = api.DeploymentConditionType(in.Type)
out.Status = pkg_api.ConditionStatus(in.Status)
if err := pkg_api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil {
return err
}
out.Reason = in.Reason
out.Message = in.Message
return nil
}
func Convert_v1_DeploymentCondition_To_api_DeploymentCondition(in *DeploymentCondition, out *api.DeploymentCondition, s conversion.Scope) error {
return autoConvert_v1_DeploymentCondition_To_api_DeploymentCondition(in, out, s)
}
func autoConvert_api_DeploymentCondition_To_v1_DeploymentCondition(in *api.DeploymentCondition, out *DeploymentCondition, s conversion.Scope) error {
out.Type = DeploymentConditionType(in.Type)
out.Status = api_v1.ConditionStatus(in.Status)
if err := pkg_api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil {
return err
}
out.Reason = in.Reason
out.Message = in.Message
return nil
}
func Convert_api_DeploymentCondition_To_v1_DeploymentCondition(in *api.DeploymentCondition, out *DeploymentCondition, s conversion.Scope) error {
return autoConvert_api_DeploymentCondition_To_v1_DeploymentCondition(in, out, s)
}
func autoConvert_v1_DeploymentConfig_To_api_DeploymentConfig(in *DeploymentConfig, out *api.DeploymentConfig, s conversion.Scope) error {
SetDefaults_DeploymentConfig(in)
if err := pkg_api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
@@ -409,6 +443,17 @@ func autoConvert_v1_DeploymentConfigStatus_To_api_DeploymentConfigStatus(in *Dep
} else {
out.Details = nil
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]api.DeploymentCondition, len(*in))
for i := range *in {
if err := Convert_v1_DeploymentCondition_To_api_DeploymentCondition(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Conditions = nil
}
return nil
}
@@ -432,6 +477,17 @@ func autoConvert_api_DeploymentConfigStatus_To_v1_DeploymentConfigStatus(in *api
} else {
out.Details = nil
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]DeploymentCondition, len(*in))
for i := range *in {
if err := Convert_api_DeploymentCondition_To_v1_DeploymentCondition(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Conditions = nil
}
return nil
}
@@ -543,6 +599,34 @@ func Convert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions(in *api.Deploym
return autoConvert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions(in, out, s)
}
func autoConvert_v1_DeploymentRequest_To_api_DeploymentRequest(in *DeploymentRequest, out *api.DeploymentRequest, s conversion.Scope) error {
if err := pkg_api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
out.Name = in.Name
out.Latest = in.Latest
out.Force = in.Force
return nil
}
func Convert_v1_DeploymentRequest_To_api_DeploymentRequest(in *DeploymentRequest, out *api.DeploymentRequest, s conversion.Scope) error {
return autoConvert_v1_DeploymentRequest_To_api_DeploymentRequest(in, out, s)
}
func autoConvert_api_DeploymentRequest_To_v1_DeploymentRequest(in *api.DeploymentRequest, out *DeploymentRequest, s conversion.Scope) error {
if err := pkg_api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
out.Name = in.Name
out.Latest = in.Latest
out.Force = in.Force
return nil
}
func Convert_api_DeploymentRequest_To_v1_DeploymentRequest(in *api.DeploymentRequest, out *DeploymentRequest, s conversion.Scope) error {
return autoConvert_api_DeploymentRequest_To_v1_DeploymentRequest(in, out, s)
}
func autoConvert_v1_DeploymentStrategy_To_api_DeploymentStrategy(in *DeploymentStrategy, out *api.DeploymentStrategy, s conversion.Scope) error {
SetDefaults_DeploymentStrategy(in)
out.Type = api.DeploymentStrategyType(in.Type)
@@ -587,6 +671,15 @@ func Convert_v1_DeploymentStrategy_To_api_DeploymentStrategy(in *DeploymentStrat
func autoConvert_api_DeploymentStrategy_To_v1_DeploymentStrategy(in *api.DeploymentStrategy, out *DeploymentStrategy, s conversion.Scope) error {
out.Type = DeploymentStrategyType(in.Type)
if in.CustomParams != nil {
in, out := &in.CustomParams, &out.CustomParams
*out = new(CustomDeploymentStrategyParams)
if err := Convert_api_CustomDeploymentStrategyParams_To_v1_CustomDeploymentStrategyParams(*in, *out, s); err != nil {
return err
}
} else {
out.CustomParams = nil
}
if in.RecreateParams != nil {
in, out := &in.RecreateParams, &out.RecreateParams
*out = new(RecreateDeploymentStrategyParams)
@@ -605,15 +698,6 @@ func autoConvert_api_DeploymentStrategy_To_v1_DeploymentStrategy(in *api.Deploym
} else {
out.RollingParams = nil
}
if in.CustomParams != nil {
in, out := &in.CustomParams, &out.CustomParams
*out = new(CustomDeploymentStrategyParams)
if err := Convert_api_CustomDeploymentStrategyParams_To_v1_CustomDeploymentStrategyParams(*in, *out, s); err != nil {
return err
}
} else {
out.CustomParams = nil
}
if err := api_v1.Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil {
return err
}
@@ -24,6 +24,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_CustomDeploymentStrategyParams, InType: reflect.TypeOf(&CustomDeploymentStrategyParams{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentCause, InType: reflect.TypeOf(&DeploymentCause{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentCauseImageTrigger, InType: reflect.TypeOf(&DeploymentCauseImageTrigger{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentCondition, InType: reflect.TypeOf(&DeploymentCondition{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentConfig, InType: reflect.TypeOf(&DeploymentConfig{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentConfigList, InType: reflect.TypeOf(&DeploymentConfigList{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentConfigRollback, InType: reflect.TypeOf(&DeploymentConfigRollback{})},
@@ -33,6 +34,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentDetails, InType: reflect.TypeOf(&DeploymentDetails{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentLog, InType: reflect.TypeOf(&DeploymentLog{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentLogOptions, InType: reflect.TypeOf(&DeploymentLogOptions{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentRequest, InType: reflect.TypeOf(&DeploymentRequest{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentStrategy, InType: reflect.TypeOf(&DeploymentStrategy{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentTriggerImageChangeParams, InType: reflect.TypeOf(&DeploymentTriggerImageChangeParams{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentTriggerPolicy, InType: reflect.TypeOf(&DeploymentTriggerPolicy{})},
@@ -96,6 +98,19 @@ func DeepCopy_v1_DeploymentCauseImageTrigger(in interface{}, out interface{}, c
}
}
func DeepCopy_v1_DeploymentCondition(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentCondition)
out := out.(*DeploymentCondition)
out.Type = in.Type
out.Status = in.Status
out.LastTransitionTime = in.LastTransitionTime.DeepCopy()
out.Reason = in.Reason
out.Message = in.Message
return nil
}
}
func DeepCopy_v1_DeploymentConfig(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentConfig)
@@ -239,6 +254,17 @@ func DeepCopy_v1_DeploymentConfigStatus(in interface{}, out interface{}, c *conv
} else {
out.Details = nil
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]DeploymentCondition, len(*in))
for i := range *in {
if err := DeepCopy_v1_DeploymentCondition(&(*in)[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Conditions = nil
}
return nil
}
}
@@ -321,6 +347,18 @@ func DeepCopy_v1_DeploymentLogOptions(in interface{}, out interface{}, c *conver
}
}
func DeepCopy_v1_DeploymentRequest(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentRequest)
out := out.(*DeploymentRequest)
out.TypeMeta = in.TypeMeta
out.Name = in.Name
out.Latest = in.Latest
out.Force = in.Force
return nil
}
}
func DeepCopy_v1_DeploymentStrategy(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentStrategy)
@@ -556,13 +594,6 @@ func DeepCopy_v1_RollingDeploymentStrategyParams(in interface{}, out interface{}
} else {
out.MaxSurge = nil
}
if in.UpdatePercent != nil {
in, out := &in.UpdatePercent, &out.UpdatePercent
*out = new(int32)
**out = **in
} else {
out.UpdatePercent = nil
}
if in.Pre != nil {
in, out := &in.Pre, &out.Pre
*out = new(LifecycleHook)
+47 -16
View File
@@ -24,6 +24,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_CustomDeploymentStrategyParams, InType: reflect.TypeOf(&CustomDeploymentStrategyParams{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentCause, InType: reflect.TypeOf(&DeploymentCause{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentCauseImageTrigger, InType: reflect.TypeOf(&DeploymentCauseImageTrigger{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentCondition, InType: reflect.TypeOf(&DeploymentCondition{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentConfig, InType: reflect.TypeOf(&DeploymentConfig{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentConfigList, InType: reflect.TypeOf(&DeploymentConfigList{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentConfigRollback, InType: reflect.TypeOf(&DeploymentConfigRollback{})},
@@ -33,6 +34,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentDetails, InType: reflect.TypeOf(&DeploymentDetails{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentLog, InType: reflect.TypeOf(&DeploymentLog{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentLogOptions, InType: reflect.TypeOf(&DeploymentLogOptions{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentRequest, InType: reflect.TypeOf(&DeploymentRequest{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentStrategy, InType: reflect.TypeOf(&DeploymentStrategy{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentTriggerImageChangeParams, InType: reflect.TypeOf(&DeploymentTriggerImageChangeParams{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentTriggerPolicy, InType: reflect.TypeOf(&DeploymentTriggerPolicy{})},
@@ -97,6 +99,19 @@ func DeepCopy_api_DeploymentCauseImageTrigger(in interface{}, out interface{}, c
}
}
func DeepCopy_api_DeploymentCondition(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentCondition)
out := out.(*DeploymentCondition)
out.Type = in.Type
out.Status = in.Status
out.LastTransitionTime = in.LastTransitionTime.DeepCopy()
out.Reason = in.Reason
out.Message = in.Message
return nil
}
}
func DeepCopy_api_DeploymentConfig(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentConfig)
@@ -240,6 +255,17 @@ func DeepCopy_api_DeploymentConfigStatus(in interface{}, out interface{}, c *con
} else {
out.Details = nil
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]DeploymentCondition, len(*in))
for i := range *in {
if err := DeepCopy_api_DeploymentCondition(&(*in)[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Conditions = nil
}
return nil
}
}
@@ -322,11 +348,32 @@ func DeepCopy_api_DeploymentLogOptions(in interface{}, out interface{}, c *conve
}
}
func DeepCopy_api_DeploymentRequest(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentRequest)
out := out.(*DeploymentRequest)
out.TypeMeta = in.TypeMeta
out.Name = in.Name
out.Latest = in.Latest
out.Force = in.Force
return nil
}
}
func DeepCopy_api_DeploymentStrategy(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*DeploymentStrategy)
out := out.(*DeploymentStrategy)
out.Type = in.Type
if in.CustomParams != nil {
in, out := &in.CustomParams, &out.CustomParams
*out = new(CustomDeploymentStrategyParams)
if err := DeepCopy_api_CustomDeploymentStrategyParams(*in, *out, c); err != nil {
return err
}
} else {
out.CustomParams = nil
}
if in.RecreateParams != nil {
in, out := &in.RecreateParams, &out.RecreateParams
*out = new(RecreateDeploymentStrategyParams)
@@ -345,15 +392,6 @@ func DeepCopy_api_DeploymentStrategy(in interface{}, out interface{}, c *convers
} else {
out.RollingParams = nil
}
if in.CustomParams != nil {
in, out := &in.CustomParams, &out.CustomParams
*out = new(CustomDeploymentStrategyParams)
if err := DeepCopy_api_CustomDeploymentStrategyParams(*in, *out, c); err != nil {
return err
}
} else {
out.CustomParams = nil
}
if err := pkg_api.DeepCopy_api_ResourceRequirements(&in.Resources, &out.Resources, c); err != nil {
return err
}
@@ -545,13 +583,6 @@ func DeepCopy_api_RollingDeploymentStrategyParams(in interface{}, out interface{
}
out.MaxUnavailable = in.MaxUnavailable
out.MaxSurge = in.MaxSurge
if in.UpdatePercent != nil {
in, out := &in.UpdatePercent, &out.UpdatePercent
*out = new(int32)
**out = **in
} else {
out.UpdatePercent = nil
}
if in.Pre != nil {
in, out := &in.Pre, &out.Pre
*out = new(LifecycleHook)
-136
View File
@@ -1,136 +0,0 @@
package cmd
import (
"time"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
kutil "k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/wait"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"github.com/openshift/origin/pkg/deploy/util"
)
// NewDeploymentConfigReaper returns a new reaper for deploymentConfigs
func NewDeploymentConfigReaper(oc client.Interface, kc kclient.Interface) kubectl.Reaper {
return &DeploymentConfigReaper{oc: oc, kc: kc, pollInterval: kubectl.Interval, timeout: kubectl.Timeout}
}
// DeploymentConfigReaper implements the Reaper interface for deploymentConfigs
type DeploymentConfigReaper struct {
oc client.Interface
kc kclient.Interface
pollInterval, timeout time.Duration
}
// pause marks the deployment configuration as paused to avoid triggering new
// deployments.
func (reaper *DeploymentConfigReaper) pause(namespace, name string) (*deployapi.DeploymentConfig, error) {
return client.UpdateConfigWithRetries(reaper.oc, namespace, name, func(d *deployapi.DeploymentConfig) {
d.Spec.RevisionHistoryLimit = kutil.Int32Ptr(0)
d.Spec.Replicas = 0
d.Spec.Paused = true
})
}
// Stop scales a replication controller via its deployment configuration down to
// zero replicas, waits for all of them to get deleted and then deletes both the
// replication controller and its deployment configuration.
func (reaper *DeploymentConfigReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error {
// Pause the deployment configuration to prevent the new deployments from
// being triggered.
config, err := reaper.pause(namespace, name)
configNotFound := kerrors.IsNotFound(err)
if err != nil && !configNotFound {
return err
}
var (
isPaused bool
legacy bool
)
// Determine if the deployment config controller noticed the pause.
if !configNotFound {
if err := wait.Poll(1*time.Second, 1*time.Minute, func() (bool, error) {
dc, err := reaper.oc.DeploymentConfigs(namespace).Get(name)
if err != nil {
return false, err
}
isPaused = dc.Spec.Paused
return dc.Status.ObservedGeneration >= config.Generation, nil
}); err != nil {
return err
}
// If we failed to pause the deployment config, it means we are talking to
// old API that does not support pausing. In that case, we delete the
// deployment config to stay backward compatible.
if !isPaused {
if err := reaper.oc.DeploymentConfigs(namespace).Delete(name); err != nil {
return err
}
// Setting this to true avoid deleting the config at the end.
legacy = true
}
}
// Clean up deployments related to the config. Even if the deployment
// configuration has been deleted, we want to sweep the existing replication
// controllers and clean them up.
options := kapi.ListOptions{LabelSelector: util.ConfigSelector(name)}
rcList, err := reaper.kc.ReplicationControllers(namespace).List(options)
if err != nil {
return err
}
rcReaper, err := kubectl.ReaperFor(kapi.Kind("ReplicationController"), reaper.kc)
if err != nil {
return err
}
// If there is neither a config nor any deployments, nor any deployer pods, we can return NotFound.
deployments := rcList.Items
if configNotFound && len(deployments) == 0 {
return kerrors.NewNotFound(kapi.Resource("deploymentconfig"), name)
}
for _, rc := range deployments {
if err = rcReaper.Stop(rc.Namespace, rc.Name, timeout, gracePeriod); err != nil {
// Better not error out here...
glog.Infof("Cannot delete ReplicationController %s/%s for deployment config %s/%s: %v", rc.Namespace, rc.Name, namespace, name, err)
}
// Only remove deployer pods when the deployment was failed. For completed
// deployment the pods should be already deleted.
if !util.IsFailedDeployment(&rc) {
continue
}
// Delete all deployer and hook pods
options = kapi.ListOptions{LabelSelector: util.DeployerPodSelector(rc.Name)}
podList, err := reaper.kc.Pods(rc.Namespace).List(options)
if err != nil {
return err
}
for _, pod := range podList.Items {
err := reaper.kc.Pods(pod.Namespace).Delete(pod.Name, gracePeriod)
if err != nil {
// Better not error out here...
glog.Infof("Cannot delete lifecycle Pod %s/%s for deployment config %s/%s: %v", pod.Namespace, pod.Name, namespace, name, err)
}
}
}
// Nothing to delete or we already deleted the deployment config because we
// failed to pause.
if configNotFound || legacy {
return nil
}
return reaper.oc.DeploymentConfigs(namespace).Delete(name)
}
-3
View File
@@ -1,3 +0,0 @@
// Package cmd contains various interface implementations for command-line tools
// associated with deploymentconfigs.
package cmd
-41
View File
@@ -1,41 +0,0 @@
package cmd
import (
"fmt"
"reflect"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/runtime"
deployapi "github.com/openshift/origin/pkg/deploy/api"
)
var basic = kubectl.BasicReplicationController{}
type BasicDeploymentConfigController struct{}
func (BasicDeploymentConfigController) ParamNames() []kubectl.GeneratorParam {
return basic.ParamNames()
}
func (BasicDeploymentConfigController) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
obj, err := basic.Generate(genericParams)
if err != nil {
return nil, err
}
switch t := obj.(type) {
case *kapi.ReplicationController:
obj = &deployapi.DeploymentConfig{
ObjectMeta: t.ObjectMeta,
Spec: deployapi.DeploymentConfigSpec{
Selector: t.Spec.Selector,
Replicas: t.Spec.Replicas,
Template: t.Spec.Template,
},
}
default:
return nil, fmt.Errorf("unrecognized object type: %v", reflect.TypeOf(t))
}
return obj, nil
}
-99
View File
@@ -1,99 +0,0 @@
package cmd
import (
"bytes"
"fmt"
"sort"
"text/tabwriter"
kapi "k8s.io/kubernetes/pkg/api"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deployutil "github.com/openshift/origin/pkg/deploy/util"
)
func NewDeploymentConfigHistoryViewer(oc client.Interface, kc kclient.Interface) kubectl.HistoryViewer {
return &DeploymentConfigHistoryViewer{dn: oc, rn: kc}
}
// DeploymentConfigHistoryViewer is an implementation of the kubectl HistoryViewer interface
// for deployment configs.
type DeploymentConfigHistoryViewer struct {
rn kclient.ReplicationControllersNamespacer
dn client.DeploymentConfigsNamespacer
}
var _ kubectl.HistoryViewer = &DeploymentConfigHistoryViewer{}
// ViewHistory returns a description of all the history it can find for a deployment config.
func (h *DeploymentConfigHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
opts := kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(name)}
deploymentList, err := h.rn.ReplicationControllers(namespace).List(opts)
if err != nil {
return "", err
}
history := deploymentList.Items
if len(deploymentList.Items) == 0 {
return "No rollout history found.", nil
}
// Print details of a specific revision
if revision > 0 {
var desired *kapi.PodTemplateSpec
// We could use a binary search here but brute-force is always faster to write
for i := range history {
rc := history[i]
if deployutil.DeploymentVersionFor(&rc) == revision {
desired = rc.Spec.Template
break
}
}
if desired == nil {
return "", fmt.Errorf("unable to find the specified revision")
}
buf := bytes.NewBuffer([]byte{})
kubectl.DescribePodTemplate(desired, buf)
return buf.String(), nil
}
sort.Sort(deployutil.ByLatestVersionAsc(history))
return tabbedString(func(out *tabwriter.Writer) error {
fmt.Fprintf(out, "REVISION\tSTATUS\tCAUSE\n")
for i := range history {
rc := history[i]
rev := deployutil.DeploymentVersionFor(&rc)
status := deployutil.DeploymentStatusFor(&rc)
cause := rc.Annotations[deployapi.DeploymentStatusReasonAnnotation]
if len(cause) == 0 {
cause = "<unknown>"
}
fmt.Fprintf(out, "%d\t%s\t%s\n", rev, status, cause)
}
return nil
})
}
// TODO: Re-use from an utility package
func tabbedString(f func(*tabwriter.Writer) error) (string, error) {
out := new(tabwriter.Writer)
buf := &bytes.Buffer{}
out.Init(buf, 0, 8, 1, '\t', 0)
err := f(out)
if err != nil {
return "", err
}
out.Flush()
str := string(buf.String())
return str, nil
}
-56
View File
@@ -1,56 +0,0 @@
package cmd
import (
"fmt"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/runtime"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
)
func NewDeploymentConfigRollbacker(oc client.Interface) kubectl.Rollbacker {
return &DeploymentConfigRollbacker{dn: oc}
}
// DeploymentConfigRollbacker is an implementation of the kubectl Rollbacker interface
// for deployment configs.
type DeploymentConfigRollbacker struct {
dn client.DeploymentConfigsNamespacer
}
var _ kubectl.Rollbacker = &DeploymentConfigRollbacker{}
// Rollback the provided deployment config to a specific revision. If revision is zero, we will
// rollback to the previous deployment.
func (r *DeploymentConfigRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64) (string, error) {
config, ok := obj.(*deployapi.DeploymentConfig)
if !ok {
return "", fmt.Errorf("passed object is not a deployment config: %#v", obj)
}
if config.Spec.Paused {
return "", fmt.Errorf("cannot rollback a paused config; resume it first with 'rollout resume dc/%s' and try again", config.Name)
}
rollback := &deployapi.DeploymentConfigRollback{
Name: config.Name,
UpdatedAnnotations: updatedAnnotations,
Spec: deployapi.DeploymentConfigRollbackSpec{
Revision: toRevision,
IncludeTemplate: true,
},
}
rolledback, err := r.dn.DeploymentConfigs(config.Namespace).Rollback(rollback)
if err != nil {
return "", err
}
_, err = r.dn.DeploymentConfigs(config.Namespace).Update(rolledback)
if err != nil {
return "", err
}
return "rolled back", nil
}
-98
View File
@@ -1,98 +0,0 @@
package cmd
import (
"time"
kapi "k8s.io/kubernetes/pkg/api"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/util/wait"
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/deploy/util"
)
// NewDeploymentConfigScaler returns a new scaler for deploymentConfigs
func NewDeploymentConfigScaler(oc client.Interface, kc kclient.Interface) kubectl.Scaler {
return &DeploymentConfigScaler{rcClient: kc, dcClient: oc, clientInterface: kc}
}
// DeploymentConfigScaler is a wrapper for the kubectl Scaler client
type DeploymentConfigScaler struct {
rcClient kclient.ReplicationControllersNamespacer
dcClient client.DeploymentConfigsNamespacer
clientInterface kclient.Interface
}
// Scale updates the DeploymentConfig with the provided namespace/name, to a
// new size, with optional precondition check (if preconditions is not nil),
// optional retries (if retry is not nil), and then optionally waits for its
// deployment replica count to reach the new value (if wait is not nil).
func (scaler *DeploymentConfigScaler) Scale(namespace, name string, newSize uint, preconditions *kubectl.ScalePrecondition, retry, waitForReplicas *kubectl.RetryParams) error {
if preconditions == nil {
preconditions = &kubectl.ScalePrecondition{Size: -1, ResourceVersion: ""}
}
if retry == nil {
// Make it try only once, immediately
retry = &kubectl.RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := kubectl.ScaleCondition(scaler, preconditions, namespace, name, newSize, nil)
if err := wait.Poll(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
// TODO: convert to a watch and use resource version from the ScaleCondition - kubernetes/kubernetes#31051
if waitForReplicas != nil {
dc, err := scaler.dcClient.DeploymentConfigs(namespace).Get(name)
if err != nil {
return err
}
rc, err := scaler.rcClient.ReplicationControllers(namespace).Get(util.LatestDeploymentNameForConfig(dc))
if err != nil {
return err
}
return wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout, controllerHasSpecifiedReplicas(scaler.clientInterface, rc, dc.Spec.Replicas))
}
return nil
}
// ScaleSimple does a simple one-shot attempt at scaling - not useful on its
// own, but a necessary building block for Scale.
func (scaler *DeploymentConfigScaler) ScaleSimple(namespace, name string, preconditions *kubectl.ScalePrecondition, newSize uint) (string, error) {
scale, err := scaler.dcClient.DeploymentConfigs(namespace).GetScale(name)
if err != nil {
return "", err
}
scale.Spec.Replicas = int32(newSize)
updated, err := scaler.dcClient.DeploymentConfigs(namespace).UpdateScale(scale)
if err != nil {
return "", kubectl.ScaleError{FailureType: kubectl.ScaleUpdateFailure, ResourceVersion: "Unknown", ActualError: err}
}
return updated.ResourceVersion, nil
}
// controllerHasSpecifiedReplicas returns a condition that will be true if and
// only if the specified replica count for a controller's ReplicaSelector
// equals the Replicas count.
//
// This is a slightly modified version of
// unversioned.ControllerHasDesiredReplicas. This is necessary because when
// scaling an RC via a DC, the RC spec replica count is not immediately
// updated to match the owning DC.
func controllerHasSpecifiedReplicas(c kclient.Interface, controller *kapi.ReplicationController, specifiedReplicas int32) wait.ConditionFunc {
// If we're given a controller where the status lags the spec, it either means that the controller is stale,
// or that the rc manager hasn't noticed the update yet. Polling status.Replicas is not safe in the latter case.
desiredGeneration := controller.Generation
return func() (bool, error) {
ctrl, err := c.ReplicationControllers(controller.Namespace).Get(controller.Name)
if err != nil {
return false, err
}
// There's a chance a concurrent update modifies the Spec.Replicas causing this check to pass,
// or, after this check has passed, a modification causes the rc manager to create more pods.
// This will not be an issue once we've implemented graceful delete for rcs, but till then
// concurrent stop operations on the same rc might have unintended side effects.
return ctrl.Status.ObservedGeneration >= desiredGeneration && ctrl.Status.Replicas == specifiedReplicas, nil
}
}
-126
View File
@@ -1,126 +0,0 @@
package analysis
import (
"fmt"
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildedges "github.com/openshift/origin/pkg/build/graph"
buildutil "github.com/openshift/origin/pkg/build/util"
deployedges "github.com/openshift/origin/pkg/deploy/graph"
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
imageedges "github.com/openshift/origin/pkg/image/graph"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)
const (
MissingImageStreamErr = "MissingImageStream"
MissingImageStreamTagWarning = "MissingImageStreamTag"
MissingReadinessProbeWarning = "MissingReadinessProbe"
)
// FindDeploymentConfigTriggerErrors checks for possible failures in deployment config
// image change triggers.
//
// Precedence of failures:
// 1. The image stream for the tag of interest does not exist.
// 2. The image stream tag does not exist.
func FindDeploymentConfigTriggerErrors(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
markers := []osgraph.Marker{}
for _, uncastDcNode := range g.NodesByKind(deploygraph.DeploymentConfigNodeKind) {
dcNode := uncastDcNode.(*deploygraph.DeploymentConfigNode)
marker := ictMarker(g, f, dcNode)
if marker != nil {
markers = append(markers, *marker)
}
}
return markers
}
// ictMarker inspects the image change triggers for the provided deploymentconfig and returns
// a marker in case of the following two scenarios:
//
// 1. The image stream pointed by the dc trigger doen not exist.
// 2. The image stream tag pointed by the dc trigger does not exist and there is no build in
// flight that could push to the tag.
func ictMarker(g osgraph.Graph, f osgraph.Namer, dcNode *deploygraph.DeploymentConfigNode) *osgraph.Marker {
for _, uncastIstNode := range g.PredecessorNodesByEdgeKind(dcNode, deployedges.TriggersDeploymentEdgeKind) {
if istNode := uncastIstNode.(*imagegraph.ImageStreamTagNode); !istNode.Found() {
// The image stream for the tag of interest does not exist.
if isNode, exists := doesImageStreamExist(g, uncastIstNode); !exists {
return &osgraph.Marker{
Node: dcNode,
RelatedNodes: []graph.Node{uncastIstNode, isNode},
Severity: osgraph.ErrorSeverity,
Key: MissingImageStreamErr,
Message: fmt.Sprintf("The image trigger for %s will have no effect because %s does not exist.",
f.ResourceName(dcNode), f.ResourceName(isNode)),
// TODO: Suggest `oc create imagestream` once we have that.
}
}
for _, bcNode := range buildedges.BuildConfigsForTag(g, istNode) {
// Avoid warning for the dc image trigger in case there is a build in flight.
if latestBuild := buildedges.GetLatestBuild(g, bcNode); latestBuild != nil && !buildutil.IsBuildComplete(latestBuild.Build) {
return nil
}
}
// The image stream tag of interest does not exist.
return &osgraph.Marker{
Node: dcNode,
RelatedNodes: []graph.Node{uncastIstNode},
Severity: osgraph.WarningSeverity,
Key: MissingImageStreamTagWarning,
Message: fmt.Sprintf("The image trigger for %s will have no effect until %s is imported or created by a build.",
f.ResourceName(dcNode), f.ResourceName(istNode)),
}
}
}
return nil
}
func doesImageStreamExist(g osgraph.Graph, istag graph.Node) (graph.Node, bool) {
for _, imagestream := range g.SuccessorNodesByEdgeKind(istag, imageedges.ReferencedImageStreamGraphEdgeKind) {
return imagestream, imagestream.(*imagegraph.ImageStreamNode).Found()
}
for _, imagestream := range g.SuccessorNodesByEdgeKind(istag, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
return imagestream, imagestream.(*imagegraph.ImageStreamNode).Found()
}
return nil, false
}
// FindDeploymentConfigReadinessWarnings inspects deploymentconfigs and reports those that
// don't have readiness probes set up.
func FindDeploymentConfigReadinessWarnings(g osgraph.Graph, f osgraph.Namer, setProbeCommand string) []osgraph.Marker {
markers := []osgraph.Marker{}
Node:
for _, uncastDcNode := range g.NodesByKind(deploygraph.DeploymentConfigNodeKind) {
dcNode := uncastDcNode.(*deploygraph.DeploymentConfigNode)
if t := dcNode.DeploymentConfig.Spec.Template; t != nil && len(t.Spec.Containers) > 0 {
for _, container := range t.Spec.Containers {
if container.ReadinessProbe != nil {
continue Node
}
}
// All of the containers in the deployment config lack a readiness probe
markers = append(markers, osgraph.Marker{
Node: uncastDcNode,
Severity: osgraph.WarningSeverity,
Key: MissingReadinessProbeWarning,
Message: fmt.Sprintf("%s has no readiness probe to verify pods are ready to accept traffic or ensure deployment is successful.",
f.ResourceName(dcNode)),
Suggestion: osgraph.Suggestion(fmt.Sprintf("%s %s --readiness ...", setProbeCommand, f.ResourceName(dcNode))),
})
continue Node
}
}
return markers
}
-3
View File
@@ -1,3 +0,0 @@
// Package analysis provides functions that analyse deployment configurations and setup markers
// that will be reported by oc status
package analysis
-85
View File
@@ -1,85 +0,0 @@
package graph
import (
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
imageapi "github.com/openshift/origin/pkg/image/api"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)
const (
// TriggersDeploymentEdgeKind points from DeploymentConfigs to ImageStreamTags that trigger the deployment
TriggersDeploymentEdgeKind = "TriggersDeployment"
// UsedInDeploymentEdgeKind points from DeploymentConfigs to DockerImageReferences that are used in the deployment
UsedInDeploymentEdgeKind = "UsedInDeployment"
// DeploymentEdgeKind points from DeploymentConfigs to the ReplicationControllers that are fulfilling the deployment
DeploymentEdgeKind = "Deployment"
)
// AddTriggerEdges creates edges that point to named Docker image repositories for each image used in the deployment.
func AddTriggerEdges(g osgraph.MutableUniqueGraph, node *deploygraph.DeploymentConfigNode) *deploygraph.DeploymentConfigNode {
podTemplate := node.DeploymentConfig.Spec.Template
if podTemplate == nil {
return node
}
deployapi.EachTemplateImage(
&podTemplate.Spec,
deployapi.DeploymentConfigHasTrigger(node.DeploymentConfig),
func(image deployapi.TemplateImage, err error) {
if err != nil {
return
}
if image.From != nil {
if len(image.From.Name) == 0 {
return
}
name, tag, _ := imageapi.SplitImageStreamTag(image.From.Name)
in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(image.From.Namespace, name, tag))
g.AddEdge(in, node, TriggersDeploymentEdgeKind)
return
}
tag := image.Ref.Tag
image.Ref.Tag = ""
in := imagegraph.EnsureDockerRepositoryNode(g, image.Ref.String(), tag)
g.AddEdge(in, node, UsedInDeploymentEdgeKind)
})
return node
}
func AddAllTriggerEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if dcNode, ok := node.(*deploygraph.DeploymentConfigNode); ok {
AddTriggerEdges(g, dcNode)
}
}
}
func AddDeploymentEdges(g osgraph.MutableUniqueGraph, node *deploygraph.DeploymentConfigNode) *deploygraph.DeploymentConfigNode {
for _, n := range g.(graph.Graph).Nodes() {
if rcNode, ok := n.(*kubegraph.ReplicationControllerNode); ok {
if rcNode.ReplicationController.Namespace != node.DeploymentConfig.Namespace {
continue
}
if BelongsToDeploymentConfig(node.DeploymentConfig, rcNode.ReplicationController) {
g.AddEdge(node, rcNode, DeploymentEdgeKind)
}
}
}
return node
}
func AddAllDeploymentEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if dcNode, ok := node.(*deploygraph.DeploymentConfigNode); ok {
AddDeploymentEdges(g, dcNode)
}
}
}
-49
View File
@@ -1,49 +0,0 @@
package graph
import (
"sort"
kapi "k8s.io/kubernetes/pkg/api"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
deployutil "github.com/openshift/origin/pkg/deploy/util"
)
// RelevantDeployments returns the active deployment and a list of inactive deployments (in order from newest to oldest)
func RelevantDeployments(g osgraph.Graph, dcNode *deploygraph.DeploymentConfigNode) (*kubegraph.ReplicationControllerNode, []*kubegraph.ReplicationControllerNode) {
allDeployments := []*kubegraph.ReplicationControllerNode{}
uncastDeployments := g.SuccessorNodesByEdgeKind(dcNode, DeploymentEdgeKind)
if len(uncastDeployments) == 0 {
return nil, []*kubegraph.ReplicationControllerNode{}
}
for i := range uncastDeployments {
allDeployments = append(allDeployments, uncastDeployments[i].(*kubegraph.ReplicationControllerNode))
}
sort.Sort(RecentDeploymentReferences(allDeployments))
if dcNode.DeploymentConfig.Status.LatestVersion == deployutil.DeploymentVersionFor(allDeployments[0].ReplicationController) {
return allDeployments[0], allDeployments[1:]
}
return nil, allDeployments
}
func BelongsToDeploymentConfig(config *deployapi.DeploymentConfig, b *kapi.ReplicationController) bool {
if b.Annotations != nil {
return config.Name == deployutil.DeploymentConfigNameFor(b)
}
return false
}
type RecentDeploymentReferences []*kubegraph.ReplicationControllerNode
func (m RecentDeploymentReferences) Len() int { return len(m) }
func (m RecentDeploymentReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m RecentDeploymentReferences) Less(i, j int) bool {
return deployutil.DeploymentVersionFor(m[i].ReplicationController) > deployutil.DeploymentVersionFor(m[j].ReplicationController)
}
-38
View File
@@ -1,38 +0,0 @@
package nodes
import (
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes"
depoyapi "github.com/openshift/origin/pkg/deploy/api"
)
// EnsureDeploymentConfigNode adds the provided deployment config to the graph if it does not exist
func EnsureDeploymentConfigNode(g osgraph.MutableUniqueGraph, dc *depoyapi.DeploymentConfig) *DeploymentConfigNode {
dcName := DeploymentConfigNodeName(dc)
dcNode := osgraph.EnsureUnique(
g,
dcName,
func(node osgraph.Node) graph.Node {
return &DeploymentConfigNode{Node: node, DeploymentConfig: dc, IsFound: true}
},
).(*DeploymentConfigNode)
if dc.Spec.Template != nil {
podTemplateSpecNode := kubegraph.EnsurePodTemplateSpecNode(g, dc.Spec.Template, dc.Namespace, dcName)
g.AddEdge(dcNode, podTemplateSpecNode, osgraph.ContainsEdgeKind)
}
return dcNode
}
func FindOrCreateSyntheticDeploymentConfigNode(g osgraph.MutableUniqueGraph, dc *depoyapi.DeploymentConfig) *DeploymentConfigNode {
return osgraph.EnsureUnique(
g,
DeploymentConfigNodeName(dc),
func(node osgraph.Node) graph.Node {
return &DeploymentConfigNode{Node: node, DeploymentConfig: dc, IsFound: false}
},
).(*DeploymentConfigNode)
}
-39
View File
@@ -1,39 +0,0 @@
package nodes
import (
"reflect"
osgraph "github.com/openshift/origin/pkg/api/graph"
deployapi "github.com/openshift/origin/pkg/deploy/api"
)
var (
DeploymentConfigNodeKind = reflect.TypeOf(deployapi.DeploymentConfig{}).Name()
)
func DeploymentConfigNodeName(o *deployapi.DeploymentConfig) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(DeploymentConfigNodeKind, o)
}
type DeploymentConfigNode struct {
osgraph.Node
DeploymentConfig *deployapi.DeploymentConfig
IsFound bool
}
func (n DeploymentConfigNode) Found() bool {
return n.IsFound
}
func (n DeploymentConfigNode) Object() interface{} {
return n.DeploymentConfig
}
func (n DeploymentConfigNode) String() string {
return string(DeploymentConfigNodeName(n.DeploymentConfig))
}
func (*DeploymentConfigNode) Kind() string {
return DeploymentConfigNodeKind
}
-496
View File
@@ -1,496 +0,0 @@
package util
import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"
"k8s.io/kubernetes/pkg/api"
kdeplutil "k8s.io/kubernetes/pkg/controller/deployment/util"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"github.com/openshift/origin/pkg/util/namer"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
)
// LatestDeploymentNameForConfig returns a stable identifier for config based on its version.
func LatestDeploymentNameForConfig(config *deployapi.DeploymentConfig) string {
return fmt.Sprintf("%s-%d", config.Name, config.Status.LatestVersion)
}
// LatestDeploymentInfo returns info about the latest deployment for a config,
// or nil if there is no latest deployment. The latest deployment is not
// always the same as the active deployment.
func LatestDeploymentInfo(config *deployapi.DeploymentConfig, deployments []api.ReplicationController) (bool, *api.ReplicationController) {
if config.Status.LatestVersion == 0 || len(deployments) == 0 {
return false, nil
}
sort.Sort(ByLatestVersionDesc(deployments))
candidate := &deployments[0]
return DeploymentVersionFor(candidate) == config.Status.LatestVersion, candidate
}
// ActiveDeployment returns the latest complete deployment, or nil if there is
// no such deployment. The active deployment is not always the same as the
// latest deployment.
func ActiveDeployment(config *deployapi.DeploymentConfig, input []api.ReplicationController) *api.ReplicationController {
var activeDeployment *api.ReplicationController
var lastCompleteDeploymentVersion int64 = 0
for i := range input {
deployment := &input[i]
deploymentVersion := DeploymentVersionFor(deployment)
if DeploymentStatusFor(deployment) == deployapi.DeploymentStatusComplete && deploymentVersion > lastCompleteDeploymentVersion {
activeDeployment = deployment
lastCompleteDeploymentVersion = deploymentVersion
}
}
return activeDeployment
}
// DeployerPodSuffix is the suffix added to pods created from a deployment
const DeployerPodSuffix = "deploy"
// DeployerPodNameForDeployment returns the name of a pod for a given deployment
func DeployerPodNameForDeployment(deployment string) string {
return namer.GetPodName(deployment, DeployerPodSuffix)
}
// LabelForDeployment builds a string identifier for a Deployment.
func LabelForDeployment(deployment *api.ReplicationController) string {
return fmt.Sprintf("%s/%s", deployment.Namespace, deployment.Name)
}
// LabelForDeploymentConfig builds a string identifier for a DeploymentConfig.
func LabelForDeploymentConfig(config *deployapi.DeploymentConfig) string {
return fmt.Sprintf("%s/%s", config.Namespace, config.Name)
}
// DeploymentNameForConfigVersion returns the name of the version-th deployment
// for the config that has the provided name
func DeploymentNameForConfigVersion(name string, version int64) string {
return fmt.Sprintf("%s-%d", name, version)
}
// ConfigSelector returns a label Selector which can be used to find all
// deployments for a DeploymentConfig.
//
// TODO: Using the annotation constant for now since the value is correct
// but we could consider adding a new constant to the public types.
func ConfigSelector(name string) labels.Selector {
return labels.Set{deployapi.DeploymentConfigAnnotation: name}.AsSelector()
}
// DeployerPodSelector returns a label Selector which can be used to find all
// deployer pods associated with a deployment with name.
func DeployerPodSelector(name string) labels.Selector {
return labels.Set{deployapi.DeployerPodForDeploymentLabel: name}.AsSelector()
}
// AnyDeployerPodSelector returns a label Selector which can be used to find
// all deployer pods across all deployments, including hook and custom
// deployer pods.
func AnyDeployerPodSelector() labels.Selector {
sel, _ := labels.Parse(deployapi.DeployerPodForDeploymentLabel)
return sel
}
// HasChangeTrigger returns whether the provided deployment configuration has
// a config change trigger or not
func HasChangeTrigger(config *deployapi.DeploymentConfig) bool {
for _, trigger := range config.Spec.Triggers {
if trigger.Type == deployapi.DeploymentTriggerOnConfigChange {
return true
}
}
return false
}
func DeploymentConfigDeepCopy(dc *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
objCopy, err := api.Scheme.DeepCopy(dc)
if err != nil {
return nil, err
}
copied, ok := objCopy.(*deployapi.DeploymentConfig)
if !ok {
return nil, fmt.Errorf("expected DeploymentConfig, got %#v", objCopy)
}
return copied, nil
}
func DeploymentDeepCopy(rc *api.ReplicationController) (*api.ReplicationController, error) {
objCopy, err := api.Scheme.DeepCopy(rc)
if err != nil {
return nil, err
}
copied, ok := objCopy.(*api.ReplicationController)
if !ok {
return nil, fmt.Errorf("expected ReplicationController, got %#v", objCopy)
}
return copied, nil
}
// DecodeDeploymentConfig decodes a DeploymentConfig from controller using codec. An error is returned
// if the controller doesn't contain an encoded config.
func DecodeDeploymentConfig(controller *api.ReplicationController, decoder runtime.Decoder) (*deployapi.DeploymentConfig, error) {
encodedConfig := []byte(EncodedDeploymentConfigFor(controller))
decoded, err := runtime.Decode(decoder, encodedConfig)
if err != nil {
return nil, fmt.Errorf("failed to decode DeploymentConfig from controller: %v", err)
}
config, ok := decoded.(*deployapi.DeploymentConfig)
if !ok {
return nil, fmt.Errorf("decoded object from controller is not a DeploymentConfig")
}
return config, nil
}
// EncodeDeploymentConfig encodes config as a string using codec.
func EncodeDeploymentConfig(config *deployapi.DeploymentConfig, codec runtime.Codec) (string, error) {
bytes, err := runtime.Encode(codec, config)
if err != nil {
return "", err
}
return string(bytes[:]), nil
}
// MakeDeployment creates a deployment represented as a ReplicationController and based on the given
// DeploymentConfig. The controller replica count will be zero.
func MakeDeployment(config *deployapi.DeploymentConfig, codec runtime.Codec) (*api.ReplicationController, error) {
var err error
var encodedConfig string
if encodedConfig, err = EncodeDeploymentConfig(config, codec); err != nil {
return nil, err
}
deploymentName := LatestDeploymentNameForConfig(config)
podSpec := api.PodSpec{}
if err := api.Scheme.Convert(&config.Spec.Template.Spec, &podSpec, nil); err != nil {
return nil, fmt.Errorf("couldn't clone podSpec: %v", err)
}
controllerLabels := make(labels.Set)
for k, v := range config.Labels {
controllerLabels[k] = v
}
// Correlate the deployment with the config.
// TODO: Using the annotation constant for now since the value is correct
// but we could consider adding a new constant to the public types.
controllerLabels[deployapi.DeploymentConfigAnnotation] = config.Name
// Ensure that pods created by this deployment controller can be safely associated back
// to the controller, and that multiple deployment controllers for the same config don't
// manipulate each others' pods.
selector := map[string]string{}
for k, v := range config.Spec.Selector {
selector[k] = v
}
selector[deployapi.DeploymentConfigLabel] = config.Name
selector[deployapi.DeploymentLabel] = deploymentName
podLabels := make(labels.Set)
for k, v := range config.Spec.Template.Labels {
podLabels[k] = v
}
podLabels[deployapi.DeploymentConfigLabel] = config.Name
podLabels[deployapi.DeploymentLabel] = deploymentName
podAnnotations := make(labels.Set)
for k, v := range config.Spec.Template.Annotations {
podAnnotations[k] = v
}
podAnnotations[deployapi.DeploymentAnnotation] = deploymentName
podAnnotations[deployapi.DeploymentConfigAnnotation] = config.Name
podAnnotations[deployapi.DeploymentVersionAnnotation] = strconv.FormatInt(config.Status.LatestVersion, 10)
deployment := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: deploymentName,
Namespace: config.Namespace,
Annotations: map[string]string{
deployapi.DeploymentConfigAnnotation: config.Name,
deployapi.DeploymentStatusAnnotation: string(deployapi.DeploymentStatusNew),
deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
deployapi.DeploymentVersionAnnotation: strconv.FormatInt(config.Status.LatestVersion, 10),
// This is the target replica count for the new deployment.
deployapi.DesiredReplicasAnnotation: strconv.Itoa(int(config.Spec.Replicas)),
deployapi.DeploymentReplicasAnnotation: strconv.Itoa(0),
},
Labels: controllerLabels,
},
Spec: api.ReplicationControllerSpec{
// The deployment should be inactive initially
Replicas: 0,
Selector: selector,
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: podLabels,
Annotations: podAnnotations,
},
Spec: podSpec,
},
},
}
if config.Status.Details != nil && len(config.Status.Details.Message) > 0 {
deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = config.Status.Details.Message
}
if value, ok := config.Annotations[deployapi.DeploymentIgnorePodAnnotation]; ok {
deployment.Annotations[deployapi.DeploymentIgnorePodAnnotation] = value
}
return deployment, nil
}
// GetReplicaCountForDeployments returns the sum of all replicas for the
// given deployments.
func GetReplicaCountForDeployments(deployments []api.ReplicationController) int32 {
totalReplicaCount := int32(0)
for _, deployment := range deployments {
totalReplicaCount += deployment.Spec.Replicas
}
return totalReplicaCount
}
// GetStatusReplicaCountForDeployments returns the sum of the replicas reported in the
// status of the given deployments.
func GetStatusReplicaCountForDeployments(deployments []api.ReplicationController) int32 {
totalReplicaCount := int32(0)
for _, deployment := range deployments {
totalReplicaCount += deployment.Status.Replicas
}
return totalReplicaCount
}
// GetAvailablePods returns all the available pods from the provided pod list.
func GetAvailablePods(pods []*api.Pod, minReadySeconds int32) int32 {
available := int32(0)
for i := range pods {
pod := pods[i]
if kdeplutil.IsPodAvailable(pod, minReadySeconds, time.Now()) {
available++
}
}
return available
}
func DeploymentConfigNameFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentConfigAnnotation)
}
func DeploymentNameFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentAnnotation)
}
func DeployerPodNameFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentPodAnnotation)
}
func DeploymentStatusFor(obj runtime.Object) deployapi.DeploymentStatus {
return deployapi.DeploymentStatus(annotationFor(obj, deployapi.DeploymentStatusAnnotation))
}
func DeploymentStatusReasonFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentStatusReasonAnnotation)
}
func DeploymentDesiredReplicas(obj runtime.Object) (int32, bool) {
return int32AnnotationFor(obj, deployapi.DesiredReplicasAnnotation)
}
func DeploymentReplicas(obj runtime.Object) (int32, bool) {
return int32AnnotationFor(obj, deployapi.DeploymentReplicasAnnotation)
}
func EncodedDeploymentConfigFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentEncodedConfigAnnotation)
}
func DeploymentVersionFor(obj runtime.Object) int64 {
v, err := strconv.ParseInt(annotationFor(obj, deployapi.DeploymentVersionAnnotation), 10, 64)
if err != nil {
return -1
}
return v
}
func IsDeploymentCancelled(deployment *api.ReplicationController) bool {
value := annotationFor(deployment, deployapi.DeploymentCancelledAnnotation)
return strings.EqualFold(value, deployapi.DeploymentCancelledAnnotationValue)
}
func HasSynced(dc *deployapi.DeploymentConfig) bool {
return dc.Status.ObservedGeneration >= dc.Generation
}
// IsOwnedByConfig checks whether the provided replication controller is part of a
// deployment configuration.
// TODO: Switch to use owner references once we got those working.
func IsOwnedByConfig(deployment *api.ReplicationController) bool {
_, ok := deployment.Annotations[deployapi.DeploymentConfigAnnotation]
return ok
}
// IsTerminatedDeployment returns true if the passed deployment has terminated (either
// complete or failed).
func IsTerminatedDeployment(deployment *api.ReplicationController) bool {
current := DeploymentStatusFor(deployment)
return current == deployapi.DeploymentStatusComplete || current == deployapi.DeploymentStatusFailed
}
// IsFailedDeployment returns true if the passed deployment failed.
func IsFailedDeployment(deployment *api.ReplicationController) bool {
current := DeploymentStatusFor(deployment)
return current == deployapi.DeploymentStatusFailed
}
// CanTransitionPhase returns whether it is allowed to go from the current to the next phase.
func CanTransitionPhase(current, next deployapi.DeploymentStatus) bool {
switch current {
case deployapi.DeploymentStatusNew:
switch next {
case deployapi.DeploymentStatusPending,
deployapi.DeploymentStatusRunning,
deployapi.DeploymentStatusFailed,
deployapi.DeploymentStatusComplete:
return true
}
case deployapi.DeploymentStatusPending:
switch next {
case deployapi.DeploymentStatusRunning,
deployapi.DeploymentStatusFailed,
deployapi.DeploymentStatusComplete:
return true
}
case deployapi.DeploymentStatusRunning:
switch next {
case deployapi.DeploymentStatusFailed, deployapi.DeploymentStatusComplete:
return true
}
}
return false
}
// annotationFor returns the annotation with key for obj.
func annotationFor(obj runtime.Object, key string) string {
meta, err := api.ObjectMetaFor(obj)
if err != nil {
return ""
}
return meta.Annotations[key]
}
func int32AnnotationFor(obj runtime.Object, key string) (int32, bool) {
s := annotationFor(obj, key)
if len(s) == 0 {
return 0, false
}
i, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0, false
}
return int32(i), true
}
// DeploymentsForCleanup determines which deployments for a configuration are relevant for the
// revision history limit quota
func DeploymentsForCleanup(configuration *deployapi.DeploymentConfig, deployments []api.ReplicationController) []api.ReplicationController {
// if the past deployment quota has been exceeded, we need to prune the oldest deployments
// until we are not exceeding the quota any longer, so we sort oldest first
sort.Sort(ByLatestVersionAsc(deployments))
relevantDeployments := []api.ReplicationController{}
activeDeployment := ActiveDeployment(configuration, deployments)
if activeDeployment == nil {
// if cleanup policy is set but no successful deployments have happened, there will be
// no active deployment. We can consider all of the deployments in this case except for
// the latest one
for i := range deployments {
deployment := &deployments[i]
if DeploymentVersionFor(deployment) != configuration.Status.LatestVersion {
relevantDeployments = append(relevantDeployments, *deployment)
}
}
} else {
// if there is an active deployment, we need to filter out any deployments that we don't
// care about, namely the active deployment and any newer deployments
for i := range deployments {
deployment := &deployments[i]
if deployment != activeDeployment && DeploymentVersionFor(deployment) < DeploymentVersionFor(activeDeployment) {
relevantDeployments = append(relevantDeployments, *deployment)
}
}
}
return relevantDeployments
}
// WaitForRunningDeployerPod waits a given period of time until the deployer pod
// for given replication controller is not running.
func WaitForRunningDeployerPod(podClient kclient.PodsNamespacer, rc *api.ReplicationController, timeout time.Duration) error {
podName := DeployerPodNameForDeployment(rc.Name)
canGetLogs := func(p *api.Pod) bool {
return api.PodSucceeded == p.Status.Phase || api.PodFailed == p.Status.Phase || api.PodRunning == p.Status.Phase
}
pod, err := podClient.Pods(rc.Namespace).Get(podName)
if err == nil && canGetLogs(pod) {
return nil
}
watcher, err := podClient.Pods(rc.Namespace).Watch(
api.ListOptions{
FieldSelector: fields.Set{"metadata.name": podName}.AsSelector(),
},
)
if err != nil {
return err
}
defer watcher.Stop()
if _, err := watch.Until(timeout, watcher, func(e watch.Event) (bool, error) {
if e.Type == watch.Error {
return false, fmt.Errorf("encountered error while watching for pod: %v", e.Object)
}
obj, isPod := e.Object.(*api.Pod)
if !isPod {
return false, errors.New("received unknown object while watching for pods")
}
return canGetLogs(obj), nil
}); err != nil {
return err
}
return nil
}
// ByLatestVersionAsc sorts deployments by LatestVersion ascending.
type ByLatestVersionAsc []api.ReplicationController
func (d ByLatestVersionAsc) Len() int { return len(d) }
func (d ByLatestVersionAsc) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d ByLatestVersionAsc) Less(i, j int) bool {
return DeploymentVersionFor(&d[i]) < DeploymentVersionFor(&d[j])
}
// ByLatestVersionDesc sorts deployments by LatestVersion descending.
type ByLatestVersionDesc []api.ReplicationController
func (d ByLatestVersionDesc) Len() int { return len(d) }
func (d ByLatestVersionDesc) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d ByLatestVersionDesc) Less(i, j int) bool {
return DeploymentVersionFor(&d[j]) < DeploymentVersionFor(&d[i])
}
// ByMostRecent sorts deployments by most recently created.
type ByMostRecent []*api.ReplicationController
func (s ByMostRecent) Len() int { return len(s) }
func (s ByMostRecent) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s ByMostRecent) Less(i, j int) bool {
return !s[i].CreationTimestamp.Before(s[j].CreationTimestamp)
}
+11 -79
View File
@@ -18,6 +18,8 @@ import (
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/golang/glog"
"github.com/openshift/origin/pkg/image/reference"
)
const (
@@ -48,24 +50,6 @@ func (fn DefaultRegistryFunc) DefaultRegistry() (string, bool) {
return fn()
}
// parseRepositoryTag splits a string into its name component and either tag or id if present.
// TODO remove
func parseRepositoryTag(repos string) (base string, tag string, id string) {
n := strings.Index(repos, "@")
if n >= 0 {
parts := strings.Split(repos, "@")
return parts[0], "", parts[1]
}
n = strings.LastIndex(repos, ":")
if n < 0 {
return repos, "", ""
}
if tag := repos[n+1:]; !strings.Contains(tag, "/") {
return repos[:n], tag, ""
}
return repos, "", ""
}
// ParseImageStreamImageName splits a string into its name component and ID component, and returns an error
// if the string is not in the right form.
func ParseImageStreamImageName(input string) (name string, id string, err error) {
@@ -109,16 +93,6 @@ func MakeImageStreamImageName(name, id string) string {
return fmt.Sprintf("%s@%s", name, id)
}
func isRegistryName(str string) bool {
switch {
case strings.Contains(str, ":"),
strings.Contains(str, "."),
str == "localhost":
return true
}
return false
}
// IsRegistryDockerHub returns true if the given registry name belongs to
// Docker hub.
func IsRegistryDockerHub(registry string) bool {
@@ -134,60 +108,18 @@ func IsRegistryDockerHub(registry string) bool {
// DockerImageReference.
func ParseDockerImageReference(spec string) (DockerImageReference, error) {
var ref DockerImageReference
// TODO replace with docker version once docker/docker PR11109 is merged upstream
stream, tag, id := parseRepositoryTag(spec)
repoParts := strings.Split(stream, "/")
switch len(repoParts) {
case 2:
if isRegistryName(repoParts[0]) {
// registry/name
ref.Registry = repoParts[0]
if IsRegistryDockerHub(ref.Registry) {
ref.Namespace = DockerDefaultNamespace
}
if len(repoParts[1]) == 0 {
return ref, fmt.Errorf("the docker pull spec %q must be two or three segments separated by slashes", spec)
}
ref.Name = repoParts[1]
ref.Tag = tag
ref.ID = id
break
}
// namespace/name
ref.Namespace = repoParts[0]
if len(repoParts[1]) == 0 {
return ref, fmt.Errorf("the docker pull spec %q must be two or three segments separated by slashes", spec)
}
ref.Name = repoParts[1]
ref.Tag = tag
ref.ID = id
break
case 3:
// registry/namespace/name
ref.Registry = repoParts[0]
ref.Namespace = repoParts[1]
if len(repoParts[2]) == 0 {
return ref, fmt.Errorf("the docker pull spec %q must be two or three segments separated by slashes", spec)
}
ref.Name = repoParts[2]
ref.Tag = tag
ref.ID = id
break
case 1:
// name
if len(repoParts[0]) == 0 {
return ref, fmt.Errorf("the docker pull spec %q must be two or three segments separated by slashes", spec)
}
ref.Name = repoParts[0]
ref.Tag = tag
ref.ID = id
break
default:
// TODO: this is no longer true with V2
return ref, fmt.Errorf("the docker pull spec %q must be two or three segments separated by slashes", spec)
namedRef, err := reference.ParseNamedDockerImageReference(spec)
if err != nil {
return ref, err
}
ref.Registry = namedRef.Registry
ref.Namespace = namedRef.Namespace
ref.Name = namedRef.Name
ref.Tag = namedRef.Tag
ref.ID = namedRef.ID
return ref, nil
}
-435
View File
@@ -1,435 +0,0 @@
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
syntax = 'proto2';
package github.com.openshift.origin.pkg.image.api.v1;
import "k8s.io/kubernetes/pkg/api/unversioned/generated.proto";
import "k8s.io/kubernetes/pkg/api/v1/generated.proto";
import "k8s.io/kubernetes/pkg/runtime/generated.proto";
import "k8s.io/kubernetes/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1";
// DockerImageReference points to a Docker image.
message DockerImageReference {
// Registry is the registry that contains the Docker image
optional string registry = 1;
// Namespace is the namespace that contains the Docker image
optional string namespace = 2;
// Name is the name of the Docker image
optional string name = 3;
// Tag is which tag of the Docker image is being referenced
optional string tag = 4;
// ID is the identifier for the Docker image
optional string iD = 5;
}
// Image is an immutable representation of a Docker image and metadata at a point in time.
message Image {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// DockerImageReference is the string that can be used to pull this image.
optional string dockerImageReference = 2;
// DockerImageMetadata contains metadata about this image
optional k8s.io.kubernetes.pkg.runtime.RawExtension dockerImageMetadata = 3;
// DockerImageMetadataVersion conveys the version of the object, which if empty defaults to "1.0"
optional string dockerImageMetadataVersion = 4;
// DockerImageManifest is the raw JSON of the manifest
optional string dockerImageManifest = 5;
// DockerImageLayers represents the layers in the image. May not be set if the image does not define that data.
repeated ImageLayer dockerImageLayers = 6;
// Signatures holds all signatures of the image.
repeated ImageSignature signatures = 7;
// DockerImageSignatures provides the signatures as opaque blobs. This is a part of manifest schema v1.
repeated bytes dockerImageSignatures = 8;
// DockerImageManifestMediaType specifies the mediaType of manifest. This is a part of manifest schema v2.
optional string dockerImageManifestMediaType = 9;
// DockerImageConfig is a JSON blob that the runtime uses to set up the container. This is a part of manifest schema v2.
optional string dockerImageConfig = 10;
}
// ImageImportSpec describes a request to import a specific image.
message ImageImportSpec {
// From is the source of an image to import; only kind DockerImage is allowed
optional k8s.io.kubernetes.pkg.api.v1.ObjectReference from = 1;
// To is a tag in the current image stream to assign the imported image to, if name is not specified the default tag from from.name will be used
optional k8s.io.kubernetes.pkg.api.v1.LocalObjectReference to = 2;
// ImportPolicy is the policy controlling how the image is imported
optional TagImportPolicy importPolicy = 3;
// IncludeManifest determines if the manifest for each image is returned in the response
optional bool includeManifest = 4;
}
// ImageImportStatus describes the result of an image import.
message ImageImportStatus {
// Status is the status of the image import, including errors encountered while retrieving the image
optional k8s.io.kubernetes.pkg.api.unversioned.Status status = 1;
// Image is the metadata of that image, if the image was located
optional Image image = 2;
// Tag is the tag this image was located under, if any
optional string tag = 3;
}
// ImageLayer represents a single layer of the image. Some images may have multiple layers. Some may have none.
message ImageLayer {
// Name of the layer as defined by the underlying store.
optional string name = 1;
// Size of the layer in bytes as defined by the underlying store.
optional int64 size = 2;
// MediaType of the referenced object.
optional string mediaType = 3;
}
// ImageList is a list of Image objects.
message ImageList {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.unversioned.ListMeta metadata = 1;
// Items is a list of images
repeated Image items = 2;
}
// ImageSignature holds a signature of an image. It allows to verify image identity and possibly other claims
// as long as the signature is trusted. Based on this information it is possible to restrict runnable images
// to those matching cluster-wide policy.
// Mandatory fields should be parsed by clients doing image verification. The others are parsed from
// signature's content by the server. They serve just an informative purpose.
message ImageSignature {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// Required: Describes a type of stored blob.
optional string type = 2;
// Required: An opaque binary string which is an image's signature.
optional bytes content = 3;
// Conditions represent the latest available observations of a signature's current state.
repeated SignatureCondition conditions = 4;
// A human readable string representing image's identity. It could be a product name and version, or an
// image pull spec (e.g. "registry.access.redhat.com/rhel7/rhel:7.2").
optional string imageIdentity = 5;
// Contains claims from the signature.
map<string, string> signedClaims = 6;
// If specified, it is the time of signature's creation.
optional k8s.io.kubernetes.pkg.api.unversioned.Time created = 7;
// If specified, it holds information about an issuer of signing certificate or key (a person or entity
// who signed the signing certificate or key).
optional SignatureIssuer issuedBy = 8;
// If specified, it holds information about a subject of signing certificate or key (a person or entity
// who signed the image).
optional SignatureSubject issuedTo = 9;
}
// ImageStream stores a mapping of tags to images, metadata overrides that are applied
// when images are tagged in a stream, and an optional reference to a Docker image
// repository on a registry.
message ImageStream {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// Spec describes the desired state of this stream
optional ImageStreamSpec spec = 2;
// Status describes the current state of this stream
optional ImageStreamStatus status = 3;
}
// ImageStreamImage represents an Image that is retrieved by image name from an ImageStream.
message ImageStreamImage {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// Image associated with the ImageStream and image name.
optional Image image = 2;
}
// ImageStreamImport imports an image from remote repositories into OpenShift.
message ImageStreamImport {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// Spec is a description of the images that the user wishes to import
optional ImageStreamImportSpec spec = 2;
// Status is the the result of importing the image
optional ImageStreamImportStatus status = 3;
}
// ImageStreamImportSpec defines what images should be imported.
message ImageStreamImportSpec {
// Import indicates whether to perform an import - if so, the specified tags are set on the spec
// and status of the image stream defined by the type meta.
optional bool import = 1;
// Repository is an optional import of an entire Docker image repository. A maximum limit on the
// number of tags imported this way is imposed by the server.
optional RepositoryImportSpec repository = 2;
// Images are a list of individual images to import.
repeated ImageImportSpec images = 3;
}
// ImageStreamImportStatus contains information about the status of an image stream import.
message ImageStreamImportStatus {
// Import is the image stream that was successfully updated or created when 'to' was set.
optional ImageStream import = 1;
// Repository is set if spec.repository was set to the outcome of the import
optional RepositoryImportStatus repository = 2;
// Images is set with the result of importing spec.images
repeated ImageImportStatus images = 3;
}
// ImageStreamList is a list of ImageStream objects.
message ImageStreamList {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.unversioned.ListMeta metadata = 1;
// Items is a list of imageStreams
repeated ImageStream items = 2;
}
// ImageStreamMapping represents a mapping from a single tag to a Docker image as
// well as the reference to the Docker image stream the image came from.
message ImageStreamMapping {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// Image is a Docker image.
optional Image image = 2;
// Tag is a string value this image can be located with inside the stream.
optional string tag = 3;
}
// ImageStreamSpec represents options for ImageStreams.
message ImageStreamSpec {
// DockerImageRepository is optional, if specified this stream is backed by a Docker repository on this server
optional string dockerImageRepository = 1;
// Tags map arbitrary string values to specific image locators
repeated TagReference tags = 2;
}
// ImageStreamStatus contains information about the state of this image stream.
message ImageStreamStatus {
// DockerImageRepository represents the effective location this stream may be accessed at.
// May be empty until the server determines where the repository is located
optional string dockerImageRepository = 1;
// Tags are a historical record of images associated with each tag. The first entry in the
// TagEvent array is the currently tagged image.
repeated NamedTagEventList tags = 2;
}
// ImageStreamTag represents an Image that is retrieved by tag name from an ImageStream.
message ImageStreamTag {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1;
// Tag is the spec tag associated with this image stream tag, and it may be null
// if only pushes have occurred to this image stream.
optional TagReference tag = 2;
// Generation is the current generation of the tagged image - if tag is provided
// and this value is not equal to the tag generation, a user has requested an
// import that has not completed, or Conditions will be filled out indicating any
// error.
optional int64 generation = 3;
// Conditions is an array of conditions that apply to the image stream tag.
repeated TagEventCondition conditions = 4;
// Image associated with the ImageStream and tag.
optional Image image = 5;
}
// ImageStreamTagList is a list of ImageStreamTag objects.
message ImageStreamTagList {
// Standard object's metadata.
optional k8s.io.kubernetes.pkg.api.unversioned.ListMeta metadata = 1;
// Items is the list of image stream tags
repeated ImageStreamTag items = 2;
}
// NamedTagEventList relates a tag to its image history.
message NamedTagEventList {
// Tag is the tag for which the history is recorded
optional string tag = 1;
// Standard object's metadata.
repeated TagEvent items = 2;
// Conditions is an array of conditions that apply to the tag event list.
repeated TagEventCondition conditions = 3;
}
// RepositoryImportSpec describes a request to import images from a Docker image repository.
message RepositoryImportSpec {
// From is the source for the image repository to import; only kind DockerImage and a name of a Docker image repository is allowed
optional k8s.io.kubernetes.pkg.api.v1.ObjectReference from = 1;
// ImportPolicy is the policy controlling how the image is imported
optional TagImportPolicy importPolicy = 2;
// IncludeManifest determines if the manifest for each image is returned in the response
optional bool includeManifest = 3;
}
// RepositoryImportStatus describes the result of an image repository import
message RepositoryImportStatus {
// Status reflects whether any failure occurred during import
optional k8s.io.kubernetes.pkg.api.unversioned.Status status = 1;
// Images is a list of images successfully retrieved by the import of the repository.
repeated ImageImportStatus images = 2;
// AdditionalTags are tags that exist in the repository but were not imported because
// a maximum limit of automatic imports was applied.
repeated string additionalTags = 3;
}
// SignatureCondition describes an image signature condition of particular kind at particular probe time.
message SignatureCondition {
// Type of signature condition, Complete or Failed.
optional string type = 1;
// Status of the condition, one of True, False, Unknown.
optional string status = 2;
// Last time the condition was checked.
optional k8s.io.kubernetes.pkg.api.unversioned.Time lastProbeTime = 3;
// Last time the condition transit from one status to another.
optional k8s.io.kubernetes.pkg.api.unversioned.Time lastTransitionTime = 4;
// (brief) reason for the condition's last transition.
optional string reason = 5;
// Human readable message indicating details about last transition.
optional string message = 6;
}
// SignatureGenericEntity holds a generic information about a person or entity who is an issuer or a subject
// of signing certificate or key.
message SignatureGenericEntity {
// Organization name.
optional string organization = 1;
// Common name (e.g. openshift-signing-service).
optional string commonName = 2;
}
// SignatureIssuer holds information about an issuer of signing certificate or key.
message SignatureIssuer {
optional SignatureGenericEntity signatureGenericEntity = 1;
}
// SignatureSubject holds information about a person or entity who created the signature.
message SignatureSubject {
optional SignatureGenericEntity signatureGenericEntity = 1;
// If present, it is a human readable key id of public key belonging to the subject used to verify image
// signature. It should contain at least 64 lowest bits of public key's fingerprint (e.g.
// 0x685ebe62bf278440).
optional string publicKeyID = 2;
}
// TagEvent is used by ImageStreamStatus to keep a historical record of images associated with a tag.
message TagEvent {
// Created holds the time the TagEvent was created
optional k8s.io.kubernetes.pkg.api.unversioned.Time created = 1;
// DockerImageReference is the string that can be used to pull this image
optional string dockerImageReference = 2;
// Image is the image
optional string image = 3;
// Generation is the spec tag generation that resulted in this tag being updated
optional int64 generation = 4;
}
// TagEventCondition contains condition information for a tag event.
message TagEventCondition {
// Type of tag event condition, currently only ImportSuccess
optional string type = 1;
// Status of the condition, one of True, False, Unknown.
optional string status = 2;
// LastTransitionTIme is the time the condition transitioned from one status to another.
optional k8s.io.kubernetes.pkg.api.unversioned.Time lastTransitionTime = 3;
// Reason is a brief machine readable explanation for the condition's last transition.
optional string reason = 4;
// Message is a human readable description of the details about last transition, complementing reason.
optional string message = 5;
// Generation is the spec tag generation that this status corresponds to
optional int64 generation = 6;
}
// TagImportPolicy describes the tag import policy
message TagImportPolicy {
// Insecure is true if the server may bypass certificate verification or connect directly over HTTP during image import.
optional bool insecure = 1;
// Scheduled indicates to the server that this tag should be periodically checked to ensure it is up to date, and imported
optional bool scheduled = 2;
}
// TagReference specifies optional annotations for images using this tag and an optional reference to an ImageStreamTag, ImageStreamImage, or DockerImage this tag should track.
message TagReference {
// Name of the tag
optional string name = 1;
// Annotations associated with images using this tag
map<string, string> annotations = 2;
// From is a reference to an image stream tag or image stream this tag should track
optional k8s.io.kubernetes.pkg.api.v1.ObjectReference from = 3;
// Reference states if the tag will be imported. Default value is false, which means the tag will be imported.
optional bool reference = 4;
// Generation is the image stream generation that updated this tag - setting it to 0 is an indication that the generation must be updated.
// Legacy clients will send this as nil, which means the client doesn't know or care.
optional int64 generation = 5;
// Import is information that controls how images may be imported by the server.
optional TagImportPolicy importPolicy = 6;
}
+1 -1
View File
@@ -119,7 +119,7 @@ func (ImageStreamImage) SwaggerDoc() map[string]string {
}
var map_ImageStreamImport = map[string]string{
"": "ImageStreamImport imports an image from remote repositories into OpenShift.",
"": "The image stream import resource provides an easy way for a user to find and import Docker images from other Docker registries into the server. Individual images or an entire image repository may be imported, and users may choose to see the results of the import prior to tagging the resulting images into the specified image stream.\n\nThis API is intended for end-user tools that need to see the metadata of the image prior to import (for instance, to generate an application from it). Clients that know the desired image can continue to create spec.tags directly into their image streams.",
"metadata": "Standard object's metadata.",
"spec": "Spec is a description of the images that the user wishes to import",
"status": "Status is the the result of importing the image",
+8 -1
View File
@@ -314,7 +314,14 @@ type DockerImageReference struct {
ID string `protobuf:"bytes,5,opt,name=iD"`
}
// ImageStreamImport imports an image from remote repositories into OpenShift.
// The image stream import resource provides an easy way for a user to find and import Docker images
// from other Docker registries into the server. Individual images or an entire image repository may
// be imported, and users may choose to see the results of the import prior to tagging the resulting
// images into the specified image stream.
//
// This API is intended for end-user tools that need to see the metadata of the image prior to import
// (for instance, to generate an application from it). Clients that know the desired image can continue
// to create spec.tags directly into their image streams.
type ImageStreamImport struct {
unversioned.TypeMeta `json:",inline"`
// Standard object's metadata.
-56
View File
@@ -1,56 +0,0 @@
package graph
import (
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
imageapi "github.com/openshift/origin/pkg/image/api"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)
const (
// ReferencedImageStreamGraphEdgeKind is an edge that goes from an ImageStreamTag node back to an ImageStream
ReferencedImageStreamGraphEdgeKind = "ReferencedImageStreamGraphEdge"
// ReferencedImageStreamImageGraphEdgeKind is an edge that goes from an ImageStreamImage node back to an ImageStream
ReferencedImageStreamImageGraphEdgeKind = "ReferencedImageStreamImageGraphEdgeKind"
)
// AddImageStreamTagRefEdge ensures that a directed edge exists between an IST Node and the IS it references
func AddImageStreamTagRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageStreamTagNode) {
isName, _, _ := imageapi.SplitImageStreamTag(node.Name)
imageStream := &imageapi.ImageStream{}
imageStream.Namespace = node.Namespace
imageStream.Name = isName
imageStreamNode := imagegraph.FindOrCreateSyntheticImageStreamNode(g, imageStream)
g.AddEdge(node, imageStreamNode, ReferencedImageStreamGraphEdgeKind)
}
// AddImageStreamImageRefEdge ensures that a directed edge exists between an ImageStreamImage Node and the IS it references
func AddImageStreamImageRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageStreamImageNode) {
dockImgRef, _ := imageapi.ParseDockerImageReference(node.Name)
imageStream := &imageapi.ImageStream{}
imageStream.Namespace = node.Namespace
imageStream.Name = dockImgRef.Name
imageStreamNode := imagegraph.FindOrCreateSyntheticImageStreamNode(g, imageStream)
g.AddEdge(node, imageStreamNode, ReferencedImageStreamImageGraphEdgeKind)
}
// AddAllImageStreamRefEdges calls AddImageStreamRefEdge for every ImageStreamTagNode in the graph
func AddAllImageStreamRefEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if istNode, ok := node.(*imagegraph.ImageStreamTagNode); ok {
AddImageStreamTagRefEdge(g, istNode)
}
}
}
// AddAllImageStreamImageRefEdges calls AddImageStreamImageRefEdge for every ImageStreamImageNode in the graph
func AddAllImageStreamImageRefEdges(g osgraph.MutableUniqueGraph) {
for _, node := range g.(graph.Graph).Nodes() {
if isimageNode, ok := node.(*imagegraph.ImageStreamImageNode); ok {
AddImageStreamImageRefEdge(g, isimageNode)
}
}
}
-172
View File
@@ -1,172 +0,0 @@
package nodes
import (
"github.com/gonum/graph"
kapi "k8s.io/kubernetes/pkg/api"
osgraph "github.com/openshift/origin/pkg/api/graph"
imageapi "github.com/openshift/origin/pkg/image/api"
)
func EnsureImageNode(g osgraph.MutableUniqueGraph, img *imageapi.Image) graph.Node {
return osgraph.EnsureUnique(g,
ImageNodeName(img),
func(node osgraph.Node) graph.Node {
return &ImageNode{node, img}
},
)
}
// EnsureAllImageStreamTagNodes creates all the ImageStreamTagNodes that are guaranteed to be present based on the ImageStream.
// This is different than inferring the presence of an object, since the IST is an object derived from a join between the ImageStream
// and the Image it references.
func EnsureAllImageStreamTagNodes(g osgraph.MutableUniqueGraph, is *imageapi.ImageStream) []*ImageStreamTagNode {
ret := []*ImageStreamTagNode{}
for tag := range is.Status.Tags {
ist := &imageapi.ImageStreamTag{}
ist.Namespace = is.Namespace
ist.Name = imageapi.JoinImageStreamTag(is.Name, tag)
istNode := EnsureImageStreamTagNode(g, ist)
ret = append(ret, istNode)
}
return ret
}
func FindImage(g osgraph.MutableUniqueGraph, imageName string) graph.Node {
return g.Find(ImageNodeName(&imageapi.Image{ObjectMeta: kapi.ObjectMeta{Name: imageName}}))
}
// EnsureDockerRepositoryNode adds the named Docker repository tag reference to the graph if it does
// not already exist. If the reference is invalid, the Name field of the graph will be used directly.
func EnsureDockerRepositoryNode(g osgraph.MutableUniqueGraph, name, tag string) graph.Node {
ref, err := imageapi.ParseDockerImageReference(name)
if err == nil {
if len(tag) != 0 {
ref.Tag = tag
}
ref = ref.DockerClientDefaults()
} else {
ref = imageapi.DockerImageReference{Name: name}
}
return osgraph.EnsureUnique(g,
DockerImageRepositoryNodeName(ref),
func(node osgraph.Node) graph.Node {
return &DockerImageRepositoryNode{node, ref}
},
)
}
// MakeImageStreamTagObjectMeta returns an ImageStreamTag that has enough information to join the graph, but it is not
// based on a full IST object. This can be used to properly initialize the graph without having to retrieve all ISTs
func MakeImageStreamTagObjectMeta(namespace, name, tag string) *imageapi.ImageStreamTag {
return &imageapi.ImageStreamTag{
ObjectMeta: kapi.ObjectMeta{
Namespace: namespace,
Name: imageapi.JoinImageStreamTag(name, tag),
},
}
}
// MakeImageStreamTagObjectMeta2 returns an ImageStreamTag that has enough information to join the graph, but it is not
// based on a full IST object. This can be used to properly initialize the graph without having to retrieve all ISTs
func MakeImageStreamTagObjectMeta2(namespace, name string) *imageapi.ImageStreamTag {
return &imageapi.ImageStreamTag{
ObjectMeta: kapi.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
}
// EnsureImageStreamTagNode adds a graph node for the specific tag in an Image Stream if it does not already exist.
func EnsureImageStreamTagNode(g osgraph.MutableUniqueGraph, ist *imageapi.ImageStreamTag) *ImageStreamTagNode {
return osgraph.EnsureUnique(g,
ImageStreamTagNodeName(ist),
func(node osgraph.Node) graph.Node {
return &ImageStreamTagNode{node, ist, true}
},
).(*ImageStreamTagNode)
}
// FindOrCreateSyntheticImageStreamTagNode returns the existing ISTNode or creates a synthetic node in its place
func FindOrCreateSyntheticImageStreamTagNode(g osgraph.MutableUniqueGraph, ist *imageapi.ImageStreamTag) *ImageStreamTagNode {
return osgraph.EnsureUnique(g,
ImageStreamTagNodeName(ist),
func(node osgraph.Node) graph.Node {
return &ImageStreamTagNode{node, ist, false}
},
).(*ImageStreamTagNode)
}
// MakeImageStreamImageObjectMeta returns an ImageStreamImage that has enough information to join the graph, but it is not
// based on a full ISI object. This can be used to properly initialize the graph without having to retrieve all ISIs
func MakeImageStreamImageObjectMeta(namespace, name string) *imageapi.ImageStreamImage {
return &imageapi.ImageStreamImage{
ObjectMeta: kapi.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
}
// EnsureImageStreamImageNode adds a graph node for the specific ImageStreamImage if it
// does not already exist.
func EnsureImageStreamImageNode(g osgraph.MutableUniqueGraph, namespace, name string) graph.Node {
isi := &imageapi.ImageStreamImage{
ObjectMeta: kapi.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
return osgraph.EnsureUnique(g,
ImageStreamImageNodeName(isi),
func(node osgraph.Node) graph.Node {
return &ImageStreamImageNode{node, isi, true}
},
)
}
// FindOrCreateSyntheticImageStreamImageNode returns the existing ISINode or creates a synthetic node in its place
func FindOrCreateSyntheticImageStreamImageNode(g osgraph.MutableUniqueGraph, isi *imageapi.ImageStreamImage) *ImageStreamImageNode {
return osgraph.EnsureUnique(g,
ImageStreamImageNodeName(isi),
func(node osgraph.Node) graph.Node {
return &ImageStreamImageNode{node, isi, false}
},
).(*ImageStreamImageNode)
}
// EnsureImageStreamNode adds a graph node for the Image Stream if it does not already exist.
func EnsureImageStreamNode(g osgraph.MutableUniqueGraph, is *imageapi.ImageStream) graph.Node {
return osgraph.EnsureUnique(g,
ImageStreamNodeName(is),
func(node osgraph.Node) graph.Node {
return &ImageStreamNode{node, is, true}
},
)
}
// FindOrCreateSyntheticImageStreamNode returns the existing ISNode or creates a synthetic node in its place
func FindOrCreateSyntheticImageStreamNode(g osgraph.MutableUniqueGraph, is *imageapi.ImageStream) *ImageStreamNode {
return osgraph.EnsureUnique(g,
ImageStreamNodeName(is),
func(node osgraph.Node) graph.Node {
return &ImageStreamNode{node, is, false}
},
).(*ImageStreamNode)
}
// EnsureImageLayerNode adds a graph node for the layer if it does not already exist.
func EnsureImageLayerNode(g osgraph.MutableUniqueGraph, layer string) graph.Node {
return osgraph.EnsureUnique(g,
ImageLayerNodeName(layer),
func(node osgraph.Node) graph.Node {
return &ImageLayerNode{node, layer}
},
)
}
-207
View File
@@ -1,207 +0,0 @@
package nodes
import (
"fmt"
"reflect"
osgraph "github.com/openshift/origin/pkg/api/graph"
imageapi "github.com/openshift/origin/pkg/image/api"
)
var (
ImageStreamNodeKind = reflect.TypeOf(imageapi.ImageStream{}).Name()
ImageNodeKind = reflect.TypeOf(imageapi.Image{}).Name()
ImageStreamTagNodeKind = reflect.TypeOf(imageapi.ImageStreamTag{}).Name()
ImageStreamImageNodeKind = reflect.TypeOf(imageapi.ImageStreamImage{}).Name()
// non-api types
DockerRepositoryNodeKind = reflect.TypeOf(imageapi.DockerImageReference{}).Name()
ImageLayerNodeKind = "ImageLayer"
)
func ImageStreamNodeName(o *imageapi.ImageStream) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(ImageStreamNodeKind, o)
}
type ImageStreamNode struct {
osgraph.Node
*imageapi.ImageStream
IsFound bool
}
func (n ImageStreamNode) Found() bool {
return n.IsFound
}
func (n ImageStreamNode) Object() interface{} {
return n.ImageStream
}
func (n ImageStreamNode) String() string {
return string(ImageStreamNodeName(n.ImageStream))
}
func (n ImageStreamNode) UniqueName() osgraph.UniqueName {
return ImageStreamNodeName(n.ImageStream)
}
func (*ImageStreamNode) Kind() string {
return ImageStreamNodeKind
}
func ImageStreamTagNodeName(o *imageapi.ImageStreamTag) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(ImageStreamTagNodeKind, o)
}
type ImageStreamTagNode struct {
osgraph.Node
*imageapi.ImageStreamTag
IsFound bool
}
func (n ImageStreamTagNode) Found() bool {
return n.IsFound
}
func (n ImageStreamTagNode) ImageSpec() string {
name, tag, _ := imageapi.SplitImageStreamTag(n.ImageStreamTag.Name)
return imageapi.DockerImageReference{Namespace: n.Namespace, Name: name, Tag: tag}.String()
}
func (n ImageStreamTagNode) ImageTag() string {
_, tag, _ := imageapi.SplitImageStreamTag(n.ImageStreamTag.Name)
return tag
}
func (n ImageStreamTagNode) Object() interface{} {
return n.ImageStreamTag
}
func (n ImageStreamTagNode) String() string {
return string(ImageStreamTagNodeName(n.ImageStreamTag))
}
func (n ImageStreamTagNode) UniqueName() osgraph.UniqueName {
return ImageStreamTagNodeName(n.ImageStreamTag)
}
func (*ImageStreamTagNode) Kind() string {
return ImageStreamTagNodeKind
}
func ImageStreamImageNodeName(o *imageapi.ImageStreamImage) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(ImageStreamImageNodeKind, o)
}
type ImageStreamImageNode struct {
osgraph.Node
*imageapi.ImageStreamImage
IsFound bool
}
func (n ImageStreamImageNode) ImageSpec() string {
return n.ImageStreamImage.Namespace + "/" + n.ImageStreamImage.Name
}
func (n ImageStreamImageNode) ImageTag() string {
_, id, _ := imageapi.SplitImageStreamImage(n.ImageStreamImage.Name)
return id
}
func (n ImageStreamImageNode) Object() interface{} {
return n.ImageStreamImage
}
func (n ImageStreamImageNode) String() string {
return string(ImageStreamImageNodeName(n.ImageStreamImage))
}
func (n ImageStreamImageNode) ResourceString() string {
return "isimage/" + n.Name
}
func (n ImageStreamImageNode) UniqueName() osgraph.UniqueName {
return ImageStreamImageNodeName(n.ImageStreamImage)
}
func (*ImageStreamImageNode) Kind() string {
return ImageStreamImageNodeKind
}
func DockerImageRepositoryNodeName(o imageapi.DockerImageReference) osgraph.UniqueName {
return osgraph.UniqueName(fmt.Sprintf("%s|%s", DockerRepositoryNodeKind, o.String()))
}
type DockerImageRepositoryNode struct {
osgraph.Node
Ref imageapi.DockerImageReference
}
func (n DockerImageRepositoryNode) ImageSpec() string {
return n.Ref.String()
}
func (n DockerImageRepositoryNode) ImageTag() string {
return n.Ref.DockerClientDefaults().Tag
}
func (n DockerImageRepositoryNode) String() string {
return string(DockerImageRepositoryNodeName(n.Ref))
}
func (*DockerImageRepositoryNode) Kind() string {
return DockerRepositoryNodeKind
}
func (n DockerImageRepositoryNode) UniqueName() osgraph.UniqueName {
return DockerImageRepositoryNodeName(n.Ref)
}
func ImageNodeName(o *imageapi.Image) osgraph.UniqueName {
return osgraph.GetUniqueRuntimeObjectNodeName(ImageNodeKind, o)
}
type ImageNode struct {
osgraph.Node
Image *imageapi.Image
}
func (n ImageNode) Object() interface{} {
return n.Image
}
func (n ImageNode) String() string {
return string(ImageNodeName(n.Image))
}
func (n ImageNode) UniqueName() osgraph.UniqueName {
return ImageNodeName(n.Image)
}
func (*ImageNode) Kind() string {
return ImageNodeKind
}
func ImageLayerNodeName(layer string) osgraph.UniqueName {
return osgraph.UniqueName(fmt.Sprintf("%s|%s", ImageLayerNodeKind, layer))
}
type ImageLayerNode struct {
osgraph.Node
Layer string
}
func (n ImageLayerNode) Object() interface{} {
return n.Layer
}
func (n ImageLayerNode) String() string {
return string(ImageLayerNodeName(n.Layer))
}
func (*ImageLayerNode) Kind() string {
return ImageLayerNodeKind
}
+51
View File
@@ -0,0 +1,51 @@
package reference
import (
"strings"
"github.com/docker/distribution/reference"
)
// NamedDockerImageReference points to a Docker image.
type NamedDockerImageReference struct {
Registry string
Namespace string
Name string
Tag string
ID string
}
// ParseNamedDockerImageReference parses a Docker pull spec string into a
// NamedDockerImageReference.
func ParseNamedDockerImageReference(spec string) (NamedDockerImageReference, error) {
var ref NamedDockerImageReference
namedRef, err := reference.ParseNamed(spec)
if err != nil {
return ref, err
}
name := namedRef.Name()
i := strings.IndexRune(name, '/')
if i == -1 || (!strings.ContainsAny(name[:i], ":.") && name[:i] != "localhost") {
ref.Name = name
} else {
ref.Registry, ref.Name = name[:i], name[i+1:]
}
if named, ok := namedRef.(reference.NamedTagged); ok {
ref.Tag = named.Tag()
}
if named, ok := namedRef.(reference.Canonical); ok {
ref.ID = named.Digest().String()
}
// It's not enough just to use the reference.ParseNamed(). We have to fill
// ref.Namespace from ref.Name
if i := strings.IndexRune(ref.Name, '/'); i != -1 {
ref.Namespace, ref.Name = ref.Name[:i], ref.Name[i+1:]
}
return ref, nil
}
+2
View File
@@ -37,6 +37,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&OAuthClientList{},
&OAuthClientAuthorization{},
&OAuthClientAuthorizationList{},
&OAuthRedirectReference{},
)
return nil
}
@@ -49,3 +50,4 @@ func (obj *OAuthAuthorizeTokenList) GetObjectKind() unversioned.ObjectKind
func (obj *OAuthAuthorizeToken) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *OAuthAccessTokenList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *OAuthAccessToken) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *OAuthRedirectReference) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
+18
View File
@@ -59,6 +59,12 @@ type OAuthAuthorizeToken struct {
// UserUID is the unique UID associated with this token. UserUID and UserName must both match
// for this token to be valid.
UserUID string
// CodeChallenge is the optional code_challenge associated with this authorization code, as described in rfc7636
CodeChallenge string
// CodeChallengeMethod is the optional code_challenge_method associated with this authorization code, as described in rfc7636
CodeChallengeMethod string
}
// +genclient=true
@@ -161,3 +167,15 @@ type OAuthClientAuthorizationList struct {
unversioned.ListMeta
Items []OAuthClientAuthorization
}
type OAuthRedirectReference struct {
unversioned.TypeMeta
kapi.ObjectMeta
Reference RedirectReference
}
type RedirectReference struct {
Group string
Kind string
Name string
}
@@ -28,6 +28,8 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_OAuthClientAuthorization, InType: reflect.TypeOf(&OAuthClientAuthorization{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_OAuthClientAuthorizationList, InType: reflect.TypeOf(&OAuthClientAuthorizationList{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_OAuthClientList, InType: reflect.TypeOf(&OAuthClientList{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_OAuthRedirectReference, InType: reflect.TypeOf(&OAuthRedirectReference{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_RedirectReference, InType: reflect.TypeOf(&RedirectReference{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ScopeRestriction, InType: reflect.TypeOf(&ScopeRestriction{})},
)
}
@@ -123,6 +125,8 @@ func DeepCopy_api_OAuthAuthorizeToken(in interface{}, out interface{}, c *conver
out.State = in.State
out.UserName = in.UserName
out.UserUID = in.UserUID
out.CodeChallenge = in.CodeChallenge
out.CodeChallengeMethod = in.CodeChallengeMethod
return nil
}
}
@@ -252,6 +256,30 @@ func DeepCopy_api_OAuthClientList(in interface{}, out interface{}, c *conversion
}
}
func DeepCopy_api_OAuthRedirectReference(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*OAuthRedirectReference)
out := out.(*OAuthRedirectReference)
out.TypeMeta = in.TypeMeta
if err := pkg_api.DeepCopy_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, c); err != nil {
return err
}
out.Reference = in.Reference
return nil
}
}
func DeepCopy_api_RedirectReference(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*RedirectReference)
out := out.(*RedirectReference)
out.Group = in.Group
out.Kind = in.Kind
out.Name = in.Name
return nil
}
}
func DeepCopy_api_ScopeRestriction(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*ScopeRestriction)

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