Merge pull request #3 from replicatedhq/preflight

Preflights and preflightjobs
This commit is contained in:
divolgin
2019-07-15 11:31:51 -07:00
committed by GitHub
31 changed files with 1699 additions and 319 deletions

View File

@@ -79,9 +79,17 @@ snapshot-release:
.PHONY: local-release
local-release: snapshot-release
docker tag replicatedhq/troubleshoot:alpha localhost:32000/troubleshoot:alpha
docker tag replicatedhq/preflight:alpha localhost:32000/preflight:alpha
docker tag replicatedhq/troubleshoot-manager:alpha localhost:32000/troubleshoot-manager:alpha
docker tag replicated/troubleshoot:alpha localhost:32000/troubleshoot:alpha
docker tag replicated/preflight:alpha localhost:32000/preflight:alpha
docker tag replicated/troubleshoot-manager:alpha localhost:32000/troubleshoot-manager:alpha
docker push localhost:32000/troubleshoot:alpha
docker push localhost:32000/preflight:alpha
docker push localhost:32000/troubleshoot-manager:alpha
.PHONY: run-preflight
run-preflight: preflight
./bin/preflight run \
--collector-image=localhost:32000/troubleshoot:alpha \
--collector-pullpolicy=Always \
--image=localhost:32000/troubleshoot:alpha \
--pullpolicy=Always

View File

@@ -13,16 +13,17 @@ import (
func Server() *cobra.Command {
cmd := &cobra.Command{
Use: "server",
Short: "run the http server",
Long: `...`,
Use: "server",
Short: "run the http server",
Hidden: true,
Long: `...`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("port", cmd.Flags().Lookup("port"))
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
server.Serve(context.Background(), fmt.Sprintf(":%d", v.GetInt("port")))
server.ServeCollector(context.Background(), fmt.Sprintf(":%d", v.GetInt("port")))
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)

View File

@@ -0,0 +1,67 @@
package cli
import (
"fmt"
"io/ioutil"
"net/http"
"os"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func receivePreflightResults(preflightJobNamespace string, preflightJobName string) error {
// poll until there are no more "running" collectors
troubleshootClient, err := createTroubleshootK8sClient()
if err != nil {
return err
}
bundlePath, err := ioutil.TempDir("", "troubleshoot")
if err != nil {
return err
}
defer os.RemoveAll(bundlePath)
receivedPreflights := []string{}
for {
job, err := troubleshootClient.PreflightJobs(preflightJobNamespace).Get(preflightJobName, metav1.GetOptions{})
if err != nil && kuberneteserrors.IsNotFound(err) {
// where did it go!
return nil
} else if err != nil {
return err
}
for _, readyPreflight := range job.Status.AnalyzersSuccessful {
alreadyReceived := false
for _, receivedPreflight := range receivedPreflights {
if receivedPreflight == readyPreflight {
alreadyReceived = true
}
}
if alreadyReceived {
continue
}
preflightResp, err := http.Get(fmt.Sprintf("http://localhost:8000/preflight/%s", readyPreflight))
if err != nil {
return err
}
defer preflightResp.Body.Close()
body, err := ioutil.ReadAll(preflightResp.Body)
if err != nil {
return err
}
fmt.Printf("%s\n", body)
receivedPreflights = append(receivedPreflights, readyPreflight)
}
if len(job.Status.AnalyzersRunning) == 0 {
return nil
}
}
}

View File

@@ -11,15 +11,18 @@ import (
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.`,
Use: "preflight",
Short: "Run and retrieve preflight checks in a cluster",
Long: `A preflight check is a set of validations that can and should be run to ensure
that a cluster meets the requirements to run an application.`,
SilenceUsage: true,
}
cobra.OnInitialize(initConfig)
cmd.AddCommand(Run())
cmd.AddCommand(Server())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
return cmd
}
@@ -32,6 +35,6 @@ func InitAndExecute() {
}
func initConfig() {
viper.SetEnvPrefix("TROUBLESHOOT")
viper.SetEnvPrefix("PREFLIGHT")
viper.AutomaticEnv()
}

139
cmd/preflight/cli/run.go Normal file
View File

@@ -0,0 +1,139 @@
package cli
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/spf13/cobra"
"github.com/spf13/viper"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Run() *cobra.Command {
cmd := &cobra.Command{
Use: "run",
Short: "run a set of preflight checks in a cluster",
Long: `run preflight checks and return the results`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlags(cmd.Flags())
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
troubleshootClient, err := createTroubleshootK8sClient()
if err != nil {
return err
}
preflightName := v.GetString("preflight")
if preflightName == "" {
preflights, err := troubleshootClient.Preflights(v.GetString("namespace")).List(metav1.ListOptions{})
if err != nil {
return err
}
if len(preflights.Items) == 1 {
preflightName = preflights.Items[0].Name
}
}
if preflightName == "" {
return errors.New("unable to fly, try using the --preflight flags")
}
// generate a unique name
now := time.Now()
suffix := fmt.Sprintf("%d", now.Unix())
preflightJobName := fmt.Sprintf("%s-job-%s", preflightName, suffix[len(suffix)-4:])
preflightJob := troubleshootv1beta1.PreflightJob{
ObjectMeta: metav1.ObjectMeta{
Name: preflightJobName,
Namespace: v.GetString("namespace"),
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "preflightjob.troubleshoot.replicated.com",
},
Spec: troubleshootv1beta1.PreflightJobSpec{
Preflight: troubleshootv1beta1.PreflightRef{
Name: preflightName,
Namespace: v.GetString("namespace"),
},
Image: v.GetString("image"),
ImagePullPolicy: v.GetString("pullpolicy"),
CollectorImage: v.GetString("collector-image"),
CollectorImagePullPolicy: v.GetString("collector-pullpolicy"),
},
}
if _, err := troubleshootClient.PreflightJobs(v.GetString("namespace")).Create(&preflightJob); err != nil {
return err
}
// Poll the status of the Custom Resource for it to include a callback
var found *troubleshootv1beta1.PreflightJob
start := time.Now()
for {
current, err := troubleshootClient.PreflightJobs(v.GetString("namespace")).Get(preflightJobName, metav1.GetOptions{})
if err != nil && kuberneteserrors.IsNotFound(err) {
continue
} else if err != nil {
return err
}
if current.Status.IsServerReady {
found = current
break
}
if time.Now().Sub(start) > time.Duration(time.Second*10) {
return errors.New("preflightjob failed to start")
}
time.Sleep(time.Millisecond * 200)
}
// Connect to the callback
stopChan, err := k8sutil.PortForward(v.GetString("kubecontext"), 8000, 8000, found.Status.ServerPodNamespace, found.Status.ServerPodName)
if err != nil {
return err
}
if err := receivePreflightResults(found.Namespace, found.Name); err != nil {
return err
}
// Write
close(stopChan)
return nil
},
}
cmd.Flags().String("preflight", "", "name of the preflight to use")
cmd.Flags().String("namespace", "default", "namespace the preflight can be found in")
cmd.Flags().String("kubecontext", filepath.Join(homeDir(), ".kube", "config"), "the kubecontext to use when connecting")
cmd.Flags().String("image", "", "the full name of the preflight image to use")
cmd.Flags().String("pullpolicy", "", "the pull policy of the preflight image")
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
cmd.Flags().String("collector-pullpolicy", "", "the pull policy of the collector image")
viper.BindPFlags(cmd.Flags())
return cmd
}
func homeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE") // windows
}

View File

@@ -0,0 +1,43 @@
package cli
import (
"context"
"fmt"
"os"
"os/signal"
"github.com/replicatedhq/troubleshoot/pkg/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func Server() *cobra.Command {
cmd := &cobra.Command{
Use: "server",
Short: "run the http server",
Hidden: true,
Long: `...`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("port", cmd.Flags().Lookup("port"))
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
server.ServePreflight(context.Background(), fmt.Sprintf(":%d", v.GetInt("port")))
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
select {
case <-c:
return nil
}
},
}
cmd.Flags().Int("port", 8000, "port to listen on")
viper.BindPFlags(cmd.Flags())
return cmd
}

View File

@@ -0,0 +1,22 @@
package cli
import (
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
"github.com/spf13/viper"
"k8s.io/client-go/tools/clientcmd"
)
func createTroubleshootK8sClient() (*troubleshootclientv1beta1.TroubleshootV1beta1Client, error) {
v := viper.GetViper()
config, err := clientcmd.BuildConfigFromFlags("", v.GetString("kubecontext"))
if err != nil {
return nil, err
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(config)
if err != nil {
return nil, err
}
return troubleshootClient, nil
}

View File

@@ -390,12 +390,81 @@ spec:
type: string
type: object
spec:
properties:
collectorImage:
type: string
collectorImagePullPolicy:
type: string
imagePullPolicy:
type: string
preflight:
properties:
name:
type: string
namespace:
type: string
required:
- name
type: object
preflightImage:
type: string
required:
- preflight
type: object
status:
properties:
analyzersFailed:
items:
type: string
type: array
analyzersRunning:
items:
type: string
type: array
analyzersSuccessful:
items:
type: string
type: array
collectorsFailed:
items:
type: string
type: array
collectorsRunning:
items:
type: string
type: array
collectorsSuccessful:
items:
type: string
type: array
isAnalyzersComplete:
type: boolean
isServerReady:
type: boolean
serverPodName:
type: string
serverPodNamespace:
type: string
serverPodPort:
type: integer
required:
- isServerReady
- serverPodName
- serverPodNamespace
- serverPodPort
- collectorsRunning
- collectorsSuccessful
- collectorsFailed
- isAnalyzersComplete
- analyzersRunning
- analyzersSuccessful
- analyzersFailed
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -390,6 +390,98 @@ spec:
type: string
type: object
spec:
properties:
analyzers:
items:
properties:
clusterVersion:
properties:
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- outcomes
type: object
storageClass:
properties:
name:
type: string
outcomes:
items:
properties:
fail:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
pass:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
warn:
properties:
message:
type: string
uri:
type: string
when:
type: string
type: object
type: object
type: array
required:
- outcomes
- name
type: object
type: object
type: array
collectors:
items:
properties:
cluster-info:
type: object
cluster-resources:
type: object
type: object
type: array
type: object
status:
type: object

View File

@@ -8,6 +8,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = *in
if in.ClusterVersion != nil {
in, out := &in.ClusterVersion, &out.ClusterVersion
*out = new(ClusterVersion)
(*in).DeepCopyInto(*out)
}
if in.StorageClass != nil {
in, out := &in.StorageClass, &out.StorageClass
*out = new(StorageClass)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
func (in *Analyze) DeepCopy() *Analyze {
if in == nil {
return nil
}
out := new(Analyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyzer) DeepCopyInto(out *Analyzer) {
*out = *in
@@ -216,6 +241,32 @@ func (in *ClusterResources) DeepCopy() *ClusterResources {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterVersion) DeepCopyInto(out *ClusterVersion) {
*out = *in
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterVersion.
func (in *ClusterVersion) DeepCopy() *ClusterVersion {
if in == nil {
return nil
}
out := new(ClusterVersion)
in.DeepCopyInto(out)
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
@@ -445,12 +496,42 @@ 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 *Outcome) DeepCopyInto(out *Outcome) {
*out = *in
if in.Fail != nil {
in, out := &in.Fail, &out.Fail
*out = new(SingleOutcome)
**out = **in
}
if in.Warn != nil {
in, out := &in.Warn, &out.Warn
*out = new(SingleOutcome)
**out = **in
}
if in.Pass != nil {
in, out := &in.Pass, &out.Pass
*out = new(SingleOutcome)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Outcome.
func (in *Outcome) DeepCopy() *Outcome {
if in == nil {
return nil
}
out := new(Outcome)
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
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -478,7 +559,7 @@ func (in *PreflightJob) DeepCopyInto(out *PreflightJob) {
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightJob.
@@ -534,6 +615,7 @@ func (in *PreflightJobList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightJobSpec) DeepCopyInto(out *PreflightJobSpec) {
*out = *in
out.Preflight = in.Preflight
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightJobSpec.
@@ -549,6 +631,36 @@ func (in *PreflightJobSpec) DeepCopy() *PreflightJobSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightJobStatus) DeepCopyInto(out *PreflightJobStatus) {
*out = *in
if in.CollectorsRunning != nil {
in, out := &in.CollectorsRunning, &out.CollectorsRunning
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.CollectorsSuccessful != nil {
in, out := &in.CollectorsSuccessful, &out.CollectorsSuccessful
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.CollectorsFailed != nil {
in, out := &in.CollectorsFailed, &out.CollectorsFailed
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AnalyzersRunning != nil {
in, out := &in.AnalyzersRunning, &out.AnalyzersRunning
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AnalyzersSuccessful != nil {
in, out := &in.AnalyzersSuccessful, &out.AnalyzersSuccessful
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AnalyzersFailed != nil {
in, out := &in.AnalyzersFailed, &out.AnalyzersFailed
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightJobStatus.
@@ -593,9 +705,46 @@ func (in *PreflightList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightRef) DeepCopyInto(out *PreflightRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightRef.
func (in *PreflightRef) DeepCopy() *PreflightRef {
if in == nil {
return nil
}
out := new(PreflightRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightSpec) DeepCopyInto(out *PreflightSpec) {
*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)
}
}
}
if in.Analyzers != nil {
in, out := &in.Analyzers, &out.Analyzers
*out = make([]*Analyze, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Analyze)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightSpec.
@@ -622,3 +771,44 @@ 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 *SingleOutcome) DeepCopyInto(out *SingleOutcome) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SingleOutcome.
func (in *SingleOutcome) DeepCopy() *SingleOutcome {
if in == nil {
return nil
}
out := new(SingleOutcome)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = *in
if in.Outcome != nil {
in, out := &in.Outcome, &out.Outcome
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageClass.
func (in *StorageClass) DeepCopy() *StorageClass {
if in == nil {
return nil
}
out := new(StorageClass)
in.DeepCopyInto(out)
return out
}

View File

@@ -1,9 +1,51 @@
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Preflight
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: preflight-sample
spec:
# Add fields here
foo: bar
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< 1.13.0"
message: You need more kubernetes
- warn:
when: "< 1.15.0"
message: You have barely enough kubernetes
- pass:
message: Good job keeping k8s current
# - storageClass:
# name: "my-custom-storage-class"
# fail:
# message: The custom storage class thing was not found
# pass:
# message: All good on storage classes
# - manifests:
# - secret:
# namespace: default
# name: shhh
# key: top-secret
# fail:
# message: The top secret secret is missing
# pass:
# message: You know the secret
# - ingress:
# namespace: default
# name: connect-to-me
# fail:
# message: The ingress isn't ingressing
# pass:
# message: All systems ok on ingress
# - imagePullSecret:
# name: replicated
# namespace: my-app
# fail:
# message: Can't pull the images
# pass:
# message: Connected to docker registry
# - customResourceDefinitions:
# name: rook
# fail:
# message: You don't have rook installed
# pass:
# message: Found rook!

View File

@@ -1,9 +1,10 @@
apiVersion: troubleshoot.replicated.com/v1beta1
kind: PreflightJob
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: preflightjob-sample
spec:
# Add fields here
foo: bar
image: localhost:32000/troubleshoot:alpha
imagePullPolicy: Always
preflight:
name: preflight-sample
namespace: default

View File

@@ -84,19 +84,21 @@ archives:
dockers:
- dockerfile: ./deploy/Dockerfile.troubleshoot
image_templates:
- "replicatedhq/troubleshoot:alpha"
- "replicated/troubleshoot:alpha"
binaries:
- collector
- troubleshoot
- dockerfile: ./deploy/Dockerfile.preflight
- preflight
- dockerfile: ./deploy/Dockerfile.troubleshoot
image_templates:
- "replicatedhq/preflight:alpha"
- "replicated/preflight:alpha"
binaries:
- collector
- troubleshoot
- preflight
- dockerfile: ./deploy/Dockerfile.manager
image_templates:
- "replicatedhq/troubleshoot-manager:alpha"
- "replicated/troubleshoot-manager:alpha"
binaries:
- manager
snapshot:

View File

@@ -1,12 +0,0 @@
FROM debian:buster
WORKDIR /
RUN apt-get -qq update \
&& apt-get -qq -y install \
ca-certificates
COPY preflight /preflight/preflight
COPY collector /preflight/collector
ENV PATH="/preflight:${PATH}"

View File

@@ -7,6 +7,7 @@ RUN apt-get -qq update \
COPY troubleshoot /troubleshoot/troubleshoot
COPY collector /troubleshoot/collector
COPY preflight /troubleshoot/preflight
ENV PATH="/troubleshoot:${PATH}"

View File

@@ -0,0 +1,27 @@
package v1beta1
type SingleOutcome struct {
When string `json:"when,omitempty" yaml:"when,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
URI string `json:"uri,omitempty" yaml:"uri,omitempty"`
}
type Outcome struct {
Fail *SingleOutcome `json:"fail,omitempty" yaml:"fail,omitempty"`
Warn *SingleOutcome `json:"warn,omitempty" yaml:"warn,omitempty"`
Pass *SingleOutcome `json:"pass,omitempty" yaml:"pass,omitempty"`
}
type ClusterVersion struct {
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
}
type StorageClass struct {
Outcome []*Outcome `json:"outcomes" yaml:"outcomes"`
Name string `json:"name" yaml:"name"`
}
type Analyze struct {
ClusterVersion *ClusterVersion `json:"clusterVersion,omitempty" yaml:"clusterVersion,omitempty"`
StorageClass *StorageClass `json:"storageClass,omitempty" yaml:"supportBundle,omitempty"`
}

View File

@@ -0,0 +1,55 @@
package v1beta1
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
func TestAnalyze_Unmarshal(t *testing.T) {
tests := []struct {
name string
spec string
expectObject Analyze
}{
{
name: "clusterVersion",
spec: `clusterVersion:
outcomes:
- fail:
message: failed
- pass:
message: passed`,
expectObject: Analyze{
ClusterVersion: &ClusterVersion{
Outcomes: []*Outcome{
&Outcome{
Fail: &SingleOutcome{
Message: "failed",
},
},
&Outcome{
Pass: &SingleOutcome{
Message: "passed",
},
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
a := Analyze{}
err := yaml.Unmarshal([]byte(test.spec), &a)
req.NoError(err)
assert.Equal(t, test.expectObject, a)
})
}
}

View File

@@ -20,13 +20,10 @@ 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.
// PreflightSpec defines the desired state of Preflight
type PreflightSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Collectors []*Collect `json:"collectors,omitempty"`
Analyzers []*Analyze `json:"analyzers,omitempty"`
}
// PreflightStatus defines the observed state of Preflight

View File

@@ -20,26 +20,43 @@ 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.
type PreflightRef struct {
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
}
// PreflightJobSpec defines the desired state of PreflightJob
type PreflightJobSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Preflight PreflightRef `json:"preflight"`
Image string `json:"preflightImage,omitempty"`
ImagePullPolicy string `json:"imagePullPolicy,omitempty"`
CollectorImage string `json:"collectorImage,omitempty"`
CollectorImagePullPolicy string `json:"collectorImagePullPolicy,omitempty"`
}
// PreflightJobStatus defines the observed state of PreflightJob
type PreflightJobStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
IsServerReady bool `json:"isServerReady"`
ServerPodName string `json:"serverPodName"`
ServerPodNamespace string `json:"serverPodNamespace"`
ServerPodPort int `json:"serverPodPort"`
CollectorsRunning []string `json:"collectorsRunning"`
CollectorsSuccessful []string `json:"collectorsSuccessful"`
CollectorsFailed []string `json:"collectorsFailed"`
IsAnalyzersComplete bool `json:"isAnalyzersComplete"`
AnalyzersRunning []string `json:"analyzersRunning"`
AnalyzersSuccessful []string `json:"analyzersSuccessful"`
AnalyzersFailed []string `json:"analyzersFailed"`
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// PreflightJob is the Schema for the preflightjobs API
// +k8s:openapi-gen=true
// +kubebuilder:subresource:status
type PreflightJob struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

View File

@@ -24,6 +24,31 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyze) DeepCopyInto(out *Analyze) {
*out = *in
if in.ClusterVersion != nil {
in, out := &in.ClusterVersion, &out.ClusterVersion
*out = new(ClusterVersion)
(*in).DeepCopyInto(*out)
}
if in.StorageClass != nil {
in, out := &in.StorageClass, &out.StorageClass
*out = new(StorageClass)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyze.
func (in *Analyze) DeepCopy() *Analyze {
if in == nil {
return nil
}
out := new(Analyze)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Analyzer) DeepCopyInto(out *Analyzer) {
*out = *in
@@ -232,6 +257,32 @@ func (in *ClusterResources) DeepCopy() *ClusterResources {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterVersion) DeepCopyInto(out *ClusterVersion) {
*out = *in
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterVersion.
func (in *ClusterVersion) DeepCopy() *ClusterVersion {
if in == nil {
return nil
}
out := new(ClusterVersion)
in.DeepCopyInto(out)
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
@@ -461,12 +512,42 @@ 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 *Outcome) DeepCopyInto(out *Outcome) {
*out = *in
if in.Fail != nil {
in, out := &in.Fail, &out.Fail
*out = new(SingleOutcome)
**out = **in
}
if in.Warn != nil {
in, out := &in.Warn, &out.Warn
*out = new(SingleOutcome)
**out = **in
}
if in.Pass != nil {
in, out := &in.Pass, &out.Pass
*out = new(SingleOutcome)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Outcome.
func (in *Outcome) DeepCopy() *Outcome {
if in == nil {
return nil
}
out := new(Outcome)
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
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
@@ -494,7 +575,7 @@ func (in *PreflightJob) DeepCopyInto(out *PreflightJob) {
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightJob.
@@ -550,6 +631,7 @@ func (in *PreflightJobList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightJobSpec) DeepCopyInto(out *PreflightJobSpec) {
*out = *in
out.Preflight = in.Preflight
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightJobSpec.
@@ -565,6 +647,36 @@ func (in *PreflightJobSpec) DeepCopy() *PreflightJobSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightJobStatus) DeepCopyInto(out *PreflightJobStatus) {
*out = *in
if in.CollectorsRunning != nil {
in, out := &in.CollectorsRunning, &out.CollectorsRunning
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.CollectorsSuccessful != nil {
in, out := &in.CollectorsSuccessful, &out.CollectorsSuccessful
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.CollectorsFailed != nil {
in, out := &in.CollectorsFailed, &out.CollectorsFailed
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AnalyzersRunning != nil {
in, out := &in.AnalyzersRunning, &out.AnalyzersRunning
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AnalyzersSuccessful != nil {
in, out := &in.AnalyzersSuccessful, &out.AnalyzersSuccessful
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AnalyzersFailed != nil {
in, out := &in.AnalyzersFailed, &out.AnalyzersFailed
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightJobStatus.
@@ -609,9 +721,46 @@ func (in *PreflightList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightRef) DeepCopyInto(out *PreflightRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightRef.
func (in *PreflightRef) DeepCopy() *PreflightRef {
if in == nil {
return nil
}
out := new(PreflightRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PreflightSpec) DeepCopyInto(out *PreflightSpec) {
*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)
}
}
}
if in.Analyzers != nil {
in, out := &in.Analyzers, &out.Analyzers
*out = make([]*Analyze, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Analyze)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightSpec.
@@ -638,3 +787,44 @@ 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 *SingleOutcome) DeepCopyInto(out *SingleOutcome) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SingleOutcome.
func (in *SingleOutcome) DeepCopy() *SingleOutcome {
if in == nil {
return nil
}
out := new(SingleOutcome)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageClass) DeepCopyInto(out *StorageClass) {
*out = *in
if in.Outcome != nil {
in, out := &in.Outcome, &out.Outcome
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageClass.
func (in *StorageClass) DeepCopy() *StorageClass {
if in == nil {
return nil
}
out := new(StorageClass)
in.DeepCopyInto(out)
return out
}

View File

@@ -34,11 +34,6 @@ import (
var log = logf.Log.WithName("controller")
/**
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
* business logic. Delete these comments after modifying this file.*
*/
// Add creates a new Collector Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {

View File

@@ -0,0 +1,127 @@
package collectorjob
import (
"context"
"fmt"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func (r *ReconcileCollectorJob) createCollectorServer(instance *troubleshootv1beta1.CollectorJob) error {
name := fmt.Sprintf("%s-%s", instance.Name, "collector")
namespacedName := types.NamespacedName{
Name: name,
Namespace: instance.Namespace,
}
found := &corev1.Pod{}
err := r.Get(context.Background(), namespacedName, found)
if err == nil || !kuberneteserrors.IsNotFound(err) {
return err
}
imageName := "replicatedhq/troubleshoot:latest"
imagePullPolicy := corev1.PullAlways
if instance.Spec.Image != "" {
imageName = instance.Spec.Image
}
if instance.Spec.ImagePullPolicy != "" {
imagePullPolicy = corev1.PullPolicy(instance.Spec.ImagePullPolicy)
}
podLabels := make(map[string]string)
podLabels["collector"] = instance.Name
podLabels["troubleshoot-role"] = "collector"
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
Labels: podLabels,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Image: imageName,
ImagePullPolicy: imagePullPolicy,
Name: "collector",
Command: []string{"collector"},
Args: []string{"server"},
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8000,
},
},
},
},
},
}
if err := controllerutil.SetControllerReference(instance, &pod, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &pod); err != nil {
return err
}
service := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
Spec: corev1.ServiceSpec{
Selector: podLabels,
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
},
},
}
if err := controllerutil.SetControllerReference(instance, &service, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &service); err != nil {
return err
}
instance.Status.ServerPodName = name
instance.Status.ServerPodNamespace = instance.Namespace
instance.Status.ServerPodPort = 8000
instance.Status.IsServerReady = true
// wait for the server to be ready
// TODO
time.Sleep(time.Second * 5)
if err := r.Update(context.Background(), instance); err != nil {
return err
}
return nil
}

View File

@@ -37,7 +37,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
@@ -145,118 +144,6 @@ func (r *ReconcileCollectorJob) Reconcile(request reconcile.Request) (reconcile.
return reconcile.Result{}, nil
}
func (r *ReconcileCollectorJob) createCollectorServer(instance *troubleshootv1beta1.CollectorJob) error {
name := fmt.Sprintf("%s-%s", instance.Name, "collector")
namespacedName := types.NamespacedName{
Name: name,
Namespace: instance.Namespace,
}
found := &corev1.Pod{}
err := r.Get(context.Background(), namespacedName, found)
if err == nil || !kuberneteserrors.IsNotFound(err) {
return err
}
imageName := "replicatedhq/troubleshoot:latest"
imagePullPolicy := corev1.PullAlways
if instance.Spec.Image != "" {
imageName = instance.Spec.Image
}
if instance.Spec.ImagePullPolicy != "" {
imagePullPolicy = corev1.PullPolicy(instance.Spec.ImagePullPolicy)
}
podLabels := make(map[string]string)
podLabels["collector"] = instance.Name
podLabels["troubleshoot-role"] = "collector"
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
Labels: podLabels,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Image: imageName,
ImagePullPolicy: imagePullPolicy,
Name: "collector",
Command: []string{"collector"},
Args: []string{"server"},
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8000,
},
},
},
},
},
}
if err := controllerutil.SetControllerReference(instance, &pod, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &pod); err != nil {
return err
}
service := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
Spec: corev1.ServiceSpec{
Selector: podLabels,
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
},
},
}
if err := controllerutil.SetControllerReference(instance, &service, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &service); err != nil {
return err
}
instance.Status.ServerPodName = name
instance.Status.ServerPodNamespace = instance.Namespace
instance.Status.ServerPodPort = 8000
instance.Status.IsServerReady = true
// wait for the server to be ready
// TODO
time.Sleep(time.Second * 5)
if err := r.Update(context.Background(), instance); err != nil {
return err
}
return nil
}
func (r *ReconcileCollectorJob) getCollectorSpec(namespace string, name string) (*troubleshootv1beta1.Collector, error) {
cfg, err := config.GetConfig()
if err != nil {

View File

@@ -18,18 +18,12 @@ package preflight
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"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
"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"
@@ -39,11 +33,6 @@ import (
var log = logf.Log.WithName("controller")
/**
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
* business logic. Delete these comments after modifying this file.*
*/
// Add creates a new Preflight Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {
@@ -69,16 +58,6 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
return err
}
// TODO(user): Modify this to be the types you create
// Uncomment watch a Deployment created by Preflight - change this for objects you create
err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &troubleshootv1beta1.Preflight{},
})
if err != nil {
return err
}
return nil
}
@@ -104,7 +83,7 @@ func (r *ReconcilePreflight) Reconcile(request reconcile.Request) (reconcile.Res
instance := &troubleshootv1beta1.Preflight{}
err := r.Get(context.TODO(), request.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
if kuberneteserrors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
// For additional cleanup logic use finalizers.
return reconcile.Result{}, nil
@@ -113,55 +92,5 @@ func (r *ReconcilePreflight) 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

@@ -0,0 +1,70 @@
package preflightjob
import (
"context"
// "fmt"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
// "gopkg.in/yaml.v2"
// corev1 "k8s.io/api/core/v1"
// kuberneteserrors "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/controller/controllerutil"
)
type AnalysisResult struct {
Success bool
TextOutput string
}
func (r *ReconcilePreflightJob) reconcilePreflightAnalyzers(instance *troubleshootv1beta1.PreflightJob, preflight *troubleshootv1beta1.Preflight) error {
for _, analyzer := range preflight.Spec.Analyzers {
if err := r.reconcileOnePreflightAnalyzer(instance, analyzer); err != nil {
return err
}
}
return nil
}
func (r *ReconcilePreflightJob) reconcileOnePreflightAnalyzer(instance *troubleshootv1beta1.PreflightJob, analyze *troubleshootv1beta1.Analyze) error {
if contains(instance.Status.AnalyzersRunning, idForAnalyzer(analyze)) {
// these are the analyzers we want to attempt to run
if analyze.ClusterVersion != nil {
result, err := r.analyzeClusterVersion(instance, analyze.ClusterVersion)
if err != nil {
return err
}
if result.Success == false {
return nil // collectors are not yet ready
}
instance.Status.AnalyzersSuccessful = append(instance.Status.AnalyzersSuccessful, idForAnalyzer(analyze))
instance.Status.AnalyzersRunning = remove(instance.Status.AnalyzersRunning, idForAnalyzer(analyze))
if err := r.Update(context.Background(), instance); err != nil {
return err
}
return nil
}
return nil
}
return nil
}
func idForAnalyzer(analyzer *troubleshootv1beta1.Analyze) string {
if analyzer.ClusterVersion != nil {
return "cluster-version"
}
if analyzer.StorageClass != nil {
return "storage-classes"
}
return ""
}

View File

@@ -0,0 +1,11 @@
package preflightjob
import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
)
func (r *ReconcilePreflightJob) analyzeClusterVersion(instance *troubleshootv1beta1.PreflightJob, clusterVersion *troubleshootv1beta1.ClusterVersion) (*AnalysisResult, error) {
return &AnalysisResult{
Success: true,
}, nil
}

View File

@@ -0,0 +1,233 @@
package preflightjob
import (
"context"
"fmt"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "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/controller/controllerutil"
)
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))
if err := r.reconcileOnePreflightCollector(instance, collector); err != nil {
return err
}
}
if !contains(requestedCollectorIDs, "cluster-info") {
clusterInfo := troubleshootv1beta1.Collect{
ClusterInfo: &troubleshootv1beta1.ClusterInfo{},
}
if err := r.reconcileOnePreflightCollector(instance, &clusterInfo); err != nil {
return err
}
}
if !contains(requestedCollectorIDs, "cluster-resources") {
clusterResources := troubleshootv1beta1.Collect{
ClusterResources: &troubleshootv1beta1.ClusterResources{},
}
if err := r.reconcileOnePreflightCollector(instance, &clusterResources); err != nil {
return err
}
}
return nil
}
func (r *ReconcilePreflightJob) reconcileOnePreflightCollector(instance *troubleshootv1beta1.PreflightJob, collect *troubleshootv1beta1.Collect) error {
if contains(instance.Status.CollectorsRunning, idForCollector(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
//
// in the very common use case (what we are building for today)
// there's not too much risk in something destroying and reaping that stopped pod
// 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))
if err := r.Update(context.Background(), instance); err != nil {
return err
}
return nil
}
if err := r.createCollectorSpecInConfigMap(instance, collect); err != nil {
return err
}
if err := r.createCollectorPod(instance, collect); err != nil {
return err
}
return nil
}
func (r *ReconcilePreflightJob) createCollectorSpecInConfigMap(instance *troubleshootv1beta1.PreflightJob, collector *troubleshootv1beta1.Collect) error {
name := fmt.Sprintf("%s-%s", instance.Name, idForCollector(collector))
namespacedName := types.NamespacedName{
Name: name,
Namespace: instance.Namespace,
}
found := &corev1.ConfigMap{}
err := r.Get(context.Background(), namespacedName, found)
if err == nil || !kuberneteserrors.IsNotFound(err) {
return err
}
specContents, err := yaml.Marshal(collector)
if err != nil {
return err
}
specData := make(map[string]string)
specData["collector.yaml"] = string(specContents)
configMap := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
Data: specData,
}
if err := controllerutil.SetControllerReference(instance, &configMap, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &configMap); err != nil {
return err
}
return nil
}
func (r *ReconcilePreflightJob) createCollectorPod(instance *troubleshootv1beta1.PreflightJob, collector *troubleshootv1beta1.Collect) error {
name := fmt.Sprintf("%s-%s", instance.Name, idForCollector(collector))
namespacedName := types.NamespacedName{
Name: name,
Namespace: instance.Namespace,
}
found := &corev1.Pod{}
err := r.Get(context.Background(), namespacedName, found)
if err == nil || !kuberneteserrors.IsNotFound(err) {
return err
}
imageName := "replicatedhq/troubleshoot:latest"
imagePullPolicy := corev1.PullAlways
if instance.Spec.Image != "" {
imageName = instance.Spec.Image
}
if instance.Spec.ImagePullPolicy != "" {
imagePullPolicy = corev1.PullPolicy(instance.Spec.ImagePullPolicy)
}
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Image: imageName,
ImagePullPolicy: imagePullPolicy,
Name: idForCollector(collector),
Command: []string{"collector"},
Args: []string{
"run",
"--collector",
"/troubleshoot/specs/collector.yaml",
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "collector",
MountPath: "/troubleshoot/specs",
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "collector",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: name,
},
},
},
},
},
},
}
if err := controllerutil.SetControllerReference(instance, &pod, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &pod); err != nil {
return err
}
instance.Status.CollectorsRunning = append(instance.Status.CollectorsRunning, idForCollector(collector))
if err := r.Update(context.Background(), instance); err != nil {
return err
}
return nil
}
// 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"
} else if collector.ClusterResources != nil {
return "cluster-resources"
}
return ""
}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func remove(s []string, r string) []string {
for i, v := range s {
if v == r {
return append(s[:i], s[i+1:]...)
}
}
return s
}

View File

@@ -0,0 +1,126 @@
package preflightjob
import (
"context"
"fmt"
"time"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func (r *ReconcilePreflightJob) createPreflightServer(instance *troubleshootv1beta1.PreflightJob) error {
name := fmt.Sprintf("%s-%s", instance.Name, "preflight")
namespacedName := types.NamespacedName{
Name: name,
Namespace: instance.Namespace,
}
found := &corev1.Pod{}
err := r.Get(context.Background(), namespacedName, found)
if err == nil || !kuberneteserrors.IsNotFound(err) {
return err
}
imageName := "replicatedhq/troubleshoot:latest"
imagePullPolicy := corev1.PullAlways
if instance.Spec.Image != "" {
imageName = instance.Spec.Image
}
if instance.Spec.ImagePullPolicy != "" {
imagePullPolicy = corev1.PullPolicy(instance.Spec.ImagePullPolicy)
}
podLabels := make(map[string]string)
podLabels["preflight"] = instance.Name
podLabels["troubleshoot-role"] = "preflight"
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
Labels: podLabels,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Image: imageName,
ImagePullPolicy: imagePullPolicy,
Name: "preflight",
Command: []string{"preflight"},
Args: []string{"server"},
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8000,
},
},
},
},
},
}
if err := controllerutil.SetControllerReference(instance, &pod, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &pod); err != nil {
return err
}
service := corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: instance.Namespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
Spec: corev1.ServiceSpec{
Selector: podLabels,
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
},
},
}
if err := controllerutil.SetControllerReference(instance, &service, r.scheme); err != nil {
return err
}
if err := r.Create(context.Background(), &service); err != nil {
return err
}
instance.Status.ServerPodName = name
instance.Status.ServerPodNamespace = instance.Namespace
instance.Status.ServerPodPort = 8000
instance.Status.IsServerReady = true
// wait for the server to be ready
// TODO
time.Sleep(time.Second * 5)
if err := r.Update(context.Background(), instance); err != nil {
return err
}
return nil
}

View File

@@ -18,18 +18,15 @@ package preflightjob
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"
troubleshootclientv1beta1 "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/typed/troubleshoot/v1beta1"
kuberneteserrors "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/client/config"
"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"
@@ -39,11 +36,6 @@ import (
var log = logf.Log.WithName("controller")
/**
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
* business logic. Delete these comments after modifying this file.*
*/
// Add creates a new PreflightJob Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {
@@ -69,16 +61,6 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
return err
}
// TODO(user): Modify this to be the types you create
// Uncomment watch a Deployment created by PreflightJob - change this for objects you create
err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &troubleshootv1beta1.PreflightJob{},
})
if err != nil {
return err
}
return nil
}
@@ -104,7 +86,7 @@ func (r *ReconcilePreflightJob) Reconcile(request reconcile.Request) (reconcile.
instance := &troubleshootv1beta1.PreflightJob{}
err := r.Get(context.TODO(), request.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
if kuberneteserrors.IsNotFound(err) {
// Object not found, return. Created objects are automatically garbage collected.
// For additional cleanup logic use finalizers.
return reconcile.Result{}, nil
@@ -113,55 +95,65 @@ func (r *ReconcilePreflightJob) Reconcile(request reconcile.Request) (reconcile.
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 {
if !instance.Status.IsServerReady {
if err := r.createPreflightServer(instance); err != nil {
return reconcile.Result{}, err
}
}
namespace := instance.Namespace
if instance.Spec.Preflight.Namespace != "" {
namespace = instance.Spec.Preflight.Namespace
}
preflightSpec, err := r.getPreflightSpec(namespace, instance.Spec.Preflight.Name)
if err != nil {
return reconcile.Result{}, err
}
if len(instance.Status.AnalyzersRunning) == 0 && len(instance.Status.AnalyzersSuccessful) == 0 && len(instance.Status.AnalyzersFailed) == 0 {
// Add them all!
analyzersRunning := []string{}
for _, analyzer := range preflightSpec.Spec.Analyzers {
analyzersRunning = append(analyzersRunning, idForAnalyzer(analyzer))
}
instance.Status.AnalyzersRunning = analyzersRunning
if err := r.Update(context.Background(), instance); err != nil {
return reconcile.Result{}, err
}
}
if err := r.reconcilePreflightCollectors(instance, preflightSpec); err != nil {
return reconcile.Result{}, err
}
if err := r.reconcilePreflightAnalyzers(instance, preflightSpec); err != nil {
return reconcile.Result{}, err
}
// just finished, nothing to do
return reconcile.Result{}, nil
}
func (r *ReconcilePreflightJob) getPreflightSpec(namespace string, name string) (*troubleshootv1beta1.Preflight, error) {
cfg, err := config.GetConfig()
if err != nil {
return nil, err
}
troubleshootClient, err := troubleshootclientv1beta1.NewForConfig(cfg)
if err != nil {
return nil, err
}
preflight, err := troubleshootClient.Preflights(namespace).Get(name, metav1.GetOptions{})
if err != nil {
if kuberneteserrors.IsNotFound(err) {
return nil, nil
}
return nil, err
}
return preflight, nil
}

View File

@@ -10,9 +10,9 @@ import (
"github.com/gin-gonic/gin"
)
var queue = make(map[string][]byte)
var collectorQueue = make(map[string][]byte)
func Serve(ctx context.Context, address string) {
func ServeCollector(ctx context.Context, address string) {
g := gin.New()
root := g.Group("/")
@@ -35,20 +35,20 @@ func putCollectorOutput(c *gin.Context) {
return
}
queue[collectorID] = body
collectorQueue[collectorID] = body
fmt.Printf("queue = %#v\n", queue)
fmt.Printf("collectorQueue = %#v\n", collectorQueue)
c.Status(201)
}
func getCollectorOutput(c *gin.Context) {
encoded := base64.StdEncoding.EncodeToString(queue[c.Param("id")])
encoded := base64.StdEncoding.EncodeToString(collectorQueue[c.Param("id")])
c.String(200, encoded)
}
func getQueuedCollectors(c *gin.Context) {
keys := make([]string, 0, len(queue))
for k := range queue {
keys := make([]string, 0, len(collectorQueue))
for k := range collectorQueue {
keys = append(keys, k)
}

56
pkg/server/preflight.go Normal file
View File

@@ -0,0 +1,56 @@
package server
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"github.com/gin-gonic/gin"
)
var preflightQueue = make(map[string][]byte)
func ServePreflight(ctx context.Context, address string) {
g := gin.New()
root := g.Group("/")
root.PUT("/", putPreflightOutput)
root.GET("/", getQueuedPreflights)
root.GET("/preflight/:id", getPreflightOutput)
srvr := http.Server{Addr: address, Handler: g}
go func() {
srvr.ListenAndServe()
}()
}
func putPreflightOutput(c *gin.Context) {
preflightID := c.Request.Header.Get("collector-id")
body, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithStatus(500)
return
}
preflightQueue[preflightID] = body
fmt.Printf("preflightQueue = %#v\n", preflightQueue)
c.Status(201)
}
func getPreflightOutput(c *gin.Context) {
encoded := base64.StdEncoding.EncodeToString(preflightQueue[c.Param("id")])
c.String(200, encoded)
}
func getQueuedPreflights(c *gin.Context) {
keys := make([]string, 0, len(preflightQueue))
for k := range preflightQueue {
keys = append(keys, k)
}
c.JSON(200, keys)
}