Wish i could get this to be inline

This commit is contained in:
Marc Campbell
2019-07-06 00:44:52 +00:00
parent 342eedf4a4
commit b229d56eee
14 changed files with 1023 additions and 141 deletions

View File

@@ -8,10 +8,18 @@ all: test manager
test: generate fmt vet manifests
go test ./pkg/... ./cmd/... -coverprofile cover.out
# Build manager binary
.PHONY: manager
manager: generate fmt vet
go build -o bin/manager github.com/replicatedhq/troubleshoot/cmd/manager
.PHONY: troubleshoot
troubleshoot: generate fmt vet
go build -o bin/troubleshoot github.com/replicatedhq/troubleshoot/cmd/troubleshoot
.PHONY: preflight
preflight: generate fmt vet
go build -o bin/preflight github.com/replicatedhq/troubleshoot/cmd/preflight
# Run against the configured Kubernetes cluster in ~/.kube/config
run: generate fmt vet
go run ./cmd/manager/main.go
@@ -26,22 +34,21 @@ deploy: manifests
kustomize build config/default | kubectl apply -f -
# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
manifests:
controller-gen paths=./pkg/apis/...
# Run go fmt against code
.PHONY: fmt
fmt:
go fmt ./pkg/... ./cmd/...
# Run go vet against code
.PHONY: vet
vet:
go vet ./pkg/... ./cmd/...
.PHONY: generate
generate: controller-gen client-gen
controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./api/...
client-gen go run ../../vendor/k8s.io/code-generator/cmd/client-gen/main.go --output-package=github.com/replicatedhq/troubleshoot/pkg/client --clientset-name troubleshootclientset --input-base github.com/replicatedhq/troubleshoot/pkg/apis --input troubleshoot/v1beta1 -h ./hack/boilerplate.go.txt
generate: controller-gen # client-gen
controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./pkg/apis/...
# client-gen --output-package=github.com/replicatedhq/troubleshoot/pkg/client --clientset-name troubleshootclientset --input-base github.com/replicatedhq/troubleshoot/pkg/apis --input troubleshoot/v1beta1 -h ./hack/boilerplate.go.txt
# Build the docker image
docker-build: test

View File

@@ -32,7 +32,7 @@ import (
func main() {
var metricsAddr string
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&metricsAddr, "metrics-addr", ":8088", "The address the metric endpoint binds to.")
flag.Parse()
logf.SetLogger(logf.ZapLogger(false))
log := logf.Log.WithName("entrypoint")

View File

@@ -0,0 +1,32 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func Collect() *cobra.Command {
cmd := &cobra.Command{
Use: "collect",
Short: "collect a support bundle from a cluster",
Long: `...`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("collectors", cmd.Flags().Lookup("collectors"))
viper.BindPFlag("namespace", cmd.Flags().Lookup("namespace"))
viper.BindPFlag("kubecontext", cmd.Flags().Lookup("kubecontext"))
},
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
cmd.Flags().String("collectors", "", "name of the collectors to use")
cmd.Flags().String("namespace", "", "namespace the collectors can be found in")
cmd.Flags().String("kubecontext", "", "the kubecontext to use when connecting")
viper.BindPFlags(cmd.Flags())
return cmd
}

View File

@@ -0,0 +1,39 @@
package cli
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func RootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "troubleshoot",
Short: "Generate and manage support bundles",
Long: `A support bundle is an archive of files, output, metrics and state
from a server that can be used to assist when troubleshooting a server.`,
SilenceUsage: true,
}
cobra.OnInitialize(initConfig)
cmd.AddCommand(Collect())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
return cmd
}
func InitAndExecute() {
if err := RootCmd().Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func initConfig() {
viper.SetEnvPrefix("TROUBLESHOT")
viper.AutomaticEnv()
}

7
cmd/troubleshoot/main.go Normal file
View File

@@ -0,0 +1,7 @@
package main
import "github.com/replicatedhq/troubleshoot/cmd/troubleshoot/cli"
func main() {
cli.InitAndExecute()
}

View File

@@ -390,6 +390,595 @@ spec:
type: string
type: object
spec:
properties:
collectors:
items:
properties:
kubernetes.api-versions:
properties:
defer:
type: boolean
description:
type: string
include_empty:
type: boolean
meta:
properties:
labels:
additionalProperties:
type: string
type: object
name:
type: string
type: object
output_dir:
type: string
scrub:
properties:
regex:
type: string
replace:
type: string
required:
- regex
- replace
type: object
timeout_seconds:
type: integer
type: object
kubernetes.cluster-info:
properties:
defer:
type: boolean
description:
type: string
include_empty:
type: boolean
meta:
properties:
labels:
additionalProperties:
type: string
type: object
name:
type: string
type: object
output_dir:
type: string
scrub:
properties:
regex:
type: string
replace:
type: string
required:
- regex
- replace
type: object
timeout_seconds:
type: integer
type: object
kubernetes.container-cp:
properties:
container:
type: string
defer:
type: boolean
description:
type: string
include_empty:
type: boolean
meta:
properties:
labels:
additionalProperties:
type: string
type: object
name:
type: string
type: object
namespace:
type: string
output_dir:
type: string
pod:
type: string
pod_list_options:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema
of this representation of an object. Servers should
convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
type: string
continue:
description: "The continue option should be set when
retrieving more results from the server. Since this
value is server defined, clients may only use the
continue value from a previous query result with identical
query parameters (except for the value of continue)
and the server may reject a continue value it does
not recognize. If the specified continue value is
no longer valid whether due to expiration (generally
five to fifteen minutes) or a configuration change
on the server, the server will respond with a 410
ResourceExpired error together with a continue token.
If the client needs a consistent list, it must restart
their list without the continue field. Otherwise,
the client may send another list request with the
token received with the 410 error, the server will
respond with a list starting from the next key, but
from the latest snapshot, which is inconsistent from
the previous list results - objects that are created,
modified, or deleted after the first list request
will be included in the response, as long as their
keys are after the \"next key\". \n This field is
not supported when watch is true. Clients may start
a watch from the last resourceVersion value returned
by the server and not miss any modifications."
type: string
fieldSelector:
description: A selector to restrict the list of returned
objects by their fields. Defaults to everything.
type: string
kind:
description: 'Kind is a string value representing the
REST resource this object represents. Servers may
infer this from the endpoint the client submits requests
to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
type: string
labelSelector:
description: A selector to restrict the list of returned
objects by their labels. Defaults to everything.
type: string
limit:
description: "limit is a maximum number of responses
to return for a list call. If more items exist, the
server will set the `continue` field on the list metadata
to a value that can be used with the same initial
query to retrieve the next set of results. Setting
a limit may return fewer than the requested amount
of items (up to zero items) in the event all requested
objects are filtered out and clients should only use
the presence of the continue field to determine whether
more results are available. Servers may choose not
to support the limit argument and will return all
of the available results. If limit is specified and
the continue field is empty, clients may assume that
no more results are available. This field is not supported
if watch is true. \n The server guarantees that the
objects returned when using continue will be identical
to issuing a single list call without a limit - that
is, no objects created, modified, or deleted after
the first request is issued will be included in any
subsequent continued requests. This is sometimes referred
to as a consistent snapshot, and ensures that a client
that is using limit to receive smaller chunks of a
very large result can ensure they see all possible
objects. If objects are updated during a chunked list
the version of the object that was present at the
time the first list result was calculated is returned."
format: int64
type: integer
resourceVersion:
description: 'When specified with a watch call, shows
changes that occur after that particular version of
a resource. Defaults to changes from the beginning
of history. When specified for list: - if unset, then
the result is returned from remote storage based on
quorum-read flag; - if it''s 0, then we simply return
what we currently have in cache, no guarantee; - if
set to non zero, then the result is at least as fresh
as given rv.'
type: string
timeoutSeconds:
description: Timeout for the list/watch call. This limits
the duration of the call, regardless of any activity
or inactivity.
format: int64
type: integer
watch:
description: Watch for changes to the described resources
and return them as a stream of add, update, and remove
notifications. Specify resourceVersion.
type: boolean
type: object
scrub:
properties:
regex:
type: string
replace:
type: string
required:
- regex
- replace
type: object
src_path:
type: string
timeout_seconds:
type: integer
type: object
kubernetes.logs:
properties:
defer:
type: boolean
description:
type: string
include_empty:
type: boolean
list_options:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema
of this representation of an object. Servers should
convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
type: string
continue:
description: "The continue option should be set when
retrieving more results from the server. Since this
value is server defined, clients may only use the
continue value from a previous query result with identical
query parameters (except for the value of continue)
and the server may reject a continue value it does
not recognize. If the specified continue value is
no longer valid whether due to expiration (generally
five to fifteen minutes) or a configuration change
on the server, the server will respond with a 410
ResourceExpired error together with a continue token.
If the client needs a consistent list, it must restart
their list without the continue field. Otherwise,
the client may send another list request with the
token received with the 410 error, the server will
respond with a list starting from the next key, but
from the latest snapshot, which is inconsistent from
the previous list results - objects that are created,
modified, or deleted after the first list request
will be included in the response, as long as their
keys are after the \"next key\". \n This field is
not supported when watch is true. Clients may start
a watch from the last resourceVersion value returned
by the server and not miss any modifications."
type: string
fieldSelector:
description: A selector to restrict the list of returned
objects by their fields. Defaults to everything.
type: string
kind:
description: 'Kind is a string value representing the
REST resource this object represents. Servers may
infer this from the endpoint the client submits requests
to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
type: string
labelSelector:
description: A selector to restrict the list of returned
objects by their labels. Defaults to everything.
type: string
limit:
description: "limit is a maximum number of responses
to return for a list call. If more items exist, the
server will set the `continue` field on the list metadata
to a value that can be used with the same initial
query to retrieve the next set of results. Setting
a limit may return fewer than the requested amount
of items (up to zero items) in the event all requested
objects are filtered out and clients should only use
the presence of the continue field to determine whether
more results are available. Servers may choose not
to support the limit argument and will return all
of the available results. If limit is specified and
the continue field is empty, clients may assume that
no more results are available. This field is not supported
if watch is true. \n The server guarantees that the
objects returned when using continue will be identical
to issuing a single list call without a limit - that
is, no objects created, modified, or deleted after
the first request is issued will be included in any
subsequent continued requests. This is sometimes referred
to as a consistent snapshot, and ensures that a client
that is using limit to receive smaller chunks of a
very large result can ensure they see all possible
objects. If objects are updated during a chunked list
the version of the object that was present at the
time the first list result was calculated is returned."
format: int64
type: integer
resourceVersion:
description: 'When specified with a watch call, shows
changes that occur after that particular version of
a resource. Defaults to changes from the beginning
of history. When specified for list: - if unset, then
the result is returned from remote storage based on
quorum-read flag; - if it''s 0, then we simply return
what we currently have in cache, no guarantee; - if
set to non zero, then the result is at least as fresh
as given rv.'
type: string
timeoutSeconds:
description: Timeout for the list/watch call. This limits
the duration of the call, regardless of any activity
or inactivity.
format: int64
type: integer
watch:
description: Watch for changes to the described resources
and return them as a stream of add, update, and remove
notifications. Specify resourceVersion.
type: boolean
type: object
meta:
properties:
labels:
additionalProperties:
type: string
type: object
name:
type: string
type: object
namespace:
type: string
output_dir:
type: string
pod:
type: string
pod_log_options:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema
of this representation of an object. Servers should
convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
type: string
container:
description: The container for which to stream logs.
Defaults to only container if there is one container
in the pod.
type: string
follow:
description: Follow the log stream of the pod. Defaults
to false.
type: boolean
kind:
description: 'Kind is a string value representing the
REST resource this object represents. Servers may
infer this from the endpoint the client submits requests
to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
type: string
limitBytes:
description: If set, the number of bytes to read from
the server before terminating the log output. This
may not display a complete final line of logging,
and may return slightly more or slightly less than
the specified limit.
format: int64
type: integer
previous:
description: Return previous terminated container logs.
Defaults to false.
type: boolean
sinceSeconds:
description: A relative time in seconds before the current
time from which to show logs. If this value precedes
the time a pod was started, only logs since the pod
start will be returned. If this value is in the future,
no logs will be returned. Only one of sinceSeconds
or sinceTime may be specified.
format: int64
type: integer
sinceTime:
description: An RFC3339 timestamp from which to show
logs. If this value precedes the time a pod was started,
only logs since the pod start will be returned. If
this value is in the future, no logs will be returned.
Only one of sinceSeconds or sinceTime may be specified.
format: date-time
type: string
tailLines:
description: If set, the number of lines from the end
of the logs to show. If not specified, logs are shown
from the creation of the container or sinceSeconds
or sinceTime
format: int64
type: integer
timestamps:
description: If true, add an RFC3339 or RFC3339Nano
timestamp at the beginning of every line of log output.
Defaults to false.
type: boolean
type: object
scrub:
properties:
regex:
type: string
replace:
type: string
required:
- regex
- replace
type: object
timeout_seconds:
type: integer
type: object
kubernetes.resource-list:
properties:
defer:
type: boolean
description:
type: string
group_version:
type: string
include_empty:
type: boolean
kind:
type: string
meta:
properties:
labels:
additionalProperties:
type: string
type: object
name:
type: string
type: object
namespace:
type: string
output_dir:
type: string
resource_list_options:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema
of this representation of an object. Servers should
convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
type: string
continue:
description: "The continue option should be set when
retrieving more results from the server. Since this
value is server defined, clients may only use the
continue value from a previous query result with identical
query parameters (except for the value of continue)
and the server may reject a continue value it does
not recognize. If the specified continue value is
no longer valid whether due to expiration (generally
five to fifteen minutes) or a configuration change
on the server, the server will respond with a 410
ResourceExpired error together with a continue token.
If the client needs a consistent list, it must restart
their list without the continue field. Otherwise,
the client may send another list request with the
token received with the 410 error, the server will
respond with a list starting from the next key, but
from the latest snapshot, which is inconsistent from
the previous list results - objects that are created,
modified, or deleted after the first list request
will be included in the response, as long as their
keys are after the \"next key\". \n This field is
not supported when watch is true. Clients may start
a watch from the last resourceVersion value returned
by the server and not miss any modifications."
type: string
fieldSelector:
description: A selector to restrict the list of returned
objects by their fields. Defaults to everything.
type: string
kind:
description: 'Kind is a string value representing the
REST resource this object represents. Servers may
infer this from the endpoint the client submits requests
to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
type: string
labelSelector:
description: A selector to restrict the list of returned
objects by their labels. Defaults to everything.
type: string
limit:
description: "limit is a maximum number of responses
to return for a list call. If more items exist, the
server will set the `continue` field on the list metadata
to a value that can be used with the same initial
query to retrieve the next set of results. Setting
a limit may return fewer than the requested amount
of items (up to zero items) in the event all requested
objects are filtered out and clients should only use
the presence of the continue field to determine whether
more results are available. Servers may choose not
to support the limit argument and will return all
of the available results. If limit is specified and
the continue field is empty, clients may assume that
no more results are available. This field is not supported
if watch is true. \n The server guarantees that the
objects returned when using continue will be identical
to issuing a single list call without a limit - that
is, no objects created, modified, or deleted after
the first request is issued will be included in any
subsequent continued requests. This is sometimes referred
to as a consistent snapshot, and ensures that a client
that is using limit to receive smaller chunks of a
very large result can ensure they see all possible
objects. If objects are updated during a chunked list
the version of the object that was present at the
time the first list result was calculated is returned."
format: int64
type: integer
resourceVersion:
description: 'When specified with a watch call, shows
changes that occur after that particular version of
a resource. Defaults to changes from the beginning
of history. When specified for list: - if unset, then
the result is returned from remote storage based on
quorum-read flag; - if it''s 0, then we simply return
what we currently have in cache, no guarantee; - if
set to non zero, then the result is at least as fresh
as given rv.'
type: string
timeoutSeconds:
description: Timeout for the list/watch call. This limits
the duration of the call, regardless of any activity
or inactivity.
format: int64
type: integer
watch:
description: Watch for changes to the described resources
and return them as a stream of add, update, and remove
notifications. Specify resourceVersion.
type: boolean
type: object
scrub:
properties:
regex:
type: string
replace:
type: string
required:
- regex
- replace
type: object
timeout_seconds:
type: integer
required:
- kind
type: object
kubernetes.version:
properties:
defer:
type: boolean
description:
type: string
include_empty:
type: boolean
meta:
properties:
labels:
additionalProperties:
type: string
type: object
name:
type: string
type: object
output_dir:
type: string
scrub:
properties:
regex:
type: string
replace:
type: string
required:
- regex
- replace
type: object
timeout_seconds:
type: integer
type: object
type: object
type: array
required:
- collectors
type: object
status:
type: object

View File

@@ -13,7 +13,7 @@ spec:
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.0
args:
- "--secure-listen-address=0.0.0.0:8443"
- "--upstream=http://127.0.0.1:8080/"
- "--upstream=http://127.0.0.1:8089/"
- "--logtostderr=true"
- "--v=10"
ports:
@@ -21,4 +21,4 @@ spec:
name: https
- name: manager
args:
- "--metrics-addr=127.0.0.1:8080"
- "--metrics-addr=127.0.0.1:8088"

View File

@@ -1,9 +1,23 @@
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Collector
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: collector-sample
spec:
# Add fields here
foo: bar
collectors:
- kubernetes.resource-list:
output_dir: /kubernetes/resources/jobs
kind: jobs
namespace: ""
- kubernetes.resource-list:
output_dir: /kubernetes/resources/deployments
kind: deployments
namespace: ""
- kubernetes.resource-list:
output_dir: /kubernetes/resources/pods
kind: pods
namespace: ""
- kubernetes.resource-list:
output_dir: /kubernetes/resources/events
kind: events
namespace: ""

10
go.sum
View File

@@ -47,6 +47,7 @@ github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -129,6 +130,7 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -152,12 +154,14 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -183,6 +187,7 @@ github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 h1:zNBQb37RGLmJybyMcs
github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
@@ -229,16 +234,21 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@@ -0,0 +1,72 @@
package v1beta1
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type Meta struct {
Name string `json:"name,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
type Scrub struct {
Regex string `json:"regex"`
Replace string `json:"replace"`
}
type SpecShared struct {
Description string `json:"description,omitempty"`
OutputDir string `json:"output_dir,omitempty"`
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
Scrub *Scrub `json:"scrub,omitempty"`
IncludeEmpty bool `json:"include_empty,omitempty"`
Meta *Meta `json:"meta,omitempty"`
Defer bool `json:"defer,omitempty"`
}
type KubernetesAPIVersionsOptions struct {
SpecShared `json:",inline,omitempty"`
}
type KubernetesClusterInfoOptions struct {
SpecShared `json:",inline,omitempty"`
}
type KubernetesVersionOptions struct {
SpecShared `json:",inline,omitempty"`
}
type KubernetesLogsOptions struct {
SpecShared `json:",inline,omitempty"`
Pod string `json:"pod,omitempty"`
Namespace string `json:"namespace,omitempty"`
PodLogOptions *v1.PodLogOptions `json:"pod_log_options,omitempty"`
ListOptions *metav1.ListOptions `json:"list_options,omitempty"`
}
type KubernetesContainerCpOptions struct {
SpecShared `json:",inline,omitempty"`
Pod string `json:"pod,omitempty"`
PodListOptions *metav1.ListOptions `json:"pod_list_options,omitempty"`
Container string `json:"container,omitempty"`
Namespace string `json:"namespace,omitempty"`
SrcPath string `json:"src_path,omitempty"`
}
type KubernetesResourceListOptions struct {
SpecShared `json:",inline,omitempty"`
Kind string `json:"kind"`
GroupVersion string `json:"group_version,omitempty"`
Namespace string `json:"namespace,omitempty"`
ListOptions *metav1.ListOptions `json:"resource_list_options,omitempty"`
}
type Collect struct {
KubernetesAPIVersions *KubernetesAPIVersionsOptions `json:"kubernetes.api-versions,omitempty"`
KubernetesClusterInfo *KubernetesClusterInfoOptions `json:"kubernetes.cluster-info,omitempty"`
KubernetesContainerCp *KubernetesContainerCpOptions `json:"kubernetes.container-cp,omitempty"`
KubernetesLogs *KubernetesLogsOptions `json:"kubernetes.logs,omitempty"`
KubernetesResourceList *KubernetesResourceListOptions `json:"kubernetes.resource-list,omitempty"`
KubernetesVersion *KubernetesVersionOptions `json:"kubernetes.version,omitempty"`
}

View File

@@ -20,13 +20,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// CollectorSpec defines the desired state of Collector
type CollectorSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Collectors []*Collect `json:"collectors"`
}
// CollectorStatus defines the observed state of Collector

View File

@@ -5,6 +5,8 @@
package v1beta1
import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@@ -186,12 +188,57 @@ func (in *AnalyzerStatus) DeepCopy() *AnalyzerStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Collect) DeepCopyInto(out *Collect) {
*out = *in
if in.KubernetesAPIVersions != nil {
in, out := &in.KubernetesAPIVersions, &out.KubernetesAPIVersions
*out = new(KubernetesAPIVersionsOptions)
(*in).DeepCopyInto(*out)
}
if in.KubernetesClusterInfo != nil {
in, out := &in.KubernetesClusterInfo, &out.KubernetesClusterInfo
*out = new(KubernetesClusterInfoOptions)
(*in).DeepCopyInto(*out)
}
if in.KubernetesContainerCp != nil {
in, out := &in.KubernetesContainerCp, &out.KubernetesContainerCp
*out = new(KubernetesContainerCpOptions)
(*in).DeepCopyInto(*out)
}
if in.KubernetesLogs != nil {
in, out := &in.KubernetesLogs, &out.KubernetesLogs
*out = new(KubernetesLogsOptions)
(*in).DeepCopyInto(*out)
}
if in.KubernetesResourceList != nil {
in, out := &in.KubernetesResourceList, &out.KubernetesResourceList
*out = new(KubernetesResourceListOptions)
(*in).DeepCopyInto(*out)
}
if in.KubernetesVersion != nil {
in, out := &in.KubernetesVersion, &out.KubernetesVersion
*out = new(KubernetesVersionOptions)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collect.
func (in *Collect) DeepCopy() *Collect {
if in == nil {
return nil
}
out := new(Collect)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Collector) DeepCopyInto(out *Collector) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -337,6 +384,17 @@ func (in *CollectorList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorSpec) DeepCopyInto(out *CollectorSpec) {
*out = *in
if in.Collectors != nil {
in, out := &in.Collectors, &out.Collectors
*out = make([]*Collect, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Collect)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorSpec.
@@ -364,6 +422,144 @@ 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 *KubernetesAPIVersionsOptions) DeepCopyInto(out *KubernetesAPIVersionsOptions) {
*out = *in
in.SpecShared.DeepCopyInto(&out.SpecShared)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesAPIVersionsOptions.
func (in *KubernetesAPIVersionsOptions) DeepCopy() *KubernetesAPIVersionsOptions {
if in == nil {
return nil
}
out := new(KubernetesAPIVersionsOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesClusterInfoOptions) DeepCopyInto(out *KubernetesClusterInfoOptions) {
*out = *in
in.SpecShared.DeepCopyInto(&out.SpecShared)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesClusterInfoOptions.
func (in *KubernetesClusterInfoOptions) DeepCopy() *KubernetesClusterInfoOptions {
if in == nil {
return nil
}
out := new(KubernetesClusterInfoOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesContainerCpOptions) DeepCopyInto(out *KubernetesContainerCpOptions) {
*out = *in
in.SpecShared.DeepCopyInto(&out.SpecShared)
if in.PodListOptions != nil {
in, out := &in.PodListOptions, &out.PodListOptions
*out = new(metav1.ListOptions)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesContainerCpOptions.
func (in *KubernetesContainerCpOptions) DeepCopy() *KubernetesContainerCpOptions {
if in == nil {
return nil
}
out := new(KubernetesContainerCpOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesLogsOptions) DeepCopyInto(out *KubernetesLogsOptions) {
*out = *in
in.SpecShared.DeepCopyInto(&out.SpecShared)
if in.PodLogOptions != nil {
in, out := &in.PodLogOptions, &out.PodLogOptions
*out = new(v1.PodLogOptions)
(*in).DeepCopyInto(*out)
}
if in.ListOptions != nil {
in, out := &in.ListOptions, &out.ListOptions
*out = new(metav1.ListOptions)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesLogsOptions.
func (in *KubernetesLogsOptions) DeepCopy() *KubernetesLogsOptions {
if in == nil {
return nil
}
out := new(KubernetesLogsOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesResourceListOptions) DeepCopyInto(out *KubernetesResourceListOptions) {
*out = *in
in.SpecShared.DeepCopyInto(&out.SpecShared)
if in.ListOptions != nil {
in, out := &in.ListOptions, &out.ListOptions
*out = new(metav1.ListOptions)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesResourceListOptions.
func (in *KubernetesResourceListOptions) DeepCopy() *KubernetesResourceListOptions {
if in == nil {
return nil
}
out := new(KubernetesResourceListOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesVersionOptions) DeepCopyInto(out *KubernetesVersionOptions) {
*out = *in
in.SpecShared.DeepCopyInto(&out.SpecShared)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesVersionOptions.
func (in *KubernetesVersionOptions) DeepCopy() *KubernetesVersionOptions {
if in == nil {
return nil
}
out := new(KubernetesVersionOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Meta) DeepCopyInto(out *Meta) {
*out = *in
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Meta.
func (in *Meta) DeepCopy() *Meta {
if in == nil {
return nil
}
out := new(Meta)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Preflight) DeepCopyInto(out *Preflight) {
*out = *in
@@ -541,3 +737,43 @@ func (in *PreflightStatus) DeepCopy() *PreflightStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Scrub) DeepCopyInto(out *Scrub) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Scrub.
func (in *Scrub) DeepCopy() *Scrub {
if in == nil {
return nil
}
out := new(Scrub)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SpecShared) DeepCopyInto(out *SpecShared) {
*out = *in
if in.Scrub != nil {
in, out := &in.Scrub, &out.Scrub
*out = new(Scrub)
**out = **in
}
if in.Meta != nil {
in, out := &in.Meta, &out.Meta
*out = new(Meta)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SpecShared.
func (in *SpecShared) DeepCopy() *SpecShared {
if in == nil {
return nil
}
out := new(SpecShared)
in.DeepCopyInto(out)
return out
}

View File

@@ -18,18 +18,13 @@ package collector
import (
"context"
"reflect"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -92,8 +87,6 @@ type ReconcileCollector struct {
// Reconcile reads that state of the cluster for a Collector object and makes changes based on the state read
// and what is in the Collector.Spec
// TODO(user): Modify this Reconcile function to implement your Controller logic. The scaffolding writes
// a Deployment as an example
// Automatically generate RBAC rules to allow the Controller to read and write Deployments
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch
@@ -113,55 +106,5 @@ func (r *ReconcileCollector) Reconcile(request reconcile.Request) (reconcile.Res
return reconcile.Result{}, err
}
// TODO(user): Change this to be the object type created by your controller
// Define the desired Deployment object
deploy := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: instance.Name + "-deployment",
Namespace: instance.Namespace,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"deployment": instance.Name + "-deployment"},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"deployment": instance.Name + "-deployment"}},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "nginx",
Image: "nginx",
},
},
},
},
},
}
if err := controllerutil.SetControllerReference(instance, deploy, r.scheme); err != nil {
return reconcile.Result{}, err
}
// TODO(user): Change this for the object type created by your controller
// Check if the Deployment already exists
found := &appsv1.Deployment{}
err = r.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, found)
if err != nil && errors.IsNotFound(err) {
log.Info("Creating Deployment", "namespace", deploy.Namespace, "name", deploy.Name)
err = r.Create(context.TODO(), deploy)
return reconcile.Result{}, err
} else if err != nil {
return reconcile.Result{}, err
}
// TODO(user): Change this for the object type created by your controller
// Update the found object and write the result back if there are any changes
if !reflect.DeepEqual(deploy.Spec, found.Spec) {
found.Spec = deploy.Spec
log.Info("Updating Deployment", "namespace", deploy.Namespace, "name", deploy.Name)
err = r.Update(context.TODO(), found)
if err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}

View File

@@ -18,71 +18,8 @@ package collector
import (
"testing"
"time"
"github.com/onsi/gomega"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"golang.org/x/net/context"
appsv1 "k8s.io/api/apps/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
var c client.Client
var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: "foo", Namespace: "default"}}
var depKey = types.NamespacedName{Name: "foo-deployment", Namespace: "default"}
const timeout = time.Second * 5
func TestReconcile(t *testing.T) {
g := gomega.NewGomegaWithT(t)
instance := &troubleshootv1beta1.Collector{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}
// Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
// channel when it is finished.
mgr, err := manager.New(cfg, manager.Options{})
g.Expect(err).NotTo(gomega.HaveOccurred())
c = mgr.GetClient()
recFn, requests := SetupTestReconcile(newReconciler(mgr))
g.Expect(add(mgr, recFn)).NotTo(gomega.HaveOccurred())
stopMgr, mgrStopped := StartTestManager(mgr, g)
defer func() {
close(stopMgr)
mgrStopped.Wait()
}()
// Create the Collector object and expect the Reconcile and Deployment to be created
err = c.Create(context.TODO(), instance)
// The instance object may not be a valid object because it might be missing some required fields.
// Please modify the instance object by adding required fields and then remove the following if statement.
if apierrors.IsInvalid(err) {
t.Logf("failed to create object, got an invalid object error: %v", err)
return
}
g.Expect(err).NotTo(gomega.HaveOccurred())
defer c.Delete(context.TODO(), instance)
g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
deploy := &appsv1.Deployment{}
g.Eventually(func() error { return c.Get(context.TODO(), depKey, deploy) }, timeout).
Should(gomega.Succeed())
// Delete the Deployment and expect Reconcile to be called for Deployment deletion
g.Expect(c.Delete(context.TODO(), deploy)).NotTo(gomega.HaveOccurred())
g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
g.Eventually(func() error { return c.Get(context.TODO(), depKey, deploy) }, timeout).
Should(gomega.Succeed())
// Manually delete Deployment since GC isn't enabled in the test control plane
g.Eventually(func() error { return c.Delete(context.TODO(), deploy) }, timeout).
Should(gomega.MatchError("deployments.apps \"foo-deployment\" not found"))
}