diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9d9acc74..7e94e0e6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,6 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 with: go-version: "${{ env.GO_VERSION }}" @@ -43,6 +44,23 @@ jobs: - name: Upload coverage report (codcov.io) run: bash <(curl -s https://codecov.io/bash) + integration: + name: Test - Integration + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "${{ env.GO_VERSION }}" + cache: true + + - name: Run integration tests + run: make test-integration + + - name: Upload coverage report (codcov.io) + run: bash <(curl -s https://codecov.io/bash) + build: name: Build strategy: @@ -69,7 +87,7 @@ jobs: e2e: name: Test - E2E runs-on: ubuntu-latest - needs: [test] + needs: [test, integration] steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index c4ec373a..2c2304a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ -certs/ -/cmd/hegel/hegel .env out/ /hegel-* .vscode /coverage.out +/bin diff --git a/Makefile b/Makefile index 888b2b9d..803165a1 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +# Configure the Make shell for recipe invocations. +SHELL := bash + # Specify the target architecture to build the binary for. (Recipes: build, image) GOARCH ?= $(shell go env GOARCH) @@ -35,6 +38,30 @@ test: ## Run unit tests. test-e2e: ## Run E2E tests. go test $(GO_TEST_ARGS) -tags=e2e -coverprofile=coverage.out ./internal/e2e +# Version should match with whatever we consume in sources (check the go.mod). +SETUP_ENVTEST := go run sigs.k8s.io/controller-runtime/tools/setup-envtest@latest +ENVTEST_BIN_DIR := $(shell pwd)/bin + +# The kubernetes version to use with envtest. Overridable when invoking make. +# E.g. make ENVTEST_KUBE_VERSION=1.24 test-integration +ENVTEST_KUBE_VERSION ?= 1.25 + +.PHONY: setup-envtest +setup-envtest: + @echo Installing Kubernetes $(ENVTEST_KUBE_VERSION) binaries into $(ENVTEST_BIN_DIR); \ + $(SETUP_ENVTEST) use --bin-dir $(ENVTEST_BIN_DIR) $(ENVTEST_KUBE_VERSION) + +# Integration tests are located next to unit test. This recipe will search the code base for +# files including the "//go:build integration" build tag and build them into the test binary. +# For packages containing both unit and integration tests its recommended to populate +# "//go:build !integration" in all unit test sources so as to avoid compiling them in this recipe. +.PHONY: test-integration +test-integration: setup-envtest +test-integration: TEST_DIRS := $(shell grep -R --include="*.go" -l -E "//go:build.*\sintegration" . | xargs dirname | uniq) +test-integration: ## Run integration tests. + source <($(SETUP_ENVTEST) use -p env --bin-dir $(ENVTEST_BIN_DIR) $(ENVTEST_KUBE_VERSION)); \ + go test $(GO_TEST_ARGS) -tags=integration -coverprofile=coverage.out $(TEST_DIRS) + # When we build the image its Linux based. This means we need a Linux binary hence we need to export # GOOS so we have compatible binary. .PHONY: image diff --git a/go.mod b/go.mod index 1ac18558..37416f46 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/tinkerbell/tink v0.8.0 google.golang.org/grpc v1.50.1 // indirect gopkg.in/yaml.v2 v2.4.0 - k8s.io/apimachinery v0.25.4 // indirect + k8s.io/apimachinery v0.25.4 k8s.io/client-go v0.25.4 sigs.k8s.io/controller-runtime v0.13.1 ) diff --git a/internal/backend/backend.go b/internal/backend/backend.go index 22b62484..468ba1dd 100644 --- a/internal/backend/backend.go +++ b/internal/backend/backend.go @@ -37,16 +37,12 @@ func New(ctx context.Context, opts Options) (Client, error) { return flatfile.FromYAMLFile(opts.Flatfile.Path) case opts.Kubernetes != nil: - config, err := kubernetes.NewConfig( - opts.Kubernetes.Kubeconfig, - opts.Kubernetes.KubeAPI, - opts.Kubernetes.KubeNamespace, - ) - if err != nil { - return nil, fmt.Errorf("loading kubernetes config: %v", err) - } - - kubeclient, err := kubernetes.NewBackend(config) + kubeclient, err := kubernetes.NewBackend(kubernetes.BackendConfig{ + Kubeconfig: opts.Kubernetes.Kubeconfig, + APIServerAddress: opts.Kubernetes.APIServerAddress, + Namespace: opts.Kubernetes.Namespace, + Context: ctx, + }) if err != nil { return nil, fmt.Errorf("kubernetes client: %v", err) } @@ -94,9 +90,9 @@ type FlatfileOptions struct { // KubernetesOptions is the configuration for a Kubernetes backend. type KubernetesOptions struct { - // KubeAPI is the URL of the Kube API the Kubernetes client talks to. + // APIServerAddress is the URL of the Kube API the Kubernetes client talks to. // Optional - KubeAPI string + APIServerAddress string // Kuberconfig is a path to a Kubeconfig file used by the Kubernetes client. // Optional @@ -104,5 +100,5 @@ type KubernetesOptions struct { // KubeNamespace is a namespace override to have Hegel use for reading resources. // Optional - KubeNamespace string + Namespace string } diff --git a/internal/backend/kubernetes/backend.go b/internal/backend/kubernetes/backend.go index 11c8bbb6..3b780030 100644 --- a/internal/backend/kubernetes/backend.go +++ b/internal/backend/kubernetes/backend.go @@ -8,6 +8,9 @@ import ( "github.com/tinkerbell/hegel/internal/frontend/ec2" tinkv1 "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" tinkcontrollers "github.com/tinkerbell/tink/pkg/controllers" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" crclient "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -26,36 +29,70 @@ type Backend struct { // NewBackend creates a new Backend instance. It launches a goroutine to perform synchronization // between the cluster and internal caches. Consumers can wait for the initial sync using WaitForCachesync(). // See k8s.io/Backend-go/tools/Backendcmd for constructing *rest.Config objects. -func NewBackend(cfg Config) (*Backend, error) { +func NewBackend(cfg BackendConfig) (*Backend, error) { opts := tinkcontrollers.GetServerOptions() opts.Namespace = cfg.Namespace - // Use a manager from the tink project so we can take advantage of the indexes and caching it configures. - // Once started, we don't really need any of the manager capabilities hence we don't store it in the - // Backend - manager, err := tinkcontrollers.NewManager(cfg.Config, opts) - if err != nil { - return nil, err - } - // Default the context. if cfg.Context == nil { cfg.Context = context.Background() } + clientConfig := cfg.ClientConfig + + // If no client was specified, build one and configure the backend with it including waiting + // for the caches to sync. + if cfg.ClientConfig == nil { + restConfig, err := loadConfig(cfg) + if err != nil { + return nil, err + } + clientConfig = restConfig + } + + // Use a manager from the tink project so we can take advantage of the indexes and caching it + // configures. Once started, we don't really need any of the manager capabilities hence we don't + // store it in the Backend. + manager, err := tinkcontrollers.NewManager(clientConfig, opts) + if err != nil { + return nil, err + } + + // TODO(chrisdoherty4) Stop panicing on error. This will likely require exposing Start in + // some capacity and allowing the caller to handle the error. go func() { if err := manager.Start(cfg.Context); err != nil { panic(err) } }() - backend := &Backend{ - client: manager.GetClient(), + return &Backend{ closer: cfg.Context.Done(), + client: manager.GetClient(), WaitForCacheSync: manager.GetCache().WaitForCacheSync, + }, nil +} + +func loadConfig(cfg BackendConfig) (*rest.Config, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.ExplicitPath = cfg.Kubeconfig + + overrides := &clientcmd.ConfigOverrides{ + ClusterInfo: clientcmdapi.Cluster{ + Server: cfg.APIServerAddress, + }, + Context: clientcmdapi.Context{ + Namespace: cfg.Namespace, + }, + } + + loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) + config, err := loader.ClientConfig() + if err != nil { + return nil, err } - return backend, nil + return config, nil } // IsHealthy returns true until the context used to create the Backend is cancelled. diff --git a/internal/backend/kubernetes/backend_integration_test.go b/internal/backend/kubernetes/backend_integration_test.go new file mode 100644 index 00000000..c34162f1 --- /dev/null +++ b/internal/backend/kubernetes/backend_integration_test.go @@ -0,0 +1,101 @@ +//go:build integration + +package kubernetes_test + +import ( + "context" + "testing" + + . "github.com/tinkerbell/hegel/internal/backend/kubernetes" + tinkv1 "github.com/tinkerbell/tink/pkg/apis/core/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +// TestBackend performs a simple sanity check on the backend initializing constructor to ensure +// it can in-fact talk to a real API server. More rigerous testing of business logic is performed +// in unit tests. +func TestBackend(t *testing.T) { + // Configure a test environment and launch it. + scheme := runtime.NewScheme() + if err := tinkv1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + + env := envtest.Environment{ + Scheme: scheme, + CRDDirectoryPaths: []string{ + // CRDs are not automatically updated and will require manual updates whenever + // we bump our Tink repository dependency version. + "testdata/integration", + }, + } + + cfg, err := env.Start() + if err != nil { + t.Fatal(err) + } + defer func() { + if err := env.Stop(); err != nil { + t.Logf("Stopping test env: %v", err) + } + }() + + // Build a client and add a Hardware resource. + client, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + t.Fatal(err) + } + + const ip = "10.10.10.10" + const hostname = "foobar" + + hw := tinkv1.Hardware{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: tinkv1.HardwareSpec{ + Interfaces: []tinkv1.Interface{ + { + DHCP: &tinkv1.DHCP{ + IP: &tinkv1.IP{ + Address: ip, + Family: 4, + }, + }, + }, + }, + Metadata: &tinkv1.HardwareMetadata{ + Instance: &tinkv1.MetadataInstance{ + Hostname: hostname, + }, + }, + }, + } + + if err := client.Create(context.Background(), &hw); err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Construct the backend and attempt to retrieve our test Hardware resource. + backend, err := NewBackend(BackendConfig{Context: ctx, ClientConfig: cfg}) + if err != nil { + t.Fatal(err) + } + backend.WaitForCacheSync(ctx) + + instance, err := backend.GetEC2Instance(ctx, ip) + if err != nil { + t.Fatal(err) + } + + if instance.Metadata.Hostname != hostname { + t.Fatalf("Expected Hostname: %s; Received Hostname: %s\n", instance.Metadata.Hostname, hostname) + } +} diff --git a/internal/backend/kubernetes/backend_test.go b/internal/backend/kubernetes/backend_test.go index 189c3bd3..8c0a735e 100644 --- a/internal/backend/kubernetes/backend_test.go +++ b/internal/backend/kubernetes/backend_test.go @@ -1,3 +1,5 @@ +//go:build !integration + package kubernetes_test import ( diff --git a/internal/backend/kubernetes/config.go b/internal/backend/kubernetes/config.go index 1a5f9e7f..885d3560 100644 --- a/internal/backend/kubernetes/config.go +++ b/internal/backend/kubernetes/config.go @@ -4,52 +4,27 @@ import ( "context" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -// Config used by the NewBackend function family. -type Config struct { - // Config is the Kubernetes client configuration. - *rest.Config - - // Namespace restricts the scope of the backend such that Hardware objects are retrieved from - // this namespace only. Defaults to "default". - Namespace string - +// BackendConfig used by the NewBackend function family. +type BackendConfig struct { // Context is the context used by the Kubernetes client. Defaults to context.Background(). // When specified it controls the lifetime of the Kubernetes client by shutting the client // down when it cancelled. Context context.Context -} - -// NewConfig loads the kubeconfig overriding it with kubeAPI. -func NewConfig(kubeconfig, kubeAPI, namespace string) (Config, error) { - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - loadingRules.ExplicitPath = kubeconfig - overrides := &clientcmd.ConfigOverrides{ - ClusterInfo: clientcmdapi.Cluster{ - Server: kubeAPI, - }, - Context: clientcmdapi.Context{ - Namespace: namespace, - }, - } + // Kubeconfig is a path to a valid kubeconfig file. When in-cluster defaults to the in-cluster + // config. Optional. + Kubeconfig string - kubeBackendCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) - config, err := kubeBackendCfg.ClientConfig() - if err != nil { - return Config{}, err - } + // APIServerAddress is the address of the kubernetes cluster (https://hostname:port). Optional. + APIServerAddress string - namespace, _, err = kubeBackendCfg.Namespace() - if err != nil { - return Config{}, err - } + // Namespace restricts the scope of the backend such that Hardware objects are retrieved from + // this namespace only. Optional. + Namespace string - return Config{ - Config: config, - Namespace: namespace, - }, nil + // ClientConfig is a Kubernetes client config. If specified, it will be used instead of + // constructing a client using the other configuration in this object. Optional. + ClientConfig *rest.Config } diff --git a/internal/backend/kubernetes/testdata/integration/tink.crd.yml b/internal/backend/kubernetes/testdata/integration/tink.crd.yml new file mode 100644 index 00000000..f3894c9b --- /dev/null +++ b/internal/backend/kubernetes/testdata/integration/tink.crd.yml @@ -0,0 +1,748 @@ +# These CRDs were copied from v0.8 of the Tink repository. +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: hardware.tinkerbell.org +spec: + group: tinkerbell.org + names: + categories: + - tinkerbell + kind: Hardware + listKind: HardwareList + plural: hardware + shortNames: + - hw + singular: hardware + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Hardware is the Schema for the Hardware API. + 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/sig-architecture/api-conventions.md#resources" + 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/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: HardwareSpec defines the desired state of Hardware. + properties: + bmcRef: + description: + BMCRef contains a relation to a BMC state management + type in the same namespace as the Hardware. This may be used for + BMC management by orchestrators. + properties: + apiGroup: + description: + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in + the core API group. For any other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + disks: + items: + description: Disk represents a disk device for Tinkerbell Hardware. + properties: + device: + type: string + type: object + type: array + interfaces: + items: + description: + Interface represents a network interface configuration + for Hardware. + properties: + dhcp: + description: DHCP configuration. + properties: + arch: + type: string + hostname: + type: string + iface_name: + type: string + ip: + description: IP configuration. + properties: + address: + type: string + family: + format: int64 + type: integer + gateway: + type: string + netmask: + type: string + type: object + lease_time: + format: int64 + type: integer + mac: + pattern: ([0-9a-f]{2}[:]){5}([0-9a-f]{2}) + type: string + name_servers: + items: + type: string + type: array + time_servers: + items: + type: string + type: array + uefi: + type: boolean + vlan_id: + description: + validation pattern for VLANDID is a string + number between 0-4096 + pattern: ^(([0-9][0-9]{0,2}|[1-3][0-9][0-9][0-9]|40([0-8][0-9]|9[0-6]))(,[1-9][0-9]{0,2}|[1-3][0-9][0-9][0-9]|40([0-8][0-9]|9[0-6]))*)$ + type: string + type: object + netboot: + description: Netboot configuration. + properties: + allowPXE: + type: boolean + allowWorkflow: + type: boolean + ipxe: + description: IPXE configuration. + properties: + contents: + type: string + url: + type: string + type: object + osie: + description: OSIE configuration. + properties: + baseURL: + type: string + initrd: + type: string + kernel: + type: string + type: object + type: object + type: object + type: array + metadata: + properties: + bonding_mode: + format: int64 + type: integer + custom: + properties: + preinstalled_operating_system_version: + properties: + distro: + type: string + image_tag: + type: string + os_slug: + type: string + slug: + type: string + version: + type: string + type: object + private_subnets: + items: + type: string + type: array + type: object + facility: + properties: + facility_code: + type: string + plan_slug: + type: string + plan_version_slug: + type: string + type: object + instance: + properties: + allow_pxe: + type: boolean + always_pxe: + type: boolean + crypted_root_password: + type: string + hostname: + type: string + id: + type: string + ips: + items: + properties: + address: + type: string + family: + format: int64 + type: integer + gateway: + type: string + management: + type: boolean + netmask: + type: string + public: + type: boolean + type: object + type: array + ipxe_script_url: + type: string + network_ready: + type: boolean + operating_system: + properties: + distro: + type: string + image_tag: + type: string + os_slug: + type: string + slug: + type: string + version: + type: string + type: object + rescue: + type: boolean + ssh_keys: + items: + type: string + type: array + state: + type: string + storage: + properties: + disks: + items: + properties: + device: + type: string + partitions: + items: + properties: + label: + type: string + number: + format: int64 + type: integer + size: + format: int64 + type: integer + start: + format: int64 + type: integer + type_guid: + type: string + type: object + type: array + wipe_table: + type: boolean + type: object + type: array + filesystems: + items: + properties: + mount: + properties: + create: + properties: + force: + type: boolean + options: + items: + type: string + type: array + type: object + device: + type: string + files: + items: + properties: + contents: + type: string + gid: + format: int64 + type: integer + mode: + format: int64 + type: integer + path: + type: string + uid: + format: int64 + type: integer + type: object + type: array + format: + type: string + point: + type: string + type: object + type: object + type: array + raid: + items: + properties: + devices: + items: + type: string + type: array + level: + type: string + name: + type: string + spare: + format: int64 + type: integer + type: object + type: array + type: object + tags: + items: + type: string + type: array + userdata: + type: string + type: object + manufacturer: + properties: + id: + type: string + slug: + type: string + type: object + state: + type: string + type: object + resources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: + Resources represents known resources that are available + on a machine. Resources may be used for scheduling by orchestrators. + type: object + tinkVersion: + format: int64 + type: integer + userData: + description: + UserData is the user data to configure in the hardware's + metadata + type: string + vendorData: + description: + VendorData is the vendor data to configure in the hardware's + metadata + type: string + type: object + status: + description: HardwareStatus defines the observed state of Hardware. + properties: + state: + description: HardwareState represents the hardware state. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: templates.tinkerbell.org +spec: + group: tinkerbell.org + names: + categories: + - tinkerbell + kind: Template + listKind: TemplateList + plural: templates + shortNames: + - tpl + singular: template + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Template is the Schema for the Templates API. + 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/sig-architecture/api-conventions.md#resources" + 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/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: TemplateSpec defines the desired state of Template. + properties: + data: + type: string + type: object + status: + description: TemplateStatus defines the observed state of Template. + properties: + state: + description: TemplateState represents the template state. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: workflowdata.tinkerbell.org +spec: + group: tinkerbell.org + names: + categories: + - tinkerbell + kind: WorkflowData + listKind: WorkflowDataList + plural: workflowdata + shortNames: + - wfdata + singular: workflowdata + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Workflow is the Schema for the Workflows API. + 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/sig-architecture/api-conventions.md#resources" + 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/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: WorkflowSpec defines the desired state of Workflow. + properties: + hardwareMap: + additionalProperties: + type: string + description: A mapping of template devices to hadware mac addresses + type: object + hardwareRef: + description: Name of the Hardware associated with this workflow. + type: string + templateRef: + description: Name of the Template associated with this workflow. + type: string + type: object + status: + description: WorkflowStatus defines the observed state of Workflow. + properties: + globalTimeout: + description: GlobalTimeout represents the max execution time + format: int64 + type: integer + state: + description: State is the state of the workflow in Tinkerbell. + type: string + tasks: + description: Tasks are the tasks to be completed + items: + description: + Task represents a series of actions to be completed + by a worker. + properties: + actions: + items: + description: Action represents a workflow action. + properties: + command: + items: + type: string + type: array + environment: + additionalProperties: + type: string + type: object + image: + type: string + message: + type: string + name: + type: string + pid: + type: string + seconds: + format: int64 + type: integer + startedAt: + format: date-time + type: string + status: + type: string + timeout: + format: int64 + type: integer + volumes: + items: + type: string + type: array + type: object + type: array + environment: + additionalProperties: + type: string + type: object + name: + type: string + volumes: + items: + type: string + type: array + worker: + type: string + required: + - actions + - name + - worker + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null + name: workflows.tinkerbell.org +spec: + group: tinkerbell.org + names: + categories: + - tinkerbell + kind: Workflow + listKind: WorkflowList + plural: workflows + shortNames: + - wf + singular: workflow + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.templateRef + name: Template + type: string + - jsonPath: .status.state + name: State + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Workflow is the Schema for the Workflows API. + 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/sig-architecture/api-conventions.md#resources" + 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/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: WorkflowSpec defines the desired state of Workflow. + properties: + hardwareMap: + additionalProperties: + type: string + description: A mapping of template devices to hadware mac addresses + type: object + hardwareRef: + description: Name of the Hardware associated with this workflow. + type: string + templateRef: + description: Name of the Template associated with this workflow. + type: string + type: object + status: + description: WorkflowStatus defines the observed state of Workflow. + properties: + globalTimeout: + description: GlobalTimeout represents the max execution time + format: int64 + type: integer + state: + description: State is the state of the workflow in Tinkerbell. + type: string + tasks: + description: Tasks are the tasks to be completed + items: + description: + Task represents a series of actions to be completed + by a worker. + properties: + actions: + items: + description: Action represents a workflow action. + properties: + command: + items: + type: string + type: array + environment: + additionalProperties: + type: string + type: object + image: + type: string + message: + type: string + name: + type: string + pid: + type: string + seconds: + format: int64 + type: integer + startedAt: + format: date-time + type: string + status: + type: string + timeout: + format: int64 + type: integer + volumes: + items: + type: string + type: array + type: object + type: array + environment: + additionalProperties: + type: string + type: object + name: + type: string + volumes: + items: + type: string + type: array + worker: + type: string + required: + - actions + - name + - worker + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 79b594ab..a71137c8 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -45,9 +45,9 @@ type RootCommandOptions struct { Backend string `mapstructure:"backend"` - KubernetesAPIURL string `mapstructure:"kubernetes"` - Kubeconfig string `mapstructure:"kubeconfig"` - KubeNamespace string `mapstructure:"kube-namespace"` + KubeAPIServerAddress string `mapstructure:"kubernetes"` + Kubeconfig string `mapstructure:"kubeconfig"` + KubeNamespace string `mapstructure:"kube-namespace"` FlatfilePath string `mapstructure:"flatfile-path"` @@ -184,9 +184,9 @@ func toBackendOptions(opts RootCommandOptions) backend.Options { case "kubernetes": backndOpts = backend.Options{ Kubernetes: &backend.KubernetesOptions{ - KubeAPI: opts.KubernetesAPIURL, - Kubeconfig: opts.Kubeconfig, - KubeNamespace: opts.KubeNamespace, + APIServerAddress: opts.KubeAPIServerAddress, + Kubeconfig: opts.Kubeconfig, + Namespace: opts.KubeNamespace, }, } } diff --git a/internal/http/server_test.go b/internal/http/server_test.go index 8f2ce1d8..7fc3769e 100644 --- a/internal/http/server_test.go +++ b/internal/http/server_test.go @@ -1,3 +1,5 @@ +//go:build integration + package http_test import ( @@ -32,6 +34,8 @@ func TestServe(t *testing.T) { go Serve(ctx, logger, fmt.Sprintf(":%d", 8080), &mux) + time.Sleep(50 * time.Millisecond) + resp, err := http.Get("http://localhost:8080") if err != nil { t.Fatal(err)