mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 Replicated, Inc.
|
||||
|
||||
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.
|
||||
@@ -1 +1,23 @@
|
||||
# Replicated Troubleshoot
|
||||
|
||||
Replicated Troubleshoot is a CLI tool and a Kubernetes Operator that provides pre-installation cluster conformance testing and validation (preflight checks) and post-installation troubleshooting and diagnostics (support bundles).
|
||||
|
||||
## Preflight Checks
|
||||
Preflight checks are an easy-to-run set of conformance tests that can be written to verify that specific requirements in a cluster are met.
|
||||
|
||||
To run a sample preflight check from a sample application, [install the preflight kubectl plugin](https://help.replicated.com/docs/troubleshoot/kubernetes/preflight/running-as-kubectl-plugin/) and run:
|
||||
|
||||
```shell
|
||||
kubectl preflight https://git.io/preflight-checks
|
||||
```
|
||||
|
||||
For a full description of the supported preflight checks, visit the [docs](https://help.replicated.com/docs/troubleshoot/kubernetes/analysis/analysis-phase/).
|
||||
|
||||
## Support Bundle
|
||||
A support bundle is an archive that's created in-cluster, by collecting logs, cluster information and executing various commands. After creating a support bundle, the cluster operator will normally deliver it to the application vendor for analysis and remote debugging.
|
||||
|
||||
To collect a sample support bundle, [install the troubleshoot kubectl plugin](/docs/troubleshoot/kubernetes/troubleshoot/running-as-kubectl-plugin/) and run:
|
||||
|
||||
```shell
|
||||
kubectl troubleshoot https://git.io/support-bundle
|
||||
```
|
||||
|
||||
@@ -396,6 +396,23 @@ spec:
|
||||
type: object
|
||||
clusterResources:
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
containerName:
|
||||
type: string
|
||||
containerPath:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- selector
|
||||
- namespace
|
||||
- containerPath
|
||||
type: object
|
||||
logs:
|
||||
properties:
|
||||
limits:
|
||||
@@ -415,6 +432,31 @@ spec:
|
||||
required:
|
||||
- selector
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
command:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
- image
|
||||
type: object
|
||||
secret:
|
||||
properties:
|
||||
includeValue:
|
||||
|
||||
@@ -618,6 +618,23 @@ spec:
|
||||
type: object
|
||||
clusterResources:
|
||||
type: object
|
||||
copy:
|
||||
properties:
|
||||
containerName:
|
||||
type: string
|
||||
containerPath:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
selector:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- selector
|
||||
- namespace
|
||||
- containerPath
|
||||
type: object
|
||||
logs:
|
||||
properties:
|
||||
limits:
|
||||
@@ -637,6 +654,31 @@ spec:
|
||||
required:
|
||||
- selector
|
||||
type: object
|
||||
run:
|
||||
properties:
|
||||
args:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
command:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
image:
|
||||
type: string
|
||||
imagePullPolicy:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
timeout:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
- image
|
||||
type: object
|
||||
secret:
|
||||
properties:
|
||||
includeValue:
|
||||
|
||||
@@ -348,6 +348,16 @@ func (in *Collect) DeepCopyInto(out *Collect) {
|
||||
*out = new(Logs)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Run != nil {
|
||||
in, out := &in.Run, &out.Run
|
||||
*out = new(Run)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Copy != nil {
|
||||
in, out := &in.Copy, &out.Copy
|
||||
*out = new(Copy)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
|
||||
@@ -564,6 +574,26 @@ func (in *CollectorStatus) DeepCopy() *CollectorStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Copy) DeepCopyInto(out *Copy) {
|
||||
*out = *in
|
||||
if in.Selector != nil {
|
||||
in, out := &in.Selector, &out.Selector
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Copy.
|
||||
func (in *Copy) DeepCopy() *Copy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Copy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinition) DeepCopyInto(out *CustomResourceDefinition) {
|
||||
*out = *in
|
||||
@@ -934,6 +964,31 @@ func (in *PreflightStatus) DeepCopy() *PreflightStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Run) DeepCopyInto(out *Run) {
|
||||
*out = *in
|
||||
if in.Command != nil {
|
||||
in, out := &in.Command, &out.Command
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Args != nil {
|
||||
in, out := &in.Args, &out.Args
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Run.
|
||||
func (in *Run) DeepCopy() *Run {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Run)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Secret) DeepCopyInto(out *Secret) {
|
||||
*out = *in
|
||||
|
||||
@@ -3,16 +3,28 @@ kind: Collector
|
||||
metadata:
|
||||
name: collector-sample
|
||||
spec:
|
||||
# - clusterInfo: {}
|
||||
# - clusterResources: {}
|
||||
- clusterInfo: {}
|
||||
- clusterResources: {}
|
||||
# - secret:
|
||||
# name: illmannered-cricket-mysql
|
||||
# namespace: default
|
||||
# key: mysql-password
|
||||
- logs:
|
||||
# - logs:
|
||||
# selector:
|
||||
# - name=nginx-ingress-microk8s
|
||||
# namespace: default
|
||||
# limits:
|
||||
# maxAge: 30d
|
||||
# maxLines: 10000
|
||||
# - run:
|
||||
# name: ping-google
|
||||
# namespace: default
|
||||
# image: flungo/netutils
|
||||
# command: ["ping"]
|
||||
# args: ["www.google.com"]
|
||||
# # timeout: 5s
|
||||
- copy:
|
||||
selector:
|
||||
- name=nginx-ingress-microk8s
|
||||
- app=illmannered-cricket-mysql
|
||||
namespace: default
|
||||
limits:
|
||||
maxAge: 30d
|
||||
maxLines: 10000
|
||||
containerPath: /etc/hosts
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
apiVersion: krew.googlecontainertools.github.com/v1alpha2
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: preflight
|
||||
spec:
|
||||
version: "v0.1.0"
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/preflight_0.1.0_linux_amd64-alpha.tar.gz
|
||||
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
|
||||
files:
|
||||
- from: "/kubectl-preflight"
|
||||
to: "."
|
||||
bin: "./kubectl-preflight"
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/preflight_0.1.0_darwin_amd64-alpha.tar.gz
|
||||
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
|
||||
files:
|
||||
- from: "/kubectl-preflight"
|
||||
to: "."
|
||||
bin: "./kubectl-preflight"
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/preflight_0.1.0_windows_amd64-alpha.tar.gz
|
||||
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
|
||||
files:
|
||||
- from: "/kubectl-preflight.exe"
|
||||
to: "."
|
||||
bin: "./kubectl-preflight.exe"
|
||||
shortDescription: Executes preflight tests and application conformance verifications on a cluster before installing an application
|
||||
homepage: https://github.com/replicaetdhq/troubleshoot
|
||||
@@ -0,0 +1,39 @@
|
||||
apiVersion: krew.googlecontainertools.github.com/v1alpha2
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: troubleshoot
|
||||
spec:
|
||||
version: "v0.1.0"
|
||||
platforms:
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/troubleshoot_0.1.0_linux_amd64-alpha.tar.gz
|
||||
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
|
||||
files:
|
||||
- from: "/kubectl-troubleshoot"
|
||||
to: "."
|
||||
bin: "./kubectl-troubleshoot"
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/troubleshoot_0.1.0_darwin_amd64-alpha.tar.gz
|
||||
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
|
||||
files:
|
||||
- from: "/kubectl-troubleshoot"
|
||||
to: "."
|
||||
bin: "./kubectl-troubleshoot"
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
uri: https://github.com/replicatedhq/troubleshoot/releases/download/v0.1.0/troubleshoot_0.1.0_windows_amd64-alpha.tar.gz
|
||||
sha256: "fcfee994959a4924d0f70817a4b40d2b4df9e21a5a2a106d340784ce77fbbc15"
|
||||
files:
|
||||
- from: "/kubectl-troubleshoot.exe"
|
||||
to: "."
|
||||
bin: "./kubectl-troubleshoot.exe"
|
||||
shortDescription: Creates support bundles for off-cluster analysis
|
||||
homepage: https://github.com/replicaetdhq/troubleshoot
|
||||
@@ -9,6 +9,7 @@ require (
|
||||
github.com/gin-gonic/gin v1.4.0
|
||||
github.com/gizak/termui/v3 v3.1.0
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/manifoldco/promptui v0.3.2
|
||||
github.com/mholt/archiver v3.1.1+incompatible
|
||||
github.com/nwaples/rardecode v1.0.0 // indirect
|
||||
|
||||
@@ -142,6 +142,7 @@ github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
|
||||
github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0=
|
||||
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
|
||||
@@ -24,9 +24,28 @@ type Logs struct {
|
||||
Limits *LogLimits `json:"limits,omitempty" yaml:"omitempty"`
|
||||
}
|
||||
|
||||
type Run struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
Image string `json:"image" yaml:"image"`
|
||||
Command []string `json:"command,omitempty" yaml:"command,omitempty"`
|
||||
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
|
||||
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
ImagePullPolicy string `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"`
|
||||
}
|
||||
|
||||
type Copy struct {
|
||||
Selector []string `json:"selector" yaml:"selector"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
ContainerPath string `json:"containerPath" yaml:"containerPath"`
|
||||
ContainerName string `json:"containerName,omitempty" yaml:"containerName,omitempty"`
|
||||
}
|
||||
|
||||
type Collect struct {
|
||||
ClusterInfo *ClusterInfo `json:"clusterInfo,omitempty" yaml:"clusterInfo,omitempty"`
|
||||
ClusterResources *ClusterResources `json:"clusterResources,omitempty" yaml:"clusterResources,omitempty"`
|
||||
Secret *Secret `json:"secret,omitempty" yaml:"secret,omitempty"`
|
||||
Logs *Logs `json:"logs,omitempty" yaml:"logs,omitempty"`
|
||||
Run *Run `json:"run,omitempty" yaml:"run,omitempty"`
|
||||
Copy *Copy `json:"copy,omitempty" yaml:"copy,omitempty"`
|
||||
}
|
||||
|
||||
@@ -364,6 +364,16 @@ func (in *Collect) DeepCopyInto(out *Collect) {
|
||||
*out = new(Logs)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Run != nil {
|
||||
in, out := &in.Run, &out.Run
|
||||
*out = new(Run)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Copy != nil {
|
||||
in, out := &in.Copy, &out.Copy
|
||||
*out = new(Copy)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
|
||||
@@ -580,6 +590,26 @@ func (in *CollectorStatus) DeepCopy() *CollectorStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Copy) DeepCopyInto(out *Copy) {
|
||||
*out = *in
|
||||
if in.Selector != nil {
|
||||
in, out := &in.Selector, &out.Selector
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Copy.
|
||||
func (in *Copy) DeepCopy() *Copy {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Copy)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinition) DeepCopyInto(out *CustomResourceDefinition) {
|
||||
*out = *in
|
||||
@@ -950,6 +980,31 @@ func (in *PreflightStatus) DeepCopy() *PreflightStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Run) DeepCopyInto(out *Run) {
|
||||
*out = *in
|
||||
if in.Command != nil {
|
||||
in, out := &in.Command, &out.Command
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Args != nil {
|
||||
in, out := &in.Args, &out.Args
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Run.
|
||||
func (in *Run) DeepCopy() *Run {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Run)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Secret) DeepCopyInto(out *Secret) {
|
||||
*out = *in
|
||||
|
||||
@@ -270,15 +270,3 @@ func (c *ClusterResourcesOutput) Redact() (*ClusterResourcesOutput, error) {
|
||||
CustomResourceDefinitions: crds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func redactMap(input map[string][]byte) (map[string][]byte, error) {
|
||||
result := make(map[string][]byte)
|
||||
for k, v := range input {
|
||||
redacted, err := redact.Redact(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[k] = redacted
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -25,10 +25,16 @@ func (c *Collector) RunCollectorSync() error {
|
||||
return ClusterResources(c.Redact)
|
||||
}
|
||||
if collect.Secret != nil {
|
||||
return Secret(collect.Secret)
|
||||
return Secret(collect.Secret, c.Redact)
|
||||
}
|
||||
if collect.Logs != nil {
|
||||
return Logs(collect.Logs)
|
||||
return Logs(collect.Logs, c.Redact)
|
||||
}
|
||||
if collect.Run != nil {
|
||||
return Run(collect.Run, c.Redact)
|
||||
}
|
||||
if collect.Copy != nil {
|
||||
return Copy(collect.Copy, c.Redact)
|
||||
}
|
||||
|
||||
return errors.New("no spec found to run")
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
)
|
||||
|
||||
type CopyOutput struct {
|
||||
Files map[string][]byte `json:"copy/,omitempty"`
|
||||
}
|
||||
|
||||
func Copy(copyCollector *troubleshootv1beta1.Copy, redact bool) error {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pods, err := listPodsInSelectors(client, copyCollector.Namespace, copyCollector.Selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copyOutput := &CopyOutput{
|
||||
Files: make(map[string][]byte),
|
||||
}
|
||||
|
||||
for _, pod := range pods {
|
||||
files, err := copyFiles(client, pod, copyCollector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range files {
|
||||
copyOutput.Files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if redact {
|
||||
// TODO
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(copyOutput, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFiles(client *kubernetes.Clientset, pod corev1.Pod, copyCollector *troubleshootv1beta1.Copy) (map[string][]byte, error) {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container := pod.Spec.Containers[0].Name
|
||||
if copyCollector.ContainerName != "" {
|
||||
container = copyCollector.ContainerName
|
||||
}
|
||||
|
||||
command := []string{"cat", copyCollector.ContainerPath}
|
||||
|
||||
output := new(bytes.Buffer)
|
||||
|
||||
req := client.CoreV1().RESTClient().Post().Resource("pods").Name(pod.Name).Namespace(pod.Namespace).SubResource("exec")
|
||||
scheme := runtime.NewScheme()
|
||||
if err := corev1.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parameterCodec := runtime.NewParameterCodec(scheme)
|
||||
req.VersionedParams(&corev1.PodExecOptions{
|
||||
Command: command,
|
||||
Container: container,
|
||||
Stdin: true,
|
||||
Stdout: false,
|
||||
Stderr: true,
|
||||
TTY: false,
|
||||
}, parameterCodec)
|
||||
|
||||
exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
err = exec.Stream(remotecommand.StreamOptions{
|
||||
Stdin: nil,
|
||||
Stdout: output,
|
||||
Stderr: &stderr,
|
||||
Tty: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
fmt.Sprintf("%s/%s/%s", pod.Namespace, pod.Name, copyCollector.ContainerPath): output.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
+20
-2
@@ -19,7 +19,7 @@ type LogsOutput struct {
|
||||
PodLogs map[string][]byte `json:"logs/,omitempty"`
|
||||
}
|
||||
|
||||
func Logs(logsCollector *troubleshootv1beta1.Logs) error {
|
||||
func Logs(logsCollector *troubleshootv1beta1.Logs, redact bool) error {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -35,7 +35,7 @@ func Logs(logsCollector *troubleshootv1beta1.Logs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
logsOutput := LogsOutput{
|
||||
logsOutput := &LogsOutput{
|
||||
PodLogs: make(map[string][]byte),
|
||||
}
|
||||
for _, pod := range pods {
|
||||
@@ -49,6 +49,13 @@ func Logs(logsCollector *troubleshootv1beta1.Logs) error {
|
||||
}
|
||||
}
|
||||
|
||||
if redact {
|
||||
logsOutput, err = logsOutput.Redact()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(logsOutput, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -114,3 +121,14 @@ func getPodLogs(client *kubernetes.Clientset, pod corev1.Pod, limits *troublesho
|
||||
fmt.Sprintf("%s/%s.txt", pod.Namespace, pod.Name): buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *LogsOutput) Redact() (*LogsOutput, error) {
|
||||
podLogs, err := redactMap(l.PodLogs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LogsOutput{
|
||||
PodLogs: podLogs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"github.com/replicatedhq/troubleshoot/pkg/redact"
|
||||
)
|
||||
|
||||
func redactMap(input map[string][]byte) (map[string][]byte, error) {
|
||||
result := make(map[string][]byte)
|
||||
for k, v := range input {
|
||||
redacted, err := redact.Redact(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[k] = redacted
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
)
|
||||
|
||||
type RunOutput struct {
|
||||
PodLogs map[string][]byte `json:"run/,omitempty"`
|
||||
}
|
||||
|
||||
func Run(runCollector *troubleshootv1beta1.Run, redact bool) error {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pod, err := runPod(client, runCollector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runOutput := &RunOutput{
|
||||
PodLogs: make(map[string][]byte),
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
then := now.Add(time.Duration(20 * time.Second))
|
||||
|
||||
if runCollector.Timeout != "" {
|
||||
parsedDuration, err := time.ParseDuration(runCollector.Timeout)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to parse time duration %s\n", runCollector.Timeout)
|
||||
} else {
|
||||
then = now.Add(parsedDuration)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
if time.Now().After(then) {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
limits := troubleshootv1beta1.LogLimits{
|
||||
MaxLines: 10000,
|
||||
}
|
||||
podLogs, err := getPodLogs(client, *pod, &limits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range podLogs {
|
||||
runOutput.PodLogs[k] = v
|
||||
}
|
||||
|
||||
if err := client.CoreV1().Pods(pod.Namespace).Delete(pod.Name, &metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if redact {
|
||||
runOutput, err = runOutput.Redact()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(runOutput, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runPod(client *kubernetes.Clientset, runCollector *troubleshootv1beta1.Run) (*corev1.Pod, error) {
|
||||
podLabels := make(map[string]string)
|
||||
podLabels["troubleshoot-role"] = "run-collector"
|
||||
|
||||
pullPolicy := corev1.PullIfNotPresent
|
||||
if runCollector.ImagePullPolicy != "" {
|
||||
pullPolicy = corev1.PullPolicy(runCollector.ImagePullPolicy)
|
||||
}
|
||||
|
||||
pod := corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: runCollector.Name,
|
||||
Namespace: runCollector.Namespace,
|
||||
Labels: podLabels,
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Image: runCollector.Image,
|
||||
ImagePullPolicy: pullPolicy,
|
||||
Name: "collector",
|
||||
Command: runCollector.Command,
|
||||
Args: runCollector.Args,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
created, err := client.CoreV1().Pods(runCollector.Namespace).Create(&pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func (r *RunOutput) Redact() (*RunOutput, error) {
|
||||
podLogs, err := redactMap(r.PodLogs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RunOutput{
|
||||
PodLogs: podLogs,
|
||||
}, nil
|
||||
}
|
||||
+3
-20
@@ -30,7 +30,7 @@ func CreateCollector(client client.Client, scheme *runtime.Scheme, ownerRef meta
|
||||
}
|
||||
|
||||
func createCollectorSpecConfigMap(client client.Client, scheme *runtime.Scheme, ownerRef metav1.Object, jobName string, jobNamespace string, collect *troubleshootv1beta1.Collect) (*corev1.ConfigMap, error) {
|
||||
name := fmt.Sprintf("%s-%s", jobName, idForCollector(collect))
|
||||
name := fmt.Sprintf("%s-%s", jobName, DeterministicIDForCollector(collect))
|
||||
namespacedName := types.NamespacedName{
|
||||
Name: name,
|
||||
Namespace: jobNamespace,
|
||||
@@ -76,7 +76,7 @@ func createCollectorSpecConfigMap(client client.Client, scheme *runtime.Scheme,
|
||||
}
|
||||
|
||||
func createCollectorPod(client client.Client, scheme *runtime.Scheme, ownerRef metav1.Object, jobName string, jobNamespace string, jobType string, collect *troubleshootv1beta1.Collect, configMap *corev1.ConfigMap, image string, pullPolicy string) (*corev1.Pod, error) {
|
||||
name := fmt.Sprintf("%s-%s", jobName, idForCollector(collect))
|
||||
name := fmt.Sprintf("%s-%s", jobName, DeterministicIDForCollector(collect))
|
||||
|
||||
namespacedName := types.NamespacedName{
|
||||
Name: name,
|
||||
@@ -120,7 +120,7 @@ func createCollectorPod(client client.Client, scheme *runtime.Scheme, ownerRef m
|
||||
{
|
||||
Image: imageName,
|
||||
ImagePullPolicy: imagePullPolicy,
|
||||
Name: idForCollector(collect),
|
||||
Name: DeterministicIDForCollector(collect),
|
||||
Command: []string{"collector"},
|
||||
Args: []string{
|
||||
"run",
|
||||
@@ -162,20 +162,3 @@ func createCollectorPod(client client.Client, scheme *runtime.Scheme, ownerRef m
|
||||
|
||||
return &pod, nil
|
||||
}
|
||||
|
||||
func idForCollector(collector *troubleshootv1beta1.Collect) string {
|
||||
if collector.ClusterInfo != nil {
|
||||
return "cluster-info"
|
||||
}
|
||||
if collector.ClusterResources != nil {
|
||||
return "cluster-resources"
|
||||
}
|
||||
if collector.Secret != nil {
|
||||
return fmt.Sprintf("secret-%s%s", collector.Secret.Namespace, collector.Secret.Name)
|
||||
}
|
||||
if collector.Logs != nil {
|
||||
randomString := "abcdef" // TODO
|
||||
return fmt.Sprintf("logs-%s%s", collector.Logs.Namespace, randomString)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
+20
-2
@@ -22,7 +22,7 @@ type SecretOutput struct {
|
||||
FoundSecret map[string][]byte `json:"secrets/,omitempty"`
|
||||
}
|
||||
|
||||
func Secret(secretCollector *troubleshootv1beta1.Secret) error {
|
||||
func Secret(secretCollector *troubleshootv1beta1.Secret, redact bool) error {
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -38,12 +38,19 @@ func Secret(secretCollector *troubleshootv1beta1.Secret) error {
|
||||
return err
|
||||
}
|
||||
|
||||
secretOutput := SecretOutput{
|
||||
secretOutput := &SecretOutput{
|
||||
FoundSecret: map[string][]byte{
|
||||
fmt.Sprintf("%s/%s.json", secret.Namespace, secret.Name): encoded,
|
||||
},
|
||||
}
|
||||
|
||||
if redact {
|
||||
secretOutput, err = secretOutput.Redact()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(secretOutput, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -92,3 +99,14 @@ func secret(client *kubernetes.Clientset, secretCollector *troubleshootv1beta1.S
|
||||
|
||||
return &secret, b, nil
|
||||
}
|
||||
|
||||
func (s *SecretOutput) Redact() (*SecretOutput, error) {
|
||||
foundSecret, err := redactMap(s.FoundSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SecretOutput{
|
||||
FoundSecret: foundSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
)
|
||||
|
||||
func DeterministicIDForCollector(collector *troubleshootv1beta1.Collect) string {
|
||||
unsafeID := ""
|
||||
|
||||
if collector.ClusterInfo != nil {
|
||||
unsafeID = "cluster-info"
|
||||
}
|
||||
|
||||
if collector.ClusterResources != nil {
|
||||
unsafeID = "cluster-resources"
|
||||
}
|
||||
|
||||
if collector.Secret != nil {
|
||||
unsafeID = fmt.Sprintf("secret-%s-%s", collector.Secret.Namespace, collector.Secret.Name)
|
||||
}
|
||||
|
||||
if collector.Logs != nil {
|
||||
unsafeID = fmt.Sprintf("logs-%s-%s", collector.Logs.Namespace, selectorToString(collector.Logs.Selector))
|
||||
}
|
||||
|
||||
if collector.Run != nil {
|
||||
unsafeID = fmt.Sprintf("run-%s", strings.ToLower(collector.Run.Name))
|
||||
}
|
||||
|
||||
if collector.Copy != nil {
|
||||
unsafeID = fmt.Sprintf("copy-%s-%s", selectorToString(collector.Copy.Selector), pathToString(collector.Copy.ContainerPath))
|
||||
}
|
||||
|
||||
return rfc1035(unsafeID)
|
||||
}
|
||||
|
||||
func selectorToString(selector []string) string {
|
||||
return strings.Replace(strings.Join(selector, "-"), "=", "-", -1)
|
||||
}
|
||||
|
||||
func pathToString(path string) string {
|
||||
return strings.Replace(path, "/", "-", -1)
|
||||
}
|
||||
|
||||
func rfc1035(in string) string {
|
||||
reg := regexp.MustCompile("[^a-z0-9\\-]+")
|
||||
out := reg.ReplaceAllString(in, "-")
|
||||
|
||||
if len(out) > 63 {
|
||||
out = out[:63]
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_selectorToString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
selector []string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "app=api",
|
||||
selector: []string{"app=api"},
|
||||
expect: "app-api",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := selectorToString(test.selector)
|
||||
assert.Equal(t, test.expect, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DeterministicIDForCollector(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
collector *troubleshootv1beta1.Collect
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "cluster-info",
|
||||
collector: &troubleshootv1beta1.Collect{
|
||||
ClusterInfo: &troubleshootv1beta1.ClusterInfo{},
|
||||
},
|
||||
expect: "cluster-info",
|
||||
},
|
||||
{
|
||||
name: "cluster-resources",
|
||||
collector: &troubleshootv1beta1.Collect{
|
||||
ClusterResources: &troubleshootv1beta1.ClusterResources{},
|
||||
},
|
||||
expect: "cluster-resources",
|
||||
},
|
||||
{
|
||||
name: "secret",
|
||||
collector: &troubleshootv1beta1.Collect{
|
||||
Secret: &troubleshootv1beta1.Secret{
|
||||
Name: "secret-agent-woman",
|
||||
Namespace: "top-secret",
|
||||
},
|
||||
},
|
||||
expect: "secret-top-secret-secret-agent-woman",
|
||||
},
|
||||
{
|
||||
name: "logs",
|
||||
collector: &troubleshootv1beta1.Collect{
|
||||
Logs: &troubleshootv1beta1.Logs{
|
||||
Namespace: "top-secret",
|
||||
Selector: []string{"this=is", "rather=long", "for=testing", "more=words", "too=many", "abcdef!=123456"},
|
||||
},
|
||||
},
|
||||
expect: "logs-top-secret-this-is-rather-long-for-testing-more-words-too-",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := DeterministicIDForCollector(test.collector)
|
||||
assert.Equal(t, test.expect, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
|
||||
collectrunner "github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"gopkg.in/yaml.v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -168,15 +169,15 @@ func (r *ReconcileCollectorJob) getCollectorSpec(namespace string, name string)
|
||||
}
|
||||
|
||||
func (r *ReconcileCollectorJob) reconileOneCollectorJob(instance *troubleshootv1beta1.CollectorJob, collect *troubleshootv1beta1.Collect) error {
|
||||
if contains(instance.Status.Running, idForCollector(collect)) {
|
||||
if contains(instance.Status.Running, collectrunner.DeterministicIDForCollector(collect)) {
|
||||
collectorPod, err := r.getCollectorPod(instance, collect)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if collectorPod.Status.Phase == corev1.PodFailed {
|
||||
instance.Status.Failed = append(instance.Status.Failed, idForCollector(collect))
|
||||
instance.Status.Running = remove(instance.Status.Running, idForCollector(collect))
|
||||
instance.Status.Failed = append(instance.Status.Failed, collectrunner.DeterministicIDForCollector(collect))
|
||||
instance.Status.Running = remove(instance.Status.Running, collectrunner.DeterministicIDForCollector(collect))
|
||||
|
||||
if err := r.Update(context.Background(), instance); err != nil {
|
||||
return err
|
||||
@@ -245,7 +246,7 @@ func (r *ReconcileCollectorJob) reconileOneCollectorJob(instance *troubleshootv1
|
||||
return err
|
||||
}
|
||||
request.ContentLength = int64(len(buf.String()))
|
||||
request.Header.Add("collector-id", idForCollector(collect))
|
||||
request.Header.Add("collector-id", collectrunner.DeterministicIDForCollector(collect))
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -260,8 +261,8 @@ func (r *ReconcileCollectorJob) reconileOneCollectorJob(instance *troubleshootv1
|
||||
close(stopCh)
|
||||
}
|
||||
|
||||
instance.Status.Successful = append(instance.Status.Successful, idForCollector(collect))
|
||||
instance.Status.Running = remove(instance.Status.Running, idForCollector(collect))
|
||||
instance.Status.Successful = append(instance.Status.Successful, collectrunner.DeterministicIDForCollector(collect))
|
||||
instance.Status.Running = remove(instance.Status.Running, collectrunner.DeterministicIDForCollector(collect))
|
||||
|
||||
if err := r.Update(context.Background(), instance); err != nil {
|
||||
return err
|
||||
@@ -283,7 +284,7 @@ func (r *ReconcileCollectorJob) reconileOneCollectorJob(instance *troubleshootv1
|
||||
}
|
||||
|
||||
func (r *ReconcileCollectorJob) createSpecInConfigMap(instance *troubleshootv1beta1.CollectorJob, collector *troubleshootv1beta1.Collect) error {
|
||||
name := fmt.Sprintf("%s-%s", instance.Name, idForCollector(collector))
|
||||
name := fmt.Sprintf("%s-%s", instance.Name, collectrunner.DeterministicIDForCollector(collector))
|
||||
|
||||
namespacedName := types.NamespacedName{
|
||||
Name: name,
|
||||
@@ -328,7 +329,7 @@ func (r *ReconcileCollectorJob) createSpecInConfigMap(instance *troubleshootv1be
|
||||
}
|
||||
|
||||
func (r *ReconcileCollectorJob) getCollectorPod(instance *troubleshootv1beta1.CollectorJob, collector *troubleshootv1beta1.Collect) (*corev1.Pod, error) {
|
||||
name := fmt.Sprintf("%s-%s", instance.Name, idForCollector(collector))
|
||||
name := fmt.Sprintf("%s-%s", instance.Name, collectrunner.DeterministicIDForCollector(collector))
|
||||
|
||||
namespacedName := types.NamespacedName{
|
||||
Name: name,
|
||||
@@ -345,7 +346,7 @@ func (r *ReconcileCollectorJob) getCollectorPod(instance *troubleshootv1beta1.Co
|
||||
}
|
||||
|
||||
func (r *ReconcileCollectorJob) createCollectorPod(instance *troubleshootv1beta1.CollectorJob, collector *troubleshootv1beta1.Collect) error {
|
||||
name := fmt.Sprintf("%s-%s", instance.Name, idForCollector(collector))
|
||||
name := fmt.Sprintf("%s-%s", instance.Name, collectrunner.DeterministicIDForCollector(collector))
|
||||
|
||||
namespacedName := types.NamespacedName{
|
||||
Name: name,
|
||||
@@ -383,7 +384,7 @@ func (r *ReconcileCollectorJob) createCollectorPod(instance *troubleshootv1beta1
|
||||
{
|
||||
Image: imageName,
|
||||
ImagePullPolicy: imagePullPolicy,
|
||||
Name: idForCollector(collector),
|
||||
Name: collectrunner.DeterministicIDForCollector(collector),
|
||||
Command: []string{"collector"},
|
||||
Args: []string{
|
||||
"run",
|
||||
@@ -422,7 +423,7 @@ func (r *ReconcileCollectorJob) createCollectorPod(instance *troubleshootv1beta1
|
||||
return err
|
||||
}
|
||||
|
||||
instance.Status.Running = append(instance.Status.Running, idForCollector(collector))
|
||||
instance.Status.Running = append(instance.Status.Running, collectrunner.DeterministicIDForCollector(collector))
|
||||
if err := r.Update(context.Background(), instance); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -430,23 +431,6 @@ func (r *ReconcileCollectorJob) createCollectorPod(instance *troubleshootv1beta1
|
||||
return nil
|
||||
}
|
||||
|
||||
func idForCollector(collector *troubleshootv1beta1.Collect) string {
|
||||
if collector.ClusterInfo != nil {
|
||||
return "cluster-info"
|
||||
}
|
||||
if collector.ClusterResources != nil {
|
||||
return "cluster-resources"
|
||||
}
|
||||
if collector.Secret != nil {
|
||||
return fmt.Sprintf("secret-%s%s", collector.Secret.Namespace, collector.Secret.Name)
|
||||
}
|
||||
if collector.Logs != nil {
|
||||
randomString := "abcdef" // TODO
|
||||
return fmt.Sprintf("logs-%s%s", collector.Logs.Namespace, randomString)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
|
||||
@@ -2,7 +2,6 @@ package preflightjob
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
|
||||
collectrunner "github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
@@ -11,7 +10,7 @@ import (
|
||||
func (r *ReconcilePreflightJob) reconcilePreflightCollectors(instance *troubleshootv1beta1.PreflightJob, preflight *troubleshootv1beta1.Preflight) error {
|
||||
requestedCollectorIDs := make([]string, 0, 0)
|
||||
for _, collector := range preflight.Spec.Collectors {
|
||||
requestedCollectorIDs = append(requestedCollectorIDs, idForCollector(collector))
|
||||
requestedCollectorIDs = append(requestedCollectorIDs, collectrunner.DeterministicIDForCollector(collector))
|
||||
if err := r.reconcileOnePreflightCollector(instance, collector); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -38,7 +37,7 @@ func (r *ReconcilePreflightJob) reconcilePreflightCollectors(instance *troublesh
|
||||
}
|
||||
|
||||
func (r *ReconcilePreflightJob) reconcileOnePreflightCollector(instance *troubleshootv1beta1.PreflightJob, collect *troubleshootv1beta1.Collect) error {
|
||||
if contains(instance.Status.CollectorsRunning, idForCollector(collect)) {
|
||||
if contains(instance.Status.CollectorsRunning, collectrunner.DeterministicIDForCollector(collect)) {
|
||||
// preflight just leaves these stopped containers.
|
||||
// it's playing with fire a little, but the analyzers can just
|
||||
// read from the stdout of the stopped container
|
||||
@@ -48,8 +47,8 @@ func (r *ReconcilePreflightJob) reconcileOnePreflightCollector(instance *trouble
|
||||
// immediately. this is a longer term problem to solve, maybe something,
|
||||
// the mananger? can broker these collector results. but, ya know...
|
||||
|
||||
instance.Status.CollectorsSuccessful = append(instance.Status.CollectorsSuccessful, idForCollector(collect))
|
||||
instance.Status.CollectorsRunning = remove(instance.Status.CollectorsRunning, idForCollector(collect))
|
||||
instance.Status.CollectorsSuccessful = append(instance.Status.CollectorsSuccessful, collectrunner.DeterministicIDForCollector(collect))
|
||||
instance.Status.CollectorsRunning = remove(instance.Status.CollectorsRunning, collectrunner.DeterministicIDForCollector(collect))
|
||||
|
||||
if err := r.Update(context.Background(), instance); err != nil {
|
||||
return err
|
||||
@@ -63,7 +62,7 @@ func (r *ReconcilePreflightJob) reconcileOnePreflightCollector(instance *trouble
|
||||
return err
|
||||
}
|
||||
|
||||
instance.Status.CollectorsRunning = append(instance.Status.CollectorsRunning, idForCollector(collect))
|
||||
instance.Status.CollectorsRunning = append(instance.Status.CollectorsRunning, collectrunner.DeterministicIDForCollector(collect))
|
||||
if err := r.Update(context.Background(), instance); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,22 +87,3 @@ func remove(s []string, r string) []string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Todo these will overlap with troubleshoot containers running at the same time
|
||||
func idForCollector(collector *troubleshootv1beta1.Collect) string {
|
||||
if collector.ClusterInfo != nil {
|
||||
return "cluster-info"
|
||||
}
|
||||
if collector.ClusterResources != nil {
|
||||
return "cluster-resources"
|
||||
}
|
||||
if collector.Secret != nil {
|
||||
return fmt.Sprintf("secret-%s%s", collector.Secret.Namespace, collector.Secret.Name)
|
||||
}
|
||||
if collector.Logs != nil {
|
||||
randomString := "abcdef" // TODO
|
||||
return fmt.Sprintf("logs-%s%s", collector.Logs.Namespace, randomString)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user