Merge pull request #39341 from smarterclayton/termination_log

Automatic merge from submit-queue

Improve TerminationMessagePath to be more flexible

* Support `terminationMessagePolicy: fallbackToLogsOnError` which allows pod authors to get useful information from containers as per kubernetes/community#154
* Set an upper bound on the size of the termination message path or log output to prevent callers from DoSing the master
* Add tests for running as root, non-root, and for the new terminationMessagePolicy cases.

I set the limit to 4096 bytes, but this may be too high for large pod containers.  Probably need to set an absolute bound, i.e. max message size allowed is 20k total, and we truncate if we're above that limit.

Fixes #31839, #23569

```release-note
A new field `terminationMessagePolicy` has been added to containers that allows a user to request `FallbackToLogsOnError`, which will read from the container's logs to populate the termination message if the user does not write to the termination message log file.  The termination message file is now properly readable for end users and has a maximum size (4k bytes) to prevent abuse.  Each pod may have up to 12k bytes of termination messages before the contents of each will be truncated.
```
pull/6/head
Kubernetes Submit Queue 2017-01-23 12:13:33 -08:00 committed by GitHub
commit fce60637ae
67 changed files with 2079 additions and 1223 deletions

4
Godeps/Godeps.json generated
View File

@ -128,6 +128,10 @@
"Comment": "v0.8.1-6-gab50d12",
"Rev": "ab50d12e88f57788bf84b83fef2be236eb1fcc0b"
},
{
"ImportPath": "github.com/armon/circbuf",
"Rev": "bbbad097214e2918d8543d5201d12bfd7bca254d"
},
{
"ImportPath": "github.com/asaskevich/govalidator",
"Comment": "v4-12-g593d645",

28
Godeps/LICENSES generated
View File

@ -1546,6 +1546,34 @@ Apache License
================================================================================
================================================================================
= vendor/github.com/armon/circbuf licensed under: =
The MIT License (MIT)
Copyright (c) 2013 Armon Dadgar
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
= vendor/github.com/armon/circbuf/LICENSE d2d77030c0183e3d1e66d26dc1f243be -
================================================================================
================================================================================
= vendor/github.com/asaskevich/govalidator licensed under: =

View File

@ -35061,7 +35061,11 @@
"type": "boolean"
},
"terminationMessagePath": {
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.",
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.",
"type": "string"
},
"terminationMessagePolicy": {
"description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.",
"type": "string"
},
"tty": {

View File

@ -2191,7 +2191,11 @@
},
"terminationMessagePath": {
"type": "string",
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated."
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated."
},
"terminationMessagePolicy": {
"type": "string",
"description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated."
},
"imagePullPolicy": {
"type": "string",

View File

@ -2196,7 +2196,11 @@
},
"terminationMessagePath": {
"type": "string",
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated."
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated."
},
"terminationMessagePolicy": {
"type": "string",
"description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated."
},
"imagePullPolicy": {
"type": "string",

View File

@ -8568,7 +8568,11 @@
},
"terminationMessagePath": {
"type": "string",
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated."
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated."
},
"terminationMessagePolicy": {
"type": "string",
"description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated."
},
"imagePullPolicy": {
"type": "string",

View File

@ -18660,7 +18660,11 @@
},
"terminationMessagePath": {
"type": "string",
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated."
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated."
},
"terminationMessagePolicy": {
"type": "string",
"description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated."
},
"imagePullPolicy": {
"type": "string",

View File

@ -1803,7 +1803,14 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePolicy</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
@ -5011,7 +5018,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-01-19 19:02:01 UTC
Last updated 2017-01-20 19:58:01 UTC
</div>
</div>
</body>

View File

@ -1707,7 +1707,14 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePolicy</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
@ -4943,7 +4950,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-01-19 19:02:17 UTC
Last updated 2017-01-20 19:58:30 UTC
</div>
</div>
</body>

View File

@ -5765,7 +5765,14 @@ Both these may change in the future. Incoming requests are matched against the h
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePolicy</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
@ -7560,7 +7567,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-01-19 19:02:29 UTC
Last updated 2017-01-20 19:59:01 UTC
</div>
</div>
</body>

View File

@ -6380,7 +6380,14 @@ Examples:<br>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePolicy</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
@ -9187,7 +9194,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-01-19 19:01:55 UTC
Last updated 2017-01-20 19:57:54 UTC
</div>
</div>
</body>

View File

@ -10895,7 +10895,11 @@
"type": "boolean"
},
"terminationMessagePath": {
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.",
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.",
"type": "string"
},
"terminationMessagePolicy": {
"description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.",
"type": "string"
},
"tty": {

View File

@ -5474,7 +5474,11 @@
},
"terminationMessagePath": {
"type": "string",
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated."
"description": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated."
},
"terminationMessagePolicy": {
"type": "string",
"description": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated."
},
"imagePullPolicy": {
"type": "string",

View File

@ -4923,7 +4923,14 @@ Both these may change in the future. Incoming requests are matched against the h
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Optional: Path at which the file to which the container&#8217;s termination message will be written is mounted into the container&#8217;s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">terminationMessagePolicy</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
@ -6263,7 +6270,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-01-06 18:15:24 UTC
Last updated 2017-01-17 18:39:44 UTC
</div>
</div>
</body>

View File

@ -289,6 +289,7 @@ pkg/util/replicaset
pkg/util/restoptions
pkg/util/runtime
pkg/util/sets
pkg/util/tail
pkg/util/validation
pkg/util/validation/field
pkg/util/version

View File

@ -358,6 +358,7 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz
func(ct *api.Container, c fuzz.Continue) {
c.FuzzNoCustom(ct) // fuzz self without calling this function again
ct.TerminationMessagePath = "/" + ct.TerminationMessagePath // Must be non-empty
ct.TerminationMessagePolicy = "File"
},
func(p *api.Probe, c fuzz.Continue) {
c.FuzzNoCustom(p)

View File

@ -1257,6 +1257,19 @@ const (
PullIfNotPresent PullPolicy = "IfNotPresent"
)
// TerminationMessagePolicy describes how termination messages are retrieved from a container.
type TerminationMessagePolicy string
const (
// TerminationMessageReadFile is the default behavior and will set the container status message to
// the contents of the container's terminationMessagePath when the container exits.
TerminationMessageReadFile TerminationMessagePolicy = "File"
// TerminationMessageFallbackToLogsOnError will read the most recent contents of the container logs
// for the container status message when the container exits with an error and the
// terminationMessagePath has no contents.
TerminationMessageFallbackToLogsOnError TerminationMessagePolicy = "FallbackToLogsOnError"
)
// Capability represent POSIX capabilities type
type Capability string
@ -1332,6 +1345,8 @@ type Container struct {
// Required.
// +optional
TerminationMessagePath string
// +optional
TerminationMessagePolicy TerminationMessagePolicy
// Required: Policy for pulling images for this container
ImagePullPolicy PullPolicy
// Optional: SecurityContext defines the security options the container should be run with.

View File

@ -121,6 +121,9 @@ func SetDefaults_Container(obj *Container) {
if obj.TerminationMessagePath == "" {
obj.TerminationMessagePath = TerminationMessagePathDefault
}
if obj.TerminationMessagePolicy == "" {
obj.TerminationMessagePolicy = TerminationMessageReadFile
}
}
func SetDefaults_ServiceSpec(obj *ServiceSpec) {
if obj.SessionAffinity == "" {

File diff suppressed because it is too large Load Diff

View File

@ -426,11 +426,23 @@ message Container {
// Optional: Path at which the file to which the container's termination message
// will be written is mounted into the container's filesystem.
// Message written is intended to be brief final status, such as an assertion failure message.
// Will be truncated by the node if greater than 4096 bytes. The total message length across
// all containers will be limited to 12kb.
// Defaults to /dev/termination-log.
// Cannot be updated.
// +optional
optional string terminationMessagePath = 13;
// Indicate how the termination message should be populated. File will use the contents of
// terminationMessagePath to populate the container status message on both success and failure.
// FallbackToLogsOnError will use the last chunk of container log output if the termination
// message file is empty and the container exited with an error.
// The log output is limited to 2048 bytes or 80 lines, whichever is smaller.
// Defaults to File.
// Cannot be updated.
// +optional
optional string terminationMessagePolicy = 20;
// Image pull policy.
// One of Always, Never, IfNotPresent.
// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.

View File

@ -22190,6 +22190,32 @@ func (x *PullPolicy) CodecDecodeSelf(d *codec1978.Decoder) {
}
}
func (x TerminationMessagePolicy) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
yym1 := z.EncBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.EncExt(x) {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x))
}
}
func (x *TerminationMessagePolicy) CodecDecodeSelf(d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yym1 := z.DecBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.DecExt(x) {
} else {
*((*string)(x)) = r.DecodeString()
}
}
func (x Capability) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
@ -22704,7 +22730,7 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [19]bool
var yyq2 [20]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[1] = x.Image != ""
@ -22720,14 +22746,15 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
yyq2[11] = x.ReadinessProbe != nil
yyq2[12] = x.Lifecycle != nil
yyq2[13] = x.TerminationMessagePath != ""
yyq2[14] = x.ImagePullPolicy != ""
yyq2[15] = x.SecurityContext != nil
yyq2[16] = x.Stdin != false
yyq2[17] = x.StdinOnce != false
yyq2[18] = x.TTY != false
yyq2[14] = x.TerminationMessagePolicy != ""
yyq2[15] = x.ImagePullPolicy != ""
yyq2[16] = x.SecurityContext != nil
yyq2[17] = x.Stdin != false
yyq2[18] = x.StdinOnce != false
yyq2[19] = x.TTY != false
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(19)
r.EncodeArrayStart(20)
} else {
yynn2 = 1
for _, b := range yyq2 {
@ -23119,12 +23146,27 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[14] {
x.ImagePullPolicy.CodecEncodeSelf(e)
x.TerminationMessagePolicy.CodecEncodeSelf(e)
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[14] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("terminationMessagePolicy"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
x.TerminationMessagePolicy.CodecEncodeSelf(e)
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[15] {
x.ImagePullPolicy.CodecEncodeSelf(e)
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[15] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("imagePullPolicy"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
@ -23133,7 +23175,7 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[15] {
if yyq2[16] {
if x.SecurityContext == nil {
r.EncodeNil()
} else {
@ -23143,7 +23185,7 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
r.EncodeNil()
}
} else {
if yyq2[15] {
if yyq2[16] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("securityContext"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
@ -23154,31 +23196,6 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[16] {
yym54 := z.EncBinary()
_ = yym54
if false {
} else {
r.EncodeBool(bool(x.Stdin))
}
} else {
r.EncodeBool(false)
}
} else {
if yyq2[16] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("stdin"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym55 := z.EncBinary()
_ = yym55
if false {
} else {
r.EncodeBool(bool(x.Stdin))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[17] {
@ -23186,7 +23203,7 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
_ = yym57
if false {
} else {
r.EncodeBool(bool(x.StdinOnce))
r.EncodeBool(bool(x.Stdin))
}
} else {
r.EncodeBool(false)
@ -23194,13 +23211,13 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
if yyq2[17] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("stdinOnce"))
r.EncodeString(codecSelferC_UTF81234, string("stdin"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym58 := z.EncBinary()
_ = yym58
if false {
} else {
r.EncodeBool(bool(x.StdinOnce))
r.EncodeBool(bool(x.Stdin))
}
}
}
@ -23211,7 +23228,7 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
_ = yym60
if false {
} else {
r.EncodeBool(bool(x.TTY))
r.EncodeBool(bool(x.StdinOnce))
}
} else {
r.EncodeBool(false)
@ -23219,11 +23236,36 @@ func (x *Container) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
if yyq2[18] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("tty"))
r.EncodeString(codecSelferC_UTF81234, string("stdinOnce"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym61 := z.EncBinary()
_ = yym61
if false {
} else {
r.EncodeBool(bool(x.StdinOnce))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[19] {
yym63 := z.EncBinary()
_ = yym63
if false {
} else {
r.EncodeBool(bool(x.TTY))
}
} else {
r.EncodeBool(false)
}
} else {
if yyq2[19] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("tty"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym64 := z.EncBinary()
_ = yym64
if false {
} else {
r.EncodeBool(bool(x.TTY))
}
@ -23450,12 +23492,19 @@ func (x *Container) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
*((*string)(yyv26)) = r.DecodeString()
}
}
case "terminationMessagePolicy":
if r.TryDecodeAsNil() {
x.TerminationMessagePolicy = ""
} else {
yyv28 := &x.TerminationMessagePolicy
yyv28.CodecDecodeSelf(d)
}
case "imagePullPolicy":
if r.TryDecodeAsNil() {
x.ImagePullPolicy = ""
} else {
yyv28 := &x.ImagePullPolicy
yyv28.CodecDecodeSelf(d)
yyv29 := &x.ImagePullPolicy
yyv29.CodecDecodeSelf(d)
}
case "securityContext":
if r.TryDecodeAsNil() {
@ -23472,36 +23521,36 @@ func (x *Container) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Stdin = false
} else {
yyv30 := &x.Stdin
yym31 := z.DecBinary()
_ = yym31
yyv31 := &x.Stdin
yym32 := z.DecBinary()
_ = yym32
if false {
} else {
*((*bool)(yyv30)) = r.DecodeBool()
*((*bool)(yyv31)) = r.DecodeBool()
}
}
case "stdinOnce":
if r.TryDecodeAsNil() {
x.StdinOnce = false
} else {
yyv32 := &x.StdinOnce
yym33 := z.DecBinary()
_ = yym33
yyv33 := &x.StdinOnce
yym34 := z.DecBinary()
_ = yym34
if false {
} else {
*((*bool)(yyv32)) = r.DecodeBool()
*((*bool)(yyv33)) = r.DecodeBool()
}
}
case "tty":
if r.TryDecodeAsNil() {
x.TTY = false
} else {
yyv34 := &x.TTY
yym35 := z.DecBinary()
_ = yym35
yyv35 := &x.TTY
yym36 := z.DecBinary()
_ = yym36
if false {
} else {
*((*bool)(yyv34)) = r.DecodeBool()
*((*bool)(yyv35)) = r.DecodeBool()
}
}
default:
@ -23515,16 +23564,16 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj36 int
var yyb36 bool
var yyhl36 bool = l >= 0
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
var yyj37 int
var yyb37 bool
var yyhl37 bool = l >= 0
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23532,21 +23581,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Name = ""
} else {
yyv37 := &x.Name
yym38 := z.DecBinary()
_ = yym38
yyv38 := &x.Name
yym39 := z.DecBinary()
_ = yym39
if false {
} else {
*((*string)(yyv37)) = r.DecodeString()
*((*string)(yyv38)) = r.DecodeString()
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23554,21 +23603,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Image = ""
} else {
yyv39 := &x.Image
yym40 := z.DecBinary()
_ = yym40
yyv40 := &x.Image
yym41 := z.DecBinary()
_ = yym41
if false {
} else {
*((*string)(yyv39)) = r.DecodeString()
*((*string)(yyv40)) = r.DecodeString()
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23576,21 +23625,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Command = nil
} else {
yyv41 := &x.Command
yym42 := z.DecBinary()
_ = yym42
yyv42 := &x.Command
yym43 := z.DecBinary()
_ = yym43
if false {
} else {
z.F.DecSliceStringX(yyv41, false, d)
z.F.DecSliceStringX(yyv42, false, d)
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23598,21 +23647,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Args = nil
} else {
yyv43 := &x.Args
yym44 := z.DecBinary()
_ = yym44
yyv44 := &x.Args
yym45 := z.DecBinary()
_ = yym45
if false {
} else {
z.F.DecSliceStringX(yyv43, false, d)
z.F.DecSliceStringX(yyv44, false, d)
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23620,21 +23669,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.WorkingDir = ""
} else {
yyv45 := &x.WorkingDir
yym46 := z.DecBinary()
_ = yym46
yyv46 := &x.WorkingDir
yym47 := z.DecBinary()
_ = yym47
if false {
} else {
*((*string)(yyv45)) = r.DecodeString()
*((*string)(yyv46)) = r.DecodeString()
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23642,21 +23691,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Ports = nil
} else {
yyv47 := &x.Ports
yym48 := z.DecBinary()
_ = yym48
yyv48 := &x.Ports
yym49 := z.DecBinary()
_ = yym49
if false {
} else {
h.decSliceContainerPort((*[]ContainerPort)(yyv47), d)
h.decSliceContainerPort((*[]ContainerPort)(yyv48), d)
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23664,21 +23713,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.EnvFrom = nil
} else {
yyv49 := &x.EnvFrom
yym50 := z.DecBinary()
_ = yym50
yyv50 := &x.EnvFrom
yym51 := z.DecBinary()
_ = yym51
if false {
} else {
h.decSliceEnvFromSource((*[]EnvFromSource)(yyv49), d)
h.decSliceEnvFromSource((*[]EnvFromSource)(yyv50), d)
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23686,21 +23735,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Env = nil
} else {
yyv51 := &x.Env
yym52 := z.DecBinary()
_ = yym52
yyv52 := &x.Env
yym53 := z.DecBinary()
_ = yym53
if false {
} else {
h.decSliceEnvVar((*[]EnvVar)(yyv51), d)
h.decSliceEnvVar((*[]EnvVar)(yyv52), d)
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23708,16 +23757,16 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Resources = ResourceRequirements{}
} else {
yyv53 := &x.Resources
yyv53.CodecDecodeSelf(d)
yyv54 := &x.Resources
yyv54.CodecDecodeSelf(d)
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23725,21 +23774,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.VolumeMounts = nil
} else {
yyv54 := &x.VolumeMounts
yym55 := z.DecBinary()
_ = yym55
yyv55 := &x.VolumeMounts
yym56 := z.DecBinary()
_ = yym56
if false {
} else {
h.decSliceVolumeMount((*[]VolumeMount)(yyv54), d)
h.decSliceVolumeMount((*[]VolumeMount)(yyv55), d)
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23754,13 +23803,13 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
}
x.LivenessProbe.CodecDecodeSelf(d)
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23775,13 +23824,13 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
}
x.ReadinessProbe.CodecDecodeSelf(d)
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23796,13 +23845,13 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
}
x.Lifecycle.CodecDecodeSelf(d)
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23810,21 +23859,38 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.TerminationMessagePath = ""
} else {
yyv59 := &x.TerminationMessagePath
yym60 := z.DecBinary()
_ = yym60
yyv60 := &x.TerminationMessagePath
yym61 := z.DecBinary()
_ = yym61
if false {
} else {
*((*string)(yyv59)) = r.DecodeString()
*((*string)(yyv60)) = r.DecodeString()
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.TerminationMessagePolicy = ""
} else {
yyv62 := &x.TerminationMessagePolicy
yyv62.CodecDecodeSelf(d)
}
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb37 = r.CheckBreak()
}
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23832,16 +23898,16 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.ImagePullPolicy = ""
} else {
yyv61 := &x.ImagePullPolicy
yyv61.CodecDecodeSelf(d)
yyv63 := &x.ImagePullPolicy
yyv63.CodecDecodeSelf(d)
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23856,13 +23922,13 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
}
x.SecurityContext.CodecDecodeSelf(d)
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -23870,29 +23936,7 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Stdin = false
} else {
yyv63 := &x.Stdin
yym64 := z.DecBinary()
_ = yym64
if false {
} else {
*((*bool)(yyv63)) = r.DecodeBool()
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
} else {
yyb36 = r.CheckBreak()
}
if yyb36 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.StdinOnce = false
} else {
yyv65 := &x.StdinOnce
yyv65 := &x.Stdin
yym66 := z.DecBinary()
_ = yym66
if false {
@ -23900,21 +23944,21 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
*((*bool)(yyv65)) = r.DecodeBool()
}
}
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb36 = r.CheckBreak()
yyb37 = r.CheckBreak()
}
if yyb36 {
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.TTY = false
x.StdinOnce = false
} else {
yyv67 := &x.TTY
yyv67 := &x.StdinOnce
yym68 := z.DecBinary()
_ = yym68
if false {
@ -23922,18 +23966,40 @@ func (x *Container) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
*((*bool)(yyv67)) = r.DecodeBool()
}
}
for {
yyj36++
if yyhl36 {
yyb36 = yyj36 > l
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb37 = r.CheckBreak()
}
if yyb37 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.TTY = false
} else {
yyv69 := &x.TTY
yym70 := z.DecBinary()
_ = yym70
if false {
} else {
yyb36 = r.CheckBreak()
*((*bool)(yyv69)) = r.DecodeBool()
}
if yyb36 {
}
for {
yyj37++
if yyhl37 {
yyb37 = yyj37 > l
} else {
yyb37 = r.CheckBreak()
}
if yyb37 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj36-1, "")
z.DecStructFieldNotFound(yyj37-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
@ -64355,7 +64421,7 @@ func (x codecSelfer1234) decSliceContainer(v *[]Container, d *codec1978.Decoder)
yyrg1 := len(yyv1) > 0
yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 280)
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 296)
if yyrt1 {
if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1]

View File

@ -1367,6 +1367,19 @@ const (
PullIfNotPresent PullPolicy = "IfNotPresent"
)
// TerminationMessagePolicy describes how termination messages are retrieved from a container.
type TerminationMessagePolicy string
const (
// TerminationMessageReadFile is the default behavior and will set the container status message to
// the contents of the container's terminationMessagePath when the container exits.
TerminationMessageReadFile TerminationMessagePolicy = "File"
// TerminationMessageFallbackToLogsOnError will read the most recent contents of the container logs
// for the container status message when the container exits with an error and the
// terminationMessagePath has no contents.
TerminationMessageFallbackToLogsOnError TerminationMessagePolicy = "FallbackToLogsOnError"
)
// Capability represent POSIX capabilities type
type Capability string
@ -1484,10 +1497,21 @@ type Container struct {
// Optional: Path at which the file to which the container's termination message
// will be written is mounted into the container's filesystem.
// Message written is intended to be brief final status, such as an assertion failure message.
// Will be truncated by the node if greater than 4096 bytes. The total message length across
// all containers will be limited to 12kb.
// Defaults to /dev/termination-log.
// Cannot be updated.
// +optional
TerminationMessagePath string `json:"terminationMessagePath,omitempty" protobuf:"bytes,13,opt,name=terminationMessagePath"`
// Indicate how the termination message should be populated. File will use the contents of
// terminationMessagePath to populate the container status message on both success and failure.
// FallbackToLogsOnError will use the last chunk of container log output if the termination
// message file is empty and the container exited with an error.
// The log output is limited to 2048 bytes or 80 lines, whichever is smaller.
// Defaults to File.
// Cannot be updated.
// +optional
TerminationMessagePolicy TerminationMessagePolicy `json:"terminationMessagePolicy,omitempty" protobuf:"bytes,20,opt,name=terminationMessagePolicy,casttype=TerminationMessagePolicy"`
// Image pull policy.
// One of Always, Never, IfNotPresent.
// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.

View File

@ -218,26 +218,27 @@ func (ConfigMapVolumeSource) SwaggerDoc() map[string]string {
}
var map_Container = map[string]string{
"": "A single application container that you want to run within a pod.",
"name": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.",
"image": "Docker image name. More info: http://kubernetes.io/docs/user-guide/images",
"command": "Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers#containers-and-commands",
"args": "Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers#containers-and-commands",
"workingDir": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.",
"ports": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated.",
"envFrom": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. An invalid key will prevent the container from starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.",
"env": "List of environment variables to set in the container. Cannot be updated.",
"resources": "Compute Resources required by this container. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/persistent-volumes#resources",
"volumeMounts": "Pod volumes to mount into the container's filesystem. Cannot be updated.",
"livenessProbe": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/pod-states#container-probes",
"readinessProbe": "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/pod-states#container-probes",
"lifecycle": "Actions that the management system should take in response to container lifecycle events. Cannot be updated.",
"terminationMessagePath": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.",
"imagePullPolicy": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/images#updating-images",
"securityContext": "Security options the pod should run with. More info: http://releases.k8s.io/HEAD/docs/design/security_context.md",
"stdin": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.",
"stdinOnce": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false",
"tty": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.",
"": "A single application container that you want to run within a pod.",
"name": "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.",
"image": "Docker image name. More info: http://kubernetes.io/docs/user-guide/images",
"command": "Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers#containers-and-commands",
"args": "Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/containers#containers-and-commands",
"workingDir": "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.",
"ports": "List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Cannot be updated.",
"envFrom": "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. An invalid key will prevent the container from starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.",
"env": "List of environment variables to set in the container. Cannot be updated.",
"resources": "Compute Resources required by this container. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/persistent-volumes#resources",
"volumeMounts": "Pod volumes to mount into the container's filesystem. Cannot be updated.",
"livenessProbe": "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/pod-states#container-probes",
"readinessProbe": "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/pod-states#container-probes",
"lifecycle": "Actions that the management system should take in response to container lifecycle events. Cannot be updated.",
"terminationMessagePath": "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.",
"terminationMessagePolicy": "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.",
"imagePullPolicy": "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/images#updating-images",
"securityContext": "Security options the pod should run with. More info: http://releases.k8s.io/HEAD/docs/design/security_context.md",
"stdin": "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.",
"stdinOnce": "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false",
"tty": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.",
}
func (Container) SwaggerDoc() map[string]string {

View File

@ -779,6 +779,7 @@ func autoConvert_v1_Container_To_api_Container(in *Container, out *api.Container
out.ReadinessProbe = (*api.Probe)(unsafe.Pointer(in.ReadinessProbe))
out.Lifecycle = (*api.Lifecycle)(unsafe.Pointer(in.Lifecycle))
out.TerminationMessagePath = in.TerminationMessagePath
out.TerminationMessagePolicy = api.TerminationMessagePolicy(in.TerminationMessagePolicy)
out.ImagePullPolicy = api.PullPolicy(in.ImagePullPolicy)
out.SecurityContext = (*api.SecurityContext)(unsafe.Pointer(in.SecurityContext))
out.Stdin = in.Stdin
@ -808,6 +809,7 @@ func autoConvert_api_Container_To_v1_Container(in *api.Container, out *Container
out.ReadinessProbe = (*Probe)(unsafe.Pointer(in.ReadinessProbe))
out.Lifecycle = (*Lifecycle)(unsafe.Pointer(in.Lifecycle))
out.TerminationMessagePath = in.TerminationMessagePath
out.TerminationMessagePolicy = TerminationMessagePolicy(in.TerminationMessagePolicy)
out.ImagePullPolicy = PullPolicy(in.ImagePullPolicy)
out.SecurityContext = (*SecurityContext)(unsafe.Pointer(in.SecurityContext))
out.Stdin = in.Stdin

View File

@ -1597,6 +1597,14 @@ func validateContainers(containers []api.Container, volumes sets.String, fldPath
allErrs = append(allErrs, field.Invalid(idxPath.Child("livenessProbe", "successThreshold"), ctr.LivenessProbe.SuccessThreshold, "must be 1"))
}
switch ctr.TerminationMessagePolicy {
case api.TerminationMessageReadFile, api.TerminationMessageFallbackToLogsOnError:
case "":
allErrs = append(allErrs, field.Required(idxPath.Child("terminationMessagePolicy"), "must be 'File' or 'FallbackToLogsOnError'"))
default:
allErrs = append(allErrs, field.Invalid(idxPath.Child("terminationMessagePolicy"), ctr.TerminationMessagePolicy, "must be 'File' or 'FallbackToLogsOnError'"))
}
allErrs = append(allErrs, validateProbe(ctr.ReadinessProbe, idxPath.Child("readinessProbe"))...)
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...)
allErrs = append(allErrs, validateEnv(ctr.Env, idxPath.Child("env"))...)

View File

@ -2484,11 +2484,11 @@ func TestValidatePullPolicy(t *testing.T) {
}
testCases := map[string]T{
"NotPresent1": {
api.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent"},
api.Container{Name: "abc", Image: "image:latest", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
api.PullIfNotPresent,
},
"NotPresent2": {
api.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent"},
api.Container{Name: "abc1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
api.PullIfNotPresent,
},
"Always1": {
@ -2534,9 +2534,9 @@ func TestValidateContainers(t *testing.T) {
})
successCase := []api.Container{
{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"},
{Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent"},
{Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent"},
{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
{Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
{Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
{
Name: "life-123",
Image: "image",
@ -2545,7 +2545,8 @@ func TestValidateContainers(t *testing.T) {
Exec: &api.ExecAction{Command: []string{"ls", "-l"}},
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "resources-test",
@ -2557,7 +2558,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "resources-test-with-gpu-with-request",
@ -2574,7 +2576,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "resources-test-with-gpu-without-request",
@ -2590,7 +2593,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "resources-request-limit-simple",
@ -2603,7 +2607,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "resources-request-limit-edge",
@ -2620,7 +2625,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "resources-request-limit-partials",
@ -2635,7 +2641,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName("my.org/resource"): resource.MustParse("10m"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "resources-request",
@ -2646,7 +2653,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{
Name: "same-host-port-different-protocol",
@ -2655,9 +2663,22 @@ func TestValidateContainers(t *testing.T) {
{ContainerPort: 80, HostPort: 80, Protocol: "TCP"},
{ContainerPort: 80, HostPort: 80, Protocol: "UDP"},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", SecurityContext: fakeValidSecurityContext(true)},
{
Name: "fallback-to-logs-termination-message",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "FallbackToLogsOnError",
},
{
Name: "file-termination-message",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)},
}
if errs := validateContainers(successCase, volumes, field.NewPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
@ -2667,26 +2688,26 @@ func TestValidateContainers(t *testing.T) {
AllowPrivileged: false,
})
errorCases := map[string][]api.Container{
"zero-length name": {{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent"}},
"name > 63 characters": {{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent"}},
"name not a DNS label": {{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent"}},
"zero-length name": {{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
"name > 63 characters": {{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
"name not a DNS label": {{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
"name not unique": {
{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"},
{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"},
{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
},
"zero-length image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent"}},
"zero-length image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
"host port not unique": {
{Name: "abc", Image: "image", Ports: []api.ContainerPort{{ContainerPort: 80, HostPort: 80, Protocol: "TCP"}},
ImagePullPolicy: "IfNotPresent"},
ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
{Name: "def", Image: "image", Ports: []api.ContainerPort{{ContainerPort: 81, HostPort: 80, Protocol: "TCP"}},
ImagePullPolicy: "IfNotPresent"},
ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
},
"invalid env var name": {
{Name: "abc", Image: "image", Env: []api.EnvVar{{Name: "ev.1"}}, ImagePullPolicy: "IfNotPresent"},
{Name: "abc", Image: "image", Env: []api.EnvVar{{Name: "ev.1"}}, ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
},
"unknown volume name": {
{Name: "abc", Image: "image", VolumeMounts: []api.VolumeMount{{Name: "anything", MountPath: "/foo"}},
ImagePullPolicy: "IfNotPresent"},
ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
},
"invalid lifecycle, no exec command.": {
{
@ -2697,7 +2718,8 @@ func TestValidateContainers(t *testing.T) {
Exec: &api.ExecAction{},
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid lifecycle, no http path.": {
@ -2709,7 +2731,8 @@ func TestValidateContainers(t *testing.T) {
HTTPGet: &api.HTTPGetAction{},
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid lifecycle, no tcp socket port.": {
@ -2721,7 +2744,8 @@ func TestValidateContainers(t *testing.T) {
TCPSocket: &api.TCPSocketAction{},
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid lifecycle, zero tcp socket port.": {
@ -2735,7 +2759,8 @@ func TestValidateContainers(t *testing.T) {
},
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid lifecycle, no action.": {
@ -2745,7 +2770,8 @@ func TestValidateContainers(t *testing.T) {
Lifecycle: &api.Lifecycle{
PreStop: &api.Handler{},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid liveness probe, no tcp socket port.": {
@ -2757,7 +2783,8 @@ func TestValidateContainers(t *testing.T) {
TCPSocket: &api.TCPSocketAction{},
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid liveness probe, no action.": {
@ -2767,7 +2794,24 @@ func TestValidateContainers(t *testing.T) {
LivenessProbe: &api.Probe{
Handler: api.Handler{},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"invalid message termination policy": {
{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "Unknown",
},
},
"empty message termination policy": {
{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "",
},
},
"privilege disabled": {
@ -2782,7 +2826,8 @@ func TestValidateContainers(t *testing.T) {
"disk": resource.MustParse("10G"),
},
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"Resource CPU invalid": {
@ -2792,7 +2837,8 @@ func TestValidateContainers(t *testing.T) {
Resources: api.ResourceRequirements{
Limits: getResourceLimits("-10", "0"),
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"Resource Requests CPU invalid": {
@ -2802,7 +2848,8 @@ func TestValidateContainers(t *testing.T) {
Resources: api.ResourceRequirements{
Requests: getResourceLimits("-10", "0"),
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"Resource Memory invalid": {
@ -2812,7 +2859,8 @@ func TestValidateContainers(t *testing.T) {
Resources: api.ResourceRequirements{
Limits: getResourceLimits("0", "-10"),
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"Resource GPU limit must match request": {
@ -2831,7 +2879,8 @@ func TestValidateContainers(t *testing.T) {
api.ResourceName(api.ResourceNvidiaGPU): resource.MustParse("1"),
},
},
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
ImagePullPolicy: "IfNotPresent",
},
},
"Request limit simple invalid": {
@ -2842,7 +2891,8 @@ func TestValidateContainers(t *testing.T) {
Limits: getResourceLimits("5", "3"),
Requests: getResourceLimits("6", "3"),
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
"Request limit multiple invalid": {
@ -2853,7 +2903,8 @@ func TestValidateContainers(t *testing.T) {
Limits: getResourceLimits("5", "3"),
Requests: getResourceLimits("6", "4"),
},
ImagePullPolicy: "IfNotPresent",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
},
},
}
@ -2908,7 +2959,7 @@ func TestValidatePodSpec(t *testing.T) {
successCases := []api.PodSpec{
{ // Populate basic fields, leave defaults for most.
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -2916,8 +2967,8 @@ func TestValidatePodSpec(t *testing.T) {
Volumes: []api.Volume{
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
InitContainers: []api.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
InitContainers: []api.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
NodeSelector: map[string]string{
"key": "value",
@ -2929,8 +2980,9 @@ func TestValidatePodSpec(t *testing.T) {
},
{ // Populate HostNetwork.
Containers: []api.Container{
{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []api.ContainerPort{
{HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}},
{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
Ports: []api.ContainerPort{
{HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}},
},
},
SecurityContext: &api.PodSecurityContext{
@ -2940,7 +2992,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
{ // Populate RunAsUser SupplementalGroups FSGroup with minID 0
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
SupplementalGroups: []int64{minID},
RunAsUser: &minID,
@ -2950,7 +3002,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
{ // Populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
SupplementalGroups: []int64{maxID},
RunAsUser: &maxID,
@ -2964,7 +3016,7 @@ func TestValidatePodSpec(t *testing.T) {
HostIPC: true,
},
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -2973,13 +3025,13 @@ func TestValidatePodSpec(t *testing.T) {
HostPID: true,
},
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
{ // Populate Affinity.
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -2998,7 +3050,7 @@ func TestValidatePodSpec(t *testing.T) {
Volumes: []api.Volume{{}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
"no containers": {
RestartPolicy: api.RestartPolicyAlways,
@ -3010,7 +3062,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
"bad init container": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
InitContainers: []api.Container{{}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
@ -3018,10 +3070,10 @@ func TestValidatePodSpec(t *testing.T) {
"bad DNS policy": {
DNSPolicy: api.DNSPolicy("invalid"),
RestartPolicy: api.RestartPolicyAlways,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
"bad service account name": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
ServiceAccountName: "invalidName",
@ -3029,7 +3081,7 @@ func TestValidatePodSpec(t *testing.T) {
"bad restart policy": {
RestartPolicy: "UnknowPolicy",
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
"with hostNetwork hostPort not equal to containerPort": {
Containers: []api.Container{
@ -3044,7 +3096,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
"bad supplementalGroups large than math.MaxInt32": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
HostNetwork: false,
SupplementalGroups: []int64{maxID, 1234},
@ -3053,7 +3105,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
"bad supplementalGroups less than 0": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
HostNetwork: false,
SupplementalGroups: []int64{minID, 1234},
@ -3062,7 +3114,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
"bad runAsUser large than math.MaxInt32": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
HostNetwork: false,
RunAsUser: &maxID,
@ -3071,7 +3123,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
"bad runAsUser less than 0": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
HostNetwork: false,
RunAsUser: &minID,
@ -3080,7 +3132,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
"bad fsGroup large than math.MaxInt32": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
HostNetwork: false,
FSGroup: &maxID,
@ -3089,7 +3141,7 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: api.DNSClusterFirst,
},
"bad fsGroup less than 0": {
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &api.PodSecurityContext{
HostNetwork: false,
FSGroup: &minID,
@ -3101,7 +3153,7 @@ func TestValidatePodSpec(t *testing.T) {
Volumes: []api.Volume{
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
NodeSelector: map[string]string{
"key": "value",
@ -3113,7 +3165,7 @@ func TestValidatePodSpec(t *testing.T) {
"bad nodeName": {
NodeName: "node name",
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -3128,7 +3180,7 @@ func TestValidatePodSpec(t *testing.T) {
func TestValidatePod(t *testing.T) {
validPodSpec := func(affinity *api.Affinity) api.PodSpec {
spec := api.PodSpec{
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
}
@ -3143,7 +3195,7 @@ func TestValidatePod(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: api.PodSpec{
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -3154,7 +3206,7 @@ func TestValidatePod(t *testing.T) {
Volumes: []api.Volume{
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
NodeSelector: map[string]string{
@ -3446,8 +3498,8 @@ func TestValidatePod(t *testing.T) {
},
},
Spec: api.PodSpec{
InitContainers: []api.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
InitContainers: []api.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -3489,9 +3541,10 @@ func TestValidatePod(t *testing.T) {
api.OpaqueIntResourceName("A"): resource.MustParse("20"),
},
},
TerminationMessagePolicy: "File",
},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -3499,7 +3552,7 @@ func TestValidatePod(t *testing.T) {
{ // valid opaque integer resources for regular container
ObjectMeta: metav1.ObjectMeta{Name: "valid-opaque-int", Namespace: "ns"},
Spec: api.PodSpec{
InitContainers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
InitContainers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
Containers: []api.Container{
{
Name: "valid-opaque-int",
@ -3513,6 +3566,7 @@ func TestValidatePod(t *testing.T) {
api.OpaqueIntResourceName("A"): resource.MustParse("20"),
},
},
TerminationMessagePolicy: "File",
},
},
RestartPolicy: api.RestartPolicyAlways,
@ -3532,7 +3586,7 @@ func TestValidatePod(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
"bad namespace": {
@ -3540,7 +3594,7 @@ func TestValidatePod(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
"bad spec": {
@ -3560,7 +3614,7 @@ func TestValidatePod(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
"invalid node selector requirement in node affinity, operator can't be null": {
@ -3575,7 +3629,6 @@ func TestValidatePod(t *testing.T) {
{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "key1",
},
},
@ -3916,8 +3969,8 @@ func TestValidatePod(t *testing.T) {
},
},
Spec: api.PodSpec{
InitContainers: []api.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
InitContainers: []api.Container{{Name: "init-ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -4039,7 +4092,7 @@ func TestValidatePod(t *testing.T) {
},
},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -4084,7 +4137,7 @@ func TestValidatePod(t *testing.T) {
},
},
},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
@ -5179,7 +5232,7 @@ func TestValidateReplicationControllerStatusUpdate(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
}
@ -5262,7 +5315,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
}
@ -5274,7 +5327,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
},
},
@ -5425,7 +5478,7 @@ func TestValidateReplicationController(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
}
@ -5438,7 +5491,7 @@ func TestValidateReplicationController(t *testing.T) {
Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
}
@ -5582,7 +5635,7 @@ func TestValidateReplicationController(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
@ -5601,7 +5654,7 @@ func TestValidateReplicationController(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyNever,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,

View File

@ -40,7 +40,7 @@ func getValidPodTemplateSpecForManual(selector *metav1.LabelSelector) api.PodTem
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
}
}
@ -59,7 +59,7 @@ func getValidPodTemplateSpecForGenerated(selector *metav1.LabelSelector) api.Pod
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
}
}
@ -165,7 +165,7 @@ func TestValidateJob(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
},
@ -186,7 +186,7 @@ func TestValidateJob(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
},
@ -207,7 +207,7 @@ func TestValidateJob(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
},
@ -518,7 +518,7 @@ func TestValidateCronJob(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
},

View File

@ -265,25 +265,25 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
validPodSpecAbc := api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
}
validPodSpecDef := api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
}
validPodSpecNodeSelector := api.PodSpec{
NodeSelector: validSelector,
NodeName: "xyz",
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
}
validPodSpecVolume := api.PodSpec{
Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
}
validPodTemplateAbc := api.PodTemplate{
@ -514,7 +514,7 @@ func TestValidateDaemonSet(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
}
@ -642,7 +642,7 @@ func TestValidateDaemonSet(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
@ -661,7 +661,7 @@ func TestValidateDaemonSet(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyNever,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
@ -725,9 +725,10 @@ func validDeployment() *extensions.Deployment {
DNSPolicy: api.DNSDefault,
Containers: []api.Container{
{
Name: "nginx",
Image: "image",
ImagePullPolicy: api.PullNever,
Name: "nginx",
Image: "image",
ImagePullPolicy: api.PullNever,
TerminationMessagePolicy: api.TerminationMessageReadFile,
},
},
},
@ -1217,7 +1218,7 @@ func TestValidateReplicaSetStatusUpdate(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
}
@ -1300,7 +1301,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
}
@ -1312,7 +1313,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
},
},
@ -1463,7 +1464,7 @@ func TestValidateReplicaSet(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
}
@ -1476,7 +1477,7 @@ func TestValidateReplicaSet(t *testing.T) {
Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
}
@ -1620,7 +1621,7 @@ func TestValidateReplicaSet(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
ObjectMeta: metav1.ObjectMeta{
Labels: validLabels,
@ -1639,7 +1640,7 @@ func TestValidateReplicaSet(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyNever,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
ObjectMeta: metav1.ObjectMeta{
Labels: validLabels,

View File

@ -1178,7 +1178,14 @@ var OpenAPIDefinitions *openapi.OpenAPIDefinitions = &openapi.OpenAPIDefinitions
},
"terminationMessagePath": {
SchemaProps: spec.SchemaProps{
Description: "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Defaults to /dev/termination-log. Cannot be updated.",
Description: "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.",
Type: []string{"string"},
Format: "",
},
},
"terminationMessagePolicy": {
SchemaProps: spec.SchemaProps{
Description: "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.",
Type: []string{"string"},
Format: "",
},

View File

@ -46,11 +46,12 @@ func TestDecodeSinglePod(t *testing.T) {
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
Containers: []v1.Container{{
Name: "image",
Image: "test/image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePath: "/dev/termination-log",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
Name: "image",
Image: "test/image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
@ -107,11 +108,13 @@ func TestDecodePodList(t *testing.T) {
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
Containers: []v1.Container{{
Name: "image",
Image: "test/image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePath: "/dev/termination-log",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
Name: "image",
Image: "test/image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,

View File

@ -69,10 +69,11 @@ func CreateValidPod(name, namespace string) *v1.Pod {
DNSPolicy: v1.DNSClusterFirst,
Containers: []v1.Container{
{
Name: "ctr",
Image: "image",
ImagePullPolicy: "IfNotPresent",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
Name: "ctr",
Image: "image",
ImagePullPolicy: "IfNotPresent",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
TerminationMessagePolicy: v1.TerminationMessageReadFile,
},
},
},
@ -204,7 +205,7 @@ func TestNewPodAddedSnapshotAndUpdates(t *testing.T) {
// container updates are separated as UPDATE
pod := *podUpdate.Pods[0]
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, &pod))
}
@ -222,7 +223,7 @@ func TestNewPodAddedSnapshot(t *testing.T) {
// container updates are separated as UPDATE
pod := *podUpdate.Pods[0]
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, &pod))
}
@ -240,7 +241,7 @@ func TestNewPodAddedUpdatedRemoved(t *testing.T) {
// an kubetypes.ADD should be converted to kubetypes.UPDATE
pod := CreateValidPod("foo", "new")
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, pod)
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
@ -282,7 +283,7 @@ func TestNewPodAddedUpdatedSet(t *testing.T) {
// should be converted to an kubetypes.ADD, kubetypes.REMOVE, and kubetypes.UPDATE
pod := CreateValidPod("foo2", "new")
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod, CreateValidPod("foo3", "new"), CreateValidPod("foo4", "new"))
channel <- podUpdate
expectPodUpdate(t, ch,

View File

@ -210,9 +210,11 @@ func getTestCases(hostname types.NodeName) []*testCase {
Containers: []v1.Container{{
Name: "image",
Image: "test/image",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},

View File

@ -145,7 +145,7 @@ func TestExtractPodsFromHTTP(t *testing.T) {
},
Spec: v1.PodSpec{
NodeName: string(nodeName),
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways, TerminationMessagePolicy: v1.TerminationMessageReadFile}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
@ -174,8 +174,9 @@ func TestExtractPodsFromHTTP(t *testing.T) {
Containers: []v1.Container{{
Name: "1",
Image: "foo",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
},
Status: v1.PodStatus{
@ -198,7 +199,7 @@ func TestExtractPodsFromHTTP(t *testing.T) {
},
Spec: v1.PodSpec{
NodeName: nodeName,
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways, TerminationMessagePolicy: v1.TerminationMessageReadFile}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
@ -213,7 +214,7 @@ func TestExtractPodsFromHTTP(t *testing.T) {
},
Spec: v1.PodSpec{
NodeName: nodeName,
Containers: []v1.Container{{Name: "2", Image: "bar:bartag", ImagePullPolicy: ""}},
Containers: []v1.Container{{Name: "2", Image: "bar:bartag", ImagePullPolicy: "", TerminationMessagePolicy: v1.TerminationMessageReadFile}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
@ -244,8 +245,9 @@ func TestExtractPodsFromHTTP(t *testing.T) {
Containers: []v1.Container{{
Name: "1",
Image: "foo",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
},
Status: v1.PodStatus{
@ -271,8 +273,9 @@ func TestExtractPodsFromHTTP(t *testing.T) {
Containers: []v1.Container{{
Name: "2",
Image: "bar:bartag",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
},
Status: v1.PodStatus{

View File

@ -32,6 +32,7 @@ type OSInterface interface {
Remove(path string) error
RemoveAll(path string) error
Create(path string) (*os.File, error)
Chmod(path string, perm os.FileMode) error
Hostname() (name string, err error)
Chtimes(path string, atime time.Time, mtime time.Time) error
Pipe() (r *os.File, w *os.File, err error)
@ -73,6 +74,12 @@ func (RealOS) Create(path string) (*os.File, error) {
return os.Create(path)
}
// Chmod will change the permissions on the specified path or return
// an error.
func (RealOS) Chmod(path string, perm os.FileMode) error {
return os.Chmod(path, perm)
}
// Hostname will call os.Hostname to return the hostname.
func (RealOS) Hostname() (name string, err error) {
return os.Hostname()

View File

@ -626,3 +626,19 @@ func (s SortContainerStatusesByCreationTime) Swap(i, j int) { s[i], s[j] = s[j],
func (s SortContainerStatusesByCreationTime) Less(i, j int) bool {
return s[i].CreatedAt.Before(s[j].CreatedAt)
}
const (
// MaxPodTerminationMessageLogLength is the maximum bytes any one pod may have written
// as termination message output across all containers. Containers will be evenly truncated
// until output is below this limit.
MaxPodTerminationMessageLogLength = 1024 * 12
// MaxContainerTerminationMessageLength is the upper bound any one container may write to
// its termination message path. Contents above this length will be truncated.
MaxContainerTerminationMessageLength = 1024 * 4
// MaxContainerTerminationMessageLogLength is the maximum bytes any one container will
// have written to its termination message when the message is read from the logs.
MaxContainerTerminationMessageLogLength = 1024 * 2
// MaxContainerTerminationMessageLogLines is the maximum number of previous lines of
// log output that the termination message can contain.
MaxContainerTerminationMessageLogLines = 80
)

View File

@ -83,6 +83,11 @@ func (FakeOS) Create(path string) (*os.File, error) {
return nil, nil
}
// Chmod is a fake call that returns nil.
func (FakeOS) Chmod(path string, perm os.FileMode) error {
return nil
}
// Hostname is a fake call that returns nil.
func (f *FakeOS) Hostname() (name string, err error) {
return f.HostName, nil

View File

@ -52,8 +52,10 @@ go_library(
"//pkg/util/procfs:go_default_library",
"//pkg/util/selinux:go_default_library",
"//pkg/util/strings:go_default_library",
"//pkg/util/tail:go_default_library",
"//pkg/util/term:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor:github.com/armon/circbuf",
"//vendor:github.com/docker/distribution/digest",
"//vendor:github.com/docker/distribution/reference",
"//vendor:github.com/docker/docker/pkg/jsonmessage",

View File

@ -33,6 +33,7 @@ import (
"sync"
"time"
"github.com/armon/circbuf"
dockertypes "github.com/docker/engine-api/types"
dockercontainer "github.com/docker/engine-api/types/container"
dockerstrslice "github.com/docker/engine-api/types/strslice"
@ -70,6 +71,7 @@ import (
"k8s.io/kubernetes/pkg/util/procfs"
"k8s.io/kubernetes/pkg/util/selinux"
utilstrings "k8s.io/kubernetes/pkg/util/strings"
"k8s.io/kubernetes/pkg/util/tail"
"k8s.io/kubernetes/pkg/util/term"
utilversion "k8s.io/kubernetes/pkg/util/version"
)
@ -482,19 +484,12 @@ func (dm *DockerManager) inspectContainer(id string, podName, podNamespace strin
startedAt = createdAt
}
terminationMessagePath := containerInfo.TerminationMessagePath
if terminationMessagePath != "" {
for _, mount := range iResult.Mounts {
if mount.Destination == terminationMessagePath {
path := mount.Source
if data, err := ioutil.ReadFile(path); err != nil {
message = fmt.Sprintf("Error on reading termination-log %s: %v", path, err)
} else {
message = string(data)
}
}
}
// retrieve the termination message from logs, file, or file with fallback to logs in case of failure
fallbackToLogs := containerInfo.TerminationMessagePolicy == v1.TerminationMessageFallbackToLogsOnError && (iResult.State.ExitCode != 0 || iResult.State.OOMKilled)
if msg := getTerminationMessage(dm.c, iResult, containerInfo.TerminationMessagePath, fallbackToLogs); len(msg) > 0 {
message = msg
}
status.State = kubecontainer.ContainerStateExited
status.Message = message
status.Reason = reason
@ -508,6 +503,49 @@ func (dm *DockerManager) inspectContainer(id string, podName, podNamespace strin
return &status, "", nil
}
func getTerminationMessage(c DockerInterface, iResult *dockertypes.ContainerJSON, terminationMessagePath string, fallbackToLogs bool) string {
if len(terminationMessagePath) != 0 {
for _, mount := range iResult.Mounts {
if mount.Destination != terminationMessagePath {
continue
}
path := mount.Source
data, _, err := tail.ReadAtMost(path, kubecontainer.MaxContainerTerminationMessageLength)
if err != nil {
return fmt.Sprintf("Error on reading termination log %s: %v", path, err)
}
if !fallbackToLogs || len(data) != 0 {
return string(data)
}
}
}
if !fallbackToLogs {
return ""
}
return readLastStringFromContainerLogs(c, iResult.Name)
}
// readLastStringFromContainerLogs attempts to a certain amount from the end of the logs for containerName.
// It will attempt to avoid reading excessive logs from the server, which may result in underestimating the amount
// of logs to fetch (such that the length of the response message is < max).
func readLastStringFromContainerLogs(c DockerInterface, containerName string) string {
logOptions := dockertypes.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
}
buf, _ := circbuf.NewBuffer(kubecontainer.MaxContainerTerminationMessageLogLength)
streamOptions := StreamOptions{
ErrorStream: buf,
OutputStream: buf,
}
logOptions.Tail = strconv.FormatInt(kubecontainer.MaxContainerTerminationMessageLogLines, 10)
if err := c.Logs(containerName, logOptions, streamOptions); err != nil {
return fmt.Sprintf("Error on reading termination message from logs: %v", err)
}
return buf.String()
}
// makeEnvList converts EnvVar list to a list of strings, in the form of
// '<key>=<value>', which can be understood by docker.
func makeEnvList(envs []kubecontainer.EnvVar) (result []string) {
@ -672,17 +710,24 @@ func (dm *DockerManager) runContainer(
fs, err := os.Create(containerLogPath)
if err != nil {
// TODO: Clean up the previously created dir? return the error?
glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err)
utilruntime.HandleError(fmt.Errorf("error creating termination-log file %q: %v", containerLogPath, err))
} else {
fs.Close() // Close immediately; we're just doing a `touch` here
b := fmt.Sprintf("%s:%s", containerLogPath, container.TerminationMessagePath)
// Chmod is needed because ioutil.WriteFile() ends up calling
// open(2) to create the file, so the final mode used is "mode &
// ~umask". But we want to make sure the specified mode is used
// in the file no matter what the umask is.
if err := os.Chmod(containerLogPath, 0666); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to set termination-log file permissions %q: %v", containerLogPath, err))
}
// Have docker relabel the termination log path if SELinux is
// enabled.
b := fmt.Sprintf("%s:%s", containerLogPath, container.TerminationMessagePath)
if selinux.SELinuxEnabled() {
b += ":Z"
}
binds = append(binds, b)
}
}

View File

@ -39,11 +39,12 @@ const (
kubernetesPodDeletionGracePeriodLabel = "io.kubernetes.pod.deletionGracePeriod"
kubernetesPodTerminationGracePeriodLabel = "io.kubernetes.pod.terminationGracePeriod"
kubernetesContainerHashLabel = "io.kubernetes.container.hash"
kubernetesContainerRestartCountLabel = "io.kubernetes.container.restartCount"
kubernetesContainerTerminationMessagePathLabel = "io.kubernetes.container.terminationMessagePath"
kubernetesContainerPreStopHandlerLabel = "io.kubernetes.container.preStopHandler"
kubernetesContainerPortsLabel = "io.kubernetes.container.ports" // Added in 1.4
kubernetesContainerHashLabel = "io.kubernetes.container.hash"
kubernetesContainerRestartCountLabel = "io.kubernetes.container.restartCount"
kubernetesContainerTerminationMessagePathLabel = "io.kubernetes.container.terminationMessagePath"
kubernetesContainerTerminationMessagePolicyLabel = "io.kubernetes.container.terminationMessagePolicy"
kubernetesContainerPreStopHandlerLabel = "io.kubernetes.container.preStopHandler"
kubernetesContainerPortsLabel = "io.kubernetes.container.ports" // Added in 1.4
// TODO(random-liu): Keep this for old containers, remove this when we drop support for v1.1.
kubernetesPodLabel = "io.kubernetes.pod.data"
@ -63,6 +64,7 @@ type labelledContainerInfo struct {
Hash string
RestartCount int
TerminationMessagePath string
TerminationMessagePolicy v1.TerminationMessagePolicy
PreStopHandler *v1.Handler
Ports []v1.ContainerPort
}
@ -83,6 +85,7 @@ func newLabels(container *v1.Container, pod *v1.Pod, restartCount int, enableCus
labels[kubernetesContainerHashLabel] = strconv.FormatUint(kubecontainer.HashContainer(container), 16)
labels[kubernetesContainerRestartCountLabel] = strconv.Itoa(restartCount)
labels[kubernetesContainerTerminationMessagePathLabel] = container.TerminationMessagePath
labels[kubernetesContainerTerminationMessagePolicyLabel] = string(container.TerminationMessagePolicy)
if container.Lifecycle != nil && container.Lifecycle.PreStop != nil {
// Using json enconding so that the PreStop handler object is readable after writing as a label
rawPreStop, err := json.Marshal(container.Lifecycle.PreStop)
@ -118,7 +121,8 @@ func getContainerInfoFromLabel(labels map[string]string) *labelledContainerInfo
PodUID: kubetypes.UID(getStringValueFromLabel(labels, types.KubernetesPodUIDLabel)),
Name: getStringValueFromLabel(labels, types.KubernetesContainerNameLabel),
Hash: getStringValueFromLabel(labels, kubernetesContainerHashLabel),
TerminationMessagePath: getStringValueFromLabel(labels, kubernetesContainerTerminationMessagePathLabel),
TerminationMessagePath: getStringValueFromLabel(labels, kubernetesContainerTerminationMessagePathLabel),
TerminationMessagePolicy: v1.TerminationMessagePolicy(getStringValueFromLabel(labels, kubernetesContainerTerminationMessagePolicyLabel)),
}
if containerInfo.RestartCount, err = getIntValueFromLabel(labels, kubernetesContainerRestartCountLabel); err != nil {
logError(containerInfo, kubernetesContainerRestartCountLabel, err)

View File

@ -47,7 +47,9 @@ go_library(
"//pkg/securitycontext:go_default_library",
"//pkg/util/parsers:go_default_library",
"//pkg/util/selinux:go_default_library",
"//pkg/util/tail:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor:github.com/armon/circbuf",
"//vendor:github.com/docker/docker/pkg/jsonlog",
"//vendor:github.com/fsnotify/fsnotify",
"//vendor:github.com/golang/glog",

View File

@ -19,7 +19,6 @@ package kuberuntime
import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/url"
"os"
@ -28,7 +27,9 @@ import (
"sync"
"time"
"github.com/armon/circbuf"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubetypes "k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@ -41,6 +42,7 @@ import (
"k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/kubelet/util/format"
"k8s.io/kubernetes/pkg/util/selinux"
"k8s.io/kubernetes/pkg/util/tail"
)
// startContainer starts a container and returns a message indicates why it is failed on error.
@ -272,9 +274,18 @@ func (m *kubeGenericRuntimeManager) makeMounts(opts *kubecontainer.RunContainerO
containerLogPath := filepath.Join(opts.PodContainerDir, cid)
fs, err := m.osInterface.Create(containerLogPath)
if err != nil {
glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err)
utilruntime.HandleError(fmt.Errorf("error on creating termination-log file %q: %v", containerLogPath, err))
} else {
fs.Close()
// Chmod is needed because ioutil.WriteFile() ends up calling
// open(2) to create the file, so the final mode used is "mode &
// ~umask". But we want to make sure the specified mode is used
// in the file no matter what the umask is.
if err := m.osInterface.Chmod(containerLogPath, 0666); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to set termination-log file permissions %q: %v", containerLogPath, err))
}
selinuxRelabel := selinux.SELinuxEnabled()
volumeMounts = append(volumeMounts, &runtimeapi.Mount{
HostPath: containerLogPath,
@ -325,29 +336,36 @@ func makeUID() string {
return fmt.Sprintf("%08x", rand.Uint32())
}
// getTerminationMessage gets termination message of the container.
func getTerminationMessage(status *runtimeapi.ContainerStatus, kubeStatus *kubecontainer.ContainerStatus, terminationMessagePath string) string {
message := ""
if !kubeStatus.FinishedAt.IsZero() || kubeStatus.ExitCode != 0 {
if terminationMessagePath == "" {
return ""
}
// getTerminationMessage looks on the filesystem for the provided termination message path, returning a limited
// amount of those bytes, or returns true if the logs should be checked.
func getTerminationMessage(status *runtimeapi.ContainerStatus, terminationMessagePath string, fallbackToLogs bool) (string, bool) {
if len(terminationMessagePath) != 0 {
for _, mount := range status.Mounts {
if mount.ContainerPath == terminationMessagePath {
path := mount.HostPath
if data, err := ioutil.ReadFile(path); err != nil {
message = fmt.Sprintf("Error on reading termination-log %s: %v", path, err)
} else {
message = string(data)
}
break
if mount.ContainerPath != terminationMessagePath {
continue
}
path := mount.HostPath
data, _, err := tail.ReadAtMost(path, kubecontainer.MaxContainerTerminationMessageLength)
if err != nil {
return fmt.Sprintf("Error on reading termination log %s: %v", path, err), false
}
if !fallbackToLogs || len(data) != 0 {
return string(data), false
}
}
}
return "", fallbackToLogs
}
return message
// readLastStringFromContainerLogs attempts to read up to the max log length from the end of the CRI log represented
// by path. It reads up to max log lines.
func readLastStringFromContainerLogs(path string) string {
value := int64(kubecontainer.MaxContainerTerminationMessageLogLines)
buf, _ := circbuf.NewBuffer(kubecontainer.MaxContainerTerminationMessageLogLength)
if err := ReadLogs(path, &v1.PodLogOptions{TailLines: &value}, buf, buf); err != nil {
return fmt.Sprintf("Error on reading termination message from logs: %v", err)
}
return buf.String()
}
// getPodContainerStatuses gets all containers' statuses for the pod.
@ -393,13 +411,19 @@ func (m *kubeGenericRuntimeManager) getPodContainerStatuses(uid kubetypes.UID, n
cStatus.Message = status.Message
cStatus.ExitCode = int(status.ExitCode)
cStatus.FinishedAt = time.Unix(0, status.FinishedAt)
fallbackToLogs := annotatedInfo.TerminationMessagePolicy == v1.TerminationMessageFallbackToLogsOnError && (cStatus.ExitCode != 0 || cStatus.Reason == "OOMKilled")
tMessage, checkLogs := getTerminationMessage(status, annotatedInfo.TerminationMessagePath, fallbackToLogs)
if checkLogs {
path := buildFullContainerLogsPath(uid, labeledInfo.ContainerName, annotatedInfo.RestartCount)
tMessage = readLastStringFromContainerLogs(path)
}
// Use the termination message written by the application is not empty
if len(tMessage) != 0 {
cStatus.Message = tMessage
}
}
tMessage := getTerminationMessage(status, cStatus, annotatedInfo.TerminationMessagePath)
// Use the termination message written by the application is not empty
if len(tMessage) != 0 {
cStatus.Message = tMessage
}
statuses[i] = cStatus
}

View File

@ -32,6 +32,7 @@ import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/util/tail"
)
// Notice that the current kuberuntime logs implementation doesn't handle
@ -120,7 +121,7 @@ func ReadLogs(path string, apiOpts *v1.PodLogOptions, stdout, stderr io.Writer)
opts := newLogOptions(apiOpts, time.Now())
// Search start point based on tail line.
start, err := tail(f, opts.tail)
start, err := tail.FindTailLineStartIndex(f, opts.tail)
if err != nil {
return fmt.Errorf("failed to tail %d lines of log file %q: %v", opts.tail, path, err)
}
@ -347,40 +348,3 @@ func (w *logWriter) write(msg *logMessage) error {
}
return nil
}
// tail returns the start of last nth line.
// * If n < 0, return the beginning of the file.
// * If n >= 0, return the beginning of last nth line.
// Notice that if the last line is incomplete (no end-of-line), it will not be counted
// as one line.
func tail(f io.ReadSeeker, n int64) (int64, error) {
if n < 0 {
return 0, nil
}
size, err := f.Seek(0, os.SEEK_END)
if err != nil {
return 0, err
}
var left, cnt int64
buf := make([]byte, blockSize)
for right := size; right > 0 && cnt <= n; right -= blockSize {
left = right - blockSize
if left < 0 {
left = 0
buf = make([]byte, right)
}
if _, err := f.Seek(left, os.SEEK_SET); err != nil {
return 0, err
}
if _, err := f.Read(buf); err != nil {
return 0, err
}
cnt += int64(bytes.Count(buf, eol))
}
for ; cnt > n; cnt-- {
idx := bytes.Index(buf, eol) + 1
buf = buf[idx:]
left += int64(idx)
}
return left, nil
}

View File

@ -18,7 +18,6 @@ package kuberuntime
import (
"bytes"
"strings"
"testing"
"time"
@ -242,28 +241,3 @@ func TestWriteLogsWithBytesLimit(t *testing.T) {
assert.Equal(t, test.expectStderr, stderrBuf.String())
}
}
func TestTail(t *testing.T) {
line := strings.Repeat("a", blockSize)
testBytes := []byte(line + "\n" +
line + "\n" +
line + "\n" +
line + "\n" +
line[blockSize/2:]) // incomplete line
for c, test := range []struct {
n int64
start int64
}{
{n: -1, start: 0},
{n: 0, start: int64(len(line)+1) * 4},
{n: 1, start: int64(len(line)+1) * 3},
{n: 9999, start: 0},
} {
t.Logf("TestCase #%d: %+v", c, test)
r := bytes.NewReader(testBytes)
s, err := tail(r, test.n)
assert.NoError(t, err)
assert.Equal(t, s, test.start)
}
}

View File

@ -33,11 +33,12 @@ const (
podDeletionGracePeriodLabel = "io.kubernetes.pod.deletionGracePeriod"
podTerminationGracePeriodLabel = "io.kubernetes.pod.terminationGracePeriod"
containerHashLabel = "io.kubernetes.container.hash"
containerRestartCountLabel = "io.kubernetes.container.restartCount"
containerTerminationMessagePathLabel = "io.kubernetes.container.terminationMessagePath"
containerPreStopHandlerLabel = "io.kubernetes.container.preStopHandler"
containerPortsLabel = "io.kubernetes.container.ports"
containerHashLabel = "io.kubernetes.container.hash"
containerRestartCountLabel = "io.kubernetes.container.restartCount"
containerTerminationMessagePathLabel = "io.kubernetes.container.terminationMessagePath"
containerTerminationMessagePolicyLabel = "io.kubernetes.container.terminationMessagePolicy"
containerPreStopHandlerLabel = "io.kubernetes.container.preStopHandler"
containerPortsLabel = "io.kubernetes.container.ports"
// kubernetesManagedLabel is used to distinguish whether a container/sandbox is managed by kubelet or not
kubernetesManagedLabel = "io.kubernetes.managed"
@ -69,6 +70,7 @@ type annotatedContainerInfo struct {
PodDeletionGracePeriod *int64
PodTerminationGracePeriod *int64
TerminationMessagePath string
TerminationMessagePolicy v1.TerminationMessagePolicy
PreStopHandler *v1.Handler
ContainerPorts []v1.ContainerPort
}
@ -113,6 +115,7 @@ func newContainerAnnotations(container *v1.Container, pod *v1.Pod, restartCount
annotations[containerHashLabel] = strconv.FormatUint(kubecontainer.HashContainer(container), 16)
annotations[containerRestartCountLabel] = strconv.Itoa(restartCount)
annotations[containerTerminationMessagePathLabel] = container.TerminationMessagePath
annotations[containerTerminationMessagePolicyLabel] = string(container.TerminationMessagePolicy)
if pod.DeletionGracePeriodSeconds != nil {
annotations[podDeletionGracePeriodLabel] = strconv.FormatInt(*pod.DeletionGracePeriodSeconds, 10)
@ -192,7 +195,8 @@ func isManagedByKubelet(labels map[string]string) bool {
func getContainerInfoFromAnnotations(annotations map[string]string) *annotatedContainerInfo {
var err error
containerInfo := &annotatedContainerInfo{
TerminationMessagePath: getStringValueFromLabel(annotations, containerTerminationMessagePathLabel),
TerminationMessagePath: getStringValueFromLabel(annotations, containerTerminationMessagePathLabel),
TerminationMessagePolicy: v1.TerminationMessagePolicy(getStringValueFromLabel(annotations, containerTerminationMessagePolicyLabel)),
}
if containerInfo.Hash, err = getUint64ValueFromLabel(annotations, containerHashLabel); err != nil {

View File

@ -513,6 +513,10 @@ func (m *manager) needsReconcile(uid types.UID, status v1.PodStatus) bool {
// kubelet temporarily.
// TODO(random-liu): Remove timestamp related logic after apiserver supports nanosecond or makes it consistent.
func normalizeStatus(pod *v1.Pod, status *v1.PodStatus) *v1.PodStatus {
bytesPerStatus := kubecontainer.MaxPodTerminationMessageLogLength
if containers := len(pod.Spec.Containers) + len(pod.Spec.InitContainers); containers > 0 {
bytesPerStatus = bytesPerStatus / containers
}
normalizeTimeStamp := func(t *metav1.Time) {
*t = t.Rfc3339Copy()
}
@ -523,6 +527,9 @@ func normalizeStatus(pod *v1.Pod, status *v1.PodStatus) *v1.PodStatus {
if c.Terminated != nil {
normalizeTimeStamp(&c.Terminated.StartedAt)
normalizeTimeStamp(&c.Terminated.FinishedAt)
if len(c.Terminated.Message) > bytesPerStatus {
c.Terminated.Message = c.Terminated.Message[:bytesPerStatus]
}
}
}

View File

@ -20,21 +20,21 @@ import (
"fmt"
"math/rand"
"strconv"
"strings"
"testing"
"time"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing"
@ -480,6 +480,40 @@ func TestStatusEquality(t *testing.T) {
}
}
func TestStatusNormalizationEnforcesMaxBytes(t *testing.T) {
pod := v1.Pod{
Spec: v1.PodSpec{},
}
containerStatus := []v1.ContainerStatus{}
for i := 0; i < 48; i++ {
s := v1.ContainerStatus{
Name: fmt.Sprintf("container%d", i),
LastTerminationState: v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
Message: strings.Repeat("abcdefgh", int(24+i%3)),
},
},
}
containerStatus = append(containerStatus, s)
}
podStatus := v1.PodStatus{
InitContainerStatuses: containerStatus[:24],
ContainerStatuses: containerStatus[24:],
}
result := normalizeStatus(&pod, &podStatus)
count := 0
for _, s := range result.InitContainerStatuses {
l := len(s.LastTerminationState.Terminated.Message)
if l < 192 || l > 256 {
t.Errorf("container message had length %d", l)
}
count += l
}
if count > kubecontainer.MaxPodTerminationMessageLogLength {
t.Errorf("message length not truncated")
}
}
func TestStaticPod(t *testing.T) {
staticPod := getTestPod()
staticPod.Annotations = map[string]string{kubetypes.ConfigSourceAnnotationKey: "file"}

View File

@ -45,7 +45,7 @@ func TestCronJobStrategy(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
}
scheduledJob := &batch.CronJob{
@ -106,7 +106,7 @@ func TestCronJobStatusStrategy(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
}
oldSchedule := "* * * * ?"

View File

@ -64,9 +64,10 @@ func validNewJob() *batch.Job {
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePolicy: api.TerminationMessageReadFile,
},
},
RestartPolicy: api.RestartPolicyOnFailure,

View File

@ -54,7 +54,7 @@ func TestJobStrategy(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
}
job := &batch.Job{
@ -111,7 +111,7 @@ func TestJobStrategyWithGeneration(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
}
job := &batch.Job{
@ -170,7 +170,7 @@ func TestJobStatusStrategy(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
}
oldParallelism := int32(10)

View File

@ -77,9 +77,10 @@ func validNewController() *api.ReplicationController {
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePolicy: api.TerminationMessageReadFile,
},
},
RestartPolicy: api.RestartPolicyAlways,

View File

@ -44,7 +44,7 @@ func TestControllerStrategy(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
}
@ -102,7 +102,7 @@ func TestControllerStatusStrategy(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
}
@ -163,7 +163,7 @@ func TestValidateUpdate(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
},
},
}

View File

@ -68,8 +68,9 @@ func validNewPod() *api.Pod {
Image: "test",
ImagePullPolicy: api.PullAlways,
TerminationMessagePath: api.TerminationMessagePathDefault,
SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(),
TerminationMessagePath: api.TerminationMessagePathDefault,
TerminationMessagePolicy: api.TerminationMessageReadFile,
SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(),
},
},
SecurityContext: &api.PodSecurityContext{},
@ -678,11 +679,12 @@ func TestEtcdUpdateScheduled(t *testing.T) {
Spec: api.PodSpec{
NodeName: "machine",
Containers: []api.Container{{
Name: "foobar",
Image: "foo:v2",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePath: api.TerminationMessagePathDefault,
SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(),
Name: "foobar",
Image: "foo:v2",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePath: api.TerminationMessagePathDefault,
TerminationMessagePolicy: api.TerminationMessageReadFile,
SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(),
}},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
@ -772,6 +774,7 @@ func TestEtcdUpdateStatus(t *testing.T) {
expected.Spec.DNSPolicy = api.DNSClusterFirst
expected.Spec.Containers[0].ImagePullPolicy = api.PullIfNotPresent
expected.Spec.Containers[0].TerminationMessagePath = api.TerminationMessagePathDefault
expected.Spec.Containers[0].TerminationMessagePolicy = api.TerminationMessageReadFile
expected.Labels = podIn.Labels
expected.Status = podIn.Status

View File

@ -59,7 +59,8 @@ func validNewPodTemplate(name string) *api.PodTemplate {
Image: "test",
ImagePullPolicy: api.PullAlways,
TerminationMessagePath: api.TerminationMessagePathDefault,
TerminationMessagePath: api.TerminationMessagePathDefault,
TerminationMessagePolicy: api.TerminationMessageReadFile,
},
},
},

View File

@ -57,9 +57,10 @@ func newValidDaemonSet() *extensions.DaemonSet {
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePolicy: api.TerminationMessageReadFile,
},
},
RestartPolicy: api.RestartPolicyAlways,

View File

@ -71,9 +71,10 @@ func validNewDeployment() *extensions.Deployment {
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePolicy: api.TerminationMessageReadFile,
},
},
RestartPolicy: api.RestartPolicyAlways,

View File

@ -69,9 +69,10 @@ func validNewReplicaSet() *extensions.ReplicaSet {
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePolicy: api.TerminationMessageReadFile,
},
},
RestartPolicy: api.RestartPolicyAlways,

View File

@ -43,7 +43,7 @@ func TestReplicaSetStrategy(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
},
},
}

View File

@ -97,6 +97,7 @@ filegroup(
"//pkg/util/strings:all-srcs",
"//pkg/util/sysctl:all-srcs",
"//pkg/util/system:all-srcs",
"//pkg/util/tail:all-srcs",
"//pkg/util/taints:all-srcs",
"//pkg/util/term:all-srcs",
"//pkg/util/threading:all-srcs",

35
pkg/util/tail/BUILD Normal file
View File

@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["tail_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["tail.go"],
tags = ["automanaged"],
)

99
pkg/util/tail/tail.go Normal file
View File

@ -0,0 +1,99 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tail
import (
"bytes"
"io"
"io/ioutil"
"os"
)
const (
// blockSize is the block size used in tail.
blockSize = 1024
)
var (
// eol is the end-of-line sign in the log.
eol = []byte{'\n'}
)
// ReadAtMost reads at most max bytes from the end of the file identified by path or
// returns an error. It returns true if the file was longer than max. It will
// allocate up to max bytes.
func ReadAtMost(path string, max int64) ([]byte, bool, error) {
f, err := os.Open(path)
if err != nil {
return nil, false, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, false, err
}
size := fi.Size()
if size == 0 {
return nil, false, nil
}
if size < max {
max = size
}
offset, err := f.Seek(-max, os.SEEK_END)
if err != nil {
return nil, false, err
}
data, err := ioutil.ReadAll(f)
return data, offset > 0, err
}
// FindTailLineStartIndex returns the start of last nth line.
// * If n < 0, return the beginning of the file.
// * If n >= 0, return the beginning of last nth line.
// Notice that if the last line is incomplete (no end-of-line), it will not be counted
// as one line.
func FindTailLineStartIndex(f io.ReadSeeker, n int64) (int64, error) {
if n < 0 {
return 0, nil
}
size, err := f.Seek(0, os.SEEK_END)
if err != nil {
return 0, err
}
var left, cnt int64
buf := make([]byte, blockSize)
for right := size; right > 0 && cnt <= n; right -= blockSize {
left = right - blockSize
if left < 0 {
left = 0
buf = make([]byte, right)
}
if _, err := f.Seek(left, os.SEEK_SET); err != nil {
return 0, err
}
if _, err := f.Read(buf); err != nil {
return 0, err
}
cnt += int64(bytes.Count(buf, eol))
}
for ; cnt > n; cnt-- {
idx := bytes.Index(buf, eol) + 1
buf = buf[idx:]
left += int64(idx)
}
return left, nil
}

View File

@ -0,0 +1,52 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tail
import (
"bytes"
"strings"
"testing"
)
func TestTail(t *testing.T) {
line := strings.Repeat("a", blockSize)
testBytes := []byte(line + "\n" +
line + "\n" +
line + "\n" +
line + "\n" +
line[blockSize/2:]) // incomplete line
for c, test := range []struct {
n int64
start int64
}{
{n: -1, start: 0},
{n: 0, start: int64(len(line)+1) * 4},
{n: 1, start: int64(len(line)+1) * 3},
{n: 9999, start: 0},
} {
t.Logf("TestCase #%d: %+v", c, test)
r := bytes.NewReader(testBytes)
s, err := FindTailLineStartIndex(r, test.n)
if err != nil {
t.Error(err)
}
if s != test.start {
t.Errorf("%d != %d", s, test.start)
}
}
}

View File

@ -28,6 +28,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
gomegatypes "github.com/onsi/gomega/types"
)
const (
@ -128,46 +129,112 @@ while true; do sleep 1; done
}
})
It("should report termination message if TerminationMessagePath is set [Conformance]", func() {
name := "termination-message-container"
terminationMessage := "DONE"
terminationMessagePath := "/dev/termination-log"
priv := true
c := ConformanceContainer{
PodClient: f.PodClient(),
Container: v1.Container{
rootUser := int64(0)
nonRootUser := int64(10000)
for _, testCase := range []struct {
name string
container v1.Container
phase v1.PodPhase
message gomegatypes.GomegaMatcher
}{
{
name: "if TerminationMessagePath is set [Conformance]",
container: v1.Container{
Image: "gcr.io/google_containers/busybox:1.24",
Name: name,
Command: []string{"/bin/sh", "-c"},
Args: []string{fmt.Sprintf("/bin/echo -n %s > %s", terminationMessage, terminationMessagePath)},
TerminationMessagePath: terminationMessagePath,
Args: []string{"/bin/echo -n DONE > /dev/termination-log"},
TerminationMessagePath: "/dev/termination-log",
SecurityContext: &v1.SecurityContext{
Privileged: &priv,
RunAsUser: &rootUser,
},
},
RestartPolicy: v1.RestartPolicyNever,
}
phase: v1.PodSucceeded,
message: Equal("DONE"),
},
By("create the container")
c.Create()
defer c.Delete()
{
name: "if TerminationMessagePath is set as non-root user and at a non-default path [Conformance]",
container: v1.Container{
Image: "gcr.io/google_containers/busybox:1.24",
Command: []string{"/bin/sh", "-c"},
Args: []string{"/bin/echo -n DONE > /dev/termination-custom-log"},
TerminationMessagePath: "/dev/termination-custom-log",
SecurityContext: &v1.SecurityContext{
RunAsUser: &nonRootUser,
},
},
phase: v1.PodSucceeded,
message: Equal("DONE"),
},
By("wait for the container to succeed")
Eventually(c.GetPhase, retryTimeout, pollInterval).Should(Equal(v1.PodSucceeded))
{
name: "from log output if TerminationMessagePolicy FallbackToLogOnError is set [Conformance]",
container: v1.Container{
Image: "gcr.io/google_containers/busybox:1.24",
Command: []string{"/bin/sh", "-c"},
Args: []string{"/bin/echo -n DONE; /bin/false"},
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError,
},
phase: v1.PodFailed,
message: Equal("DONE\n"),
},
By("get the container status")
status, err := c.GetStatus()
Expect(err).NotTo(HaveOccurred())
{
name: "as empty when pod succeeds and TerminationMessagePolicy FallbackToLogOnError is set",
container: v1.Container{
Image: "gcr.io/google_containers/busybox:1.24",
Command: []string{"/bin/sh", "-c"},
Args: []string{"/bin/echo DONE; /bin/true"},
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError,
},
phase: v1.PodSucceeded,
message: Equal(""),
},
By("the container should be terminated")
Expect(GetContainerState(status.State)).To(Equal(ContainerStateTerminated))
{
name: "from file when pod succeeds and TerminationMessagePolicy FallbackToLogOnError is set [Conformance]",
container: v1.Container{
Image: "gcr.io/google_containers/busybox:1.24",
Command: []string{"/bin/sh", "-c"},
Args: []string{"/bin/echo -n OK > /dev/termination-log; /bin/echo DONE; /bin/true"},
TerminationMessagePath: "/dev/termination-log",
TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError,
},
phase: v1.PodSucceeded,
message: Equal("OK"),
},
} {
It(fmt.Sprintf("should report termination message %s", testCase.name), func() {
testCase.container.Name = "termination-message-container"
c := ConformanceContainer{
PodClient: f.PodClient(),
Container: testCase.container,
RestartPolicy: v1.RestartPolicyNever,
}
By("the termination message should be set")
Expect(status.State.Terminated.Message).Should(Equal(terminationMessage))
By("create the container")
c.Create()
defer c.Delete()
By("delete the container")
Expect(c.Delete()).To(Succeed())
})
By(fmt.Sprintf("wait for the container to reach %s", testCase.phase))
Eventually(c.GetPhase, retryTimeout, pollInterval).Should(Equal(testCase.phase))
By("get the container status")
status, err := c.GetStatus()
Expect(err).NotTo(HaveOccurred())
By("the container should be terminated")
Expect(GetContainerState(status.State)).To(Equal(ContainerStateTerminated))
By("the termination message should be set")
Expect(status.State.Terminated.Message).Should(testCase.message)
By("delete the container")
Expect(c.Delete()).To(Succeed())
})
}
})
Context("when running a container with a new image", func() {

6
vendor/BUILD vendored
View File

@ -13718,3 +13718,9 @@ go_library(
srcs = ["k8s.io/client-go/third_party/forked/golang/netutil/addr.go"],
tags = ["automanaged"],
)
go_library(
name = "github.com/armon/circbuf",
srcs = ["github.com/armon/circbuf/circbuf.go"],
tags = ["automanaged"],
)

22
vendor/github.com/armon/circbuf/.gitignore generated vendored Executable file
View File

@ -0,0 +1,22 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

20
vendor/github.com/armon/circbuf/LICENSE generated vendored Executable file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Armon Dadgar
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

28
vendor/github.com/armon/circbuf/README.md generated vendored Normal file
View File

@ -0,0 +1,28 @@
circbuf
=======
This repository provides the `circbuf` package. This provides a `Buffer` object
which is a circular (or ring) buffer. It has a fixed size, but can be written
to infinitely. Only the last `size` bytes are ever retained. The buffer implements
the `io.Writer` interface.
Documentation
=============
Full documentation can be found on [Godoc](http://godoc.org/github.com/armon/circbuf)
Usage
=====
The `circbuf` package is very easy to use:
```go
buf, _ := NewBuffer(6)
buf.Write([]byte("hello world"))
if string(buf.Bytes()) != " world" {
panic("should only have last 6 bytes!")
}
```

92
vendor/github.com/armon/circbuf/circbuf.go generated vendored Normal file
View File

@ -0,0 +1,92 @@
package circbuf
import (
"fmt"
)
// Buffer implements a circular buffer. It is a fixed size,
// and new writes overwrite older data, such that for a buffer
// of size N, for any amount of writes, only the last N bytes
// are retained.
type Buffer struct {
data []byte
size int64
writeCursor int64
written int64
}
// NewBuffer creates a new buffer of a given size. The size
// must be greater than 0.
func NewBuffer(size int64) (*Buffer, error) {
if size <= 0 {
return nil, fmt.Errorf("Size must be positive")
}
b := &Buffer{
size: size,
data: make([]byte, size),
}
return b, nil
}
// Write writes up to len(buf) bytes to the internal ring,
// overriding older data if necessary.
func (b *Buffer) Write(buf []byte) (int, error) {
// Account for total bytes written
n := len(buf)
b.written += int64(n)
// If the buffer is larger than ours, then we only care
// about the last size bytes anyways
if int64(n) > b.size {
buf = buf[int64(n)-b.size:]
}
// Copy in place
remain := b.size - b.writeCursor
copy(b.data[b.writeCursor:], buf)
if int64(len(buf)) > remain {
copy(b.data, buf[remain:])
}
// Update location of the cursor
b.writeCursor = ((b.writeCursor + int64(len(buf))) % b.size)
return n, nil
}
// Size returns the size of the buffer
func (b *Buffer) Size() int64 {
return b.size
}
// TotalWritten provides the total number of bytes written
func (b *Buffer) TotalWritten() int64 {
return b.written
}
// Bytes provides a slice of the bytes written. This
// slice should not be written to.
func (b *Buffer) Bytes() []byte {
switch {
case b.written >= b.size && b.writeCursor == 0:
return b.data
case b.written > b.size:
out := make([]byte, b.size)
copy(out, b.data[b.writeCursor:])
copy(out[b.size-b.writeCursor:], b.data[:b.writeCursor])
return out
default:
return b.data[:b.writeCursor]
}
}
// Reset resets the buffer so it has no content.
func (b *Buffer) Reset() {
b.writeCursor = 0
b.written = 0
}
// String returns the contents of the buffer as a string
func (b *Buffer) String() string {
return string(b.Bytes())
}