mirror of
https://github.com/kairos-io/osbuilder.git
synced 2025-07-14 23:44:19 +00:00
allow for custom artifact export logic
Signed-off-by: Jacob Payne <jacob@spectrocloud.com>
This commit is contained in:
parent
95dd24d549
commit
8d67aafa9c
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
// Package v1alpha1 contains API Schema definitions for the build v1alpha1 API group
|
// Package v1alpha1 contains API Schema definitions for the build v1alpha1 API group
|
||||||
// +kubebuilder:object:generate=true
|
// +kubebuilder:object:generate=true
|
||||||
// +groupName=build.kairos.io
|
// +groupName=build.kairos.io
|
||||||
package v1alpha1
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// GroupVersion is group version used to register these objects
|
// GroupVersion is group version used to register these objects
|
||||||
GroupVersion = schema.GroupVersion{Group: "build.kairos.io", Version: "v1alpha1"}
|
GroupVersion = schema.GroupVersion{Group: "build.kairos.io", Version: "v1alpha2"}
|
||||||
|
|
||||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
@ -14,23 +14,18 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package v1alpha1
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
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.
|
|
||||||
|
|
||||||
// OSArtifactSpec defines the desired state of OSArtifact
|
// OSArtifactSpec defines the desired state of OSArtifact
|
||||||
type OSArtifactSpec struct {
|
type OSArtifactSpec struct {
|
||||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
|
||||||
// Important: Run "make" to regenerate code after modifying this file
|
|
||||||
|
|
||||||
// Foo is an example field of OSArtifact. Edit osartifact_types.go to remove/update
|
|
||||||
ImageName string `json:"imageName,omitempty"`
|
ImageName string `json:"imageName,omitempty"`
|
||||||
// This needs to be revisited
|
|
||||||
ISO bool `json:"iso,omitempty"`
|
ISO bool `json:"iso,omitempty"`
|
||||||
|
|
||||||
//Disk-only stuff
|
//Disk-only stuff
|
||||||
@ -42,47 +37,42 @@ type OSArtifactSpec struct {
|
|||||||
Netboot bool `json:"netboot,omitempty"`
|
Netboot bool `json:"netboot,omitempty"`
|
||||||
NetbootURL string `json:"netbootURL,omitempty"`
|
NetbootURL string `json:"netbootURL,omitempty"`
|
||||||
|
|
||||||
// TODO: treat cloudconfig as a secret, and take a secretRef where to store it (optionally)
|
CloudConfigRef *SecretKeySelector `json:"cloudConfigRef,omitempty"`
|
||||||
CloudConfig string `json:"cloudConfig,omitempty"`
|
GRUBConfig string `json:"grubConfig,omitempty"`
|
||||||
GRUBConfig string `json:"grubConfig,omitempty"`
|
|
||||||
|
|
||||||
Bundles []string `json:"bundles,omitempty"`
|
Bundles []string `json:"bundles,omitempty"`
|
||||||
PullOptions Pull `json:"pull,omitempty"`
|
OSRelease string `json:"osRelease,omitempty"`
|
||||||
OSRelease string `json:"osRelease,omitempty"`
|
|
||||||
// TODO: Currently not used. Reserved to be used when we have a way to push to registries.
|
|
||||||
PushOptions Push `json:"push,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Push struct {
|
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
|
||||||
Push bool `json:"push,omitempty"`
|
Exporters []batchv1.JobSpec `json:"exporter,omitempty"`
|
||||||
ImageName string `json:"imageName,omitempty"`
|
Volume *corev1.PersistentVolumeClaimSpec `json:"volume,omitempty"`
|
||||||
ContainerRegistryCredentials *SecretKeySelector `json:"containerRegistryCredentials,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pull struct {
|
|
||||||
ContainerRegistryCredentials *SecretKeySelector `json:"containerRegistryCredentials,omitempty"`
|
|
||||||
}
|
|
||||||
type LocalObjectReference struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecretKeySelector struct {
|
type SecretKeySelector struct {
|
||||||
LocalObjectReference `json:",inline"`
|
Name string `json:"name"`
|
||||||
// +optional
|
|
||||||
Namespace string `json:"namespace,omitempty"`
|
|
||||||
// +optional
|
// +optional
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ArtifactPhase string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Pending = "Pending"
|
||||||
|
Building = "Building"
|
||||||
|
Exporting = "Exporting"
|
||||||
|
Ready = "Ready"
|
||||||
|
Error = "Error"
|
||||||
|
)
|
||||||
|
|
||||||
// OSArtifactStatus defines the observed state of OSArtifact
|
// OSArtifactStatus defines the observed state of OSArtifact
|
||||||
type OSArtifactStatus struct {
|
type OSArtifactStatus struct {
|
||||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
// +kubebuilder:default=Pending
|
||||||
// Important: Run "make" to regenerate code after modifying this file
|
Phase ArtifactPhase `json:"phase,omitempty"`
|
||||||
Phase string `json:"phase,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//+kubebuilder:object:root=true
|
//+kubebuilder:object:root=true
|
||||||
//+kubebuilder:subresource:status
|
//+kubebuilder:subresource:status
|
||||||
|
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
|
||||||
|
|
||||||
// OSArtifact is the Schema for the osartifacts API
|
// OSArtifact is the Schema for the osartifacts API
|
||||||
type OSArtifact struct {
|
type OSArtifact struct {
|
@ -19,27 +19,14 @@ limitations under the License.
|
|||||||
|
|
||||||
// Code generated by controller-gen. DO NOT EDIT.
|
// Code generated by controller-gen. DO NOT EDIT.
|
||||||
|
|
||||||
package v1alpha1
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
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 *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) {
|
|
||||||
*out = *in
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference.
|
|
||||||
func (in *LocalObjectReference) DeepCopy() *LocalObjectReference {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(LocalObjectReference)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *OSArtifact) DeepCopyInto(out *OSArtifact) {
|
func (in *OSArtifact) DeepCopyInto(out *OSArtifact) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@ -102,13 +89,33 @@ func (in *OSArtifactList) DeepCopyObject() runtime.Object {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *OSArtifactSpec) DeepCopyInto(out *OSArtifactSpec) {
|
func (in *OSArtifactSpec) DeepCopyInto(out *OSArtifactSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.CloudConfigRef != nil {
|
||||||
|
in, out := &in.CloudConfigRef, &out.CloudConfigRef
|
||||||
|
*out = new(SecretKeySelector)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.Bundles != nil {
|
if in.Bundles != nil {
|
||||||
in, out := &in.Bundles, &out.Bundles
|
in, out := &in.Bundles, &out.Bundles
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
in.PullOptions.DeepCopyInto(&out.PullOptions)
|
if in.ImagePullSecrets != nil {
|
||||||
in.PushOptions.DeepCopyInto(&out.PushOptions)
|
in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
|
||||||
|
*out = make([]v1.LocalObjectReference, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Exporters != nil {
|
||||||
|
in, out := &in.Exporters, &out.Exporters
|
||||||
|
*out = make([]batchv1.JobSpec, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.Volume != nil {
|
||||||
|
in, out := &in.Volume, &out.Volume
|
||||||
|
*out = new(v1.PersistentVolumeClaimSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OSArtifactSpec.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OSArtifactSpec.
|
||||||
@ -136,50 +143,9 @@ func (in *OSArtifactStatus) DeepCopy() *OSArtifactStatus {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *Pull) DeepCopyInto(out *Pull) {
|
|
||||||
*out = *in
|
|
||||||
if in.ContainerRegistryCredentials != nil {
|
|
||||||
in, out := &in.ContainerRegistryCredentials, &out.ContainerRegistryCredentials
|
|
||||||
*out = new(SecretKeySelector)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pull.
|
|
||||||
func (in *Pull) DeepCopy() *Pull {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(Pull)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *Push) DeepCopyInto(out *Push) {
|
|
||||||
*out = *in
|
|
||||||
if in.ContainerRegistryCredentials != nil {
|
|
||||||
in, out := &in.ContainerRegistryCredentials, &out.ContainerRegistryCredentials
|
|
||||||
*out = new(SecretKeySelector)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Push.
|
|
||||||
func (in *Push) DeepCopy() *Push {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(Push)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) {
|
func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) {
|
||||||
*out = *in
|
*out = *in
|
||||||
out.LocalObjectReference = in.LocalObjectReference
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector.
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,3 @@ resources:
|
|||||||
- auth_proxy_role.yaml
|
- auth_proxy_role.yaml
|
||||||
- auth_proxy_role_binding.yaml
|
- auth_proxy_role_binding.yaml
|
||||||
- auth_proxy_client_clusterrole.yaml
|
- auth_proxy_client_clusterrole.yaml
|
||||||
|
|
||||||
patchesStrategicMerge:
|
|
||||||
- role_custom.yaml
|
|
@ -5,6 +5,49 @@ metadata:
|
|||||||
creationTimestamp: null
|
creationTimestamp: null
|
||||||
name: manager-role
|
name: manager-role
|
||||||
rules:
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- persistentvolumeclaims
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- batch
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- build.kairos.io
|
- build.kairos.io
|
||||||
resources:
|
resources:
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
creationTimestamp: null
|
|
||||||
name: manager-role
|
|
||||||
rules:
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- serviceaccounts
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- "rbac.authorization.k8s.io"
|
|
||||||
resources:
|
|
||||||
- roles
|
|
||||||
- rolebindings
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- build.kairos.io
|
|
||||||
resources:
|
|
||||||
- osartifacts
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- build.kairos.io
|
|
||||||
resources:
|
|
||||||
- osartifacts/finalizers
|
|
||||||
verbs:
|
|
||||||
- update
|
|
||||||
- apiGroups:
|
|
||||||
- build.kairos.io
|
|
||||||
resources:
|
|
||||||
- osartifacts/status
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- apiGroups:
|
|
||||||
- build.kairos.io
|
|
||||||
resources:
|
|
||||||
- osartifacts/finalizers
|
|
||||||
verbs:
|
|
||||||
- update
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- configmaps
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- create
|
|
||||||
- update
|
|
||||||
- apiGroups:
|
|
||||||
- "batch"
|
|
||||||
resources:
|
|
||||||
- jobs
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- create
|
|
||||||
- update
|
|
||||||
# Temporary so that it can grant these permissions to the created role
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- pods
|
|
||||||
verbs:
|
|
||||||
- list
|
|
||||||
- get
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- pods/exec
|
|
||||||
verbs:
|
|
||||||
- create
|
|
@ -17,22 +17,19 @@ limitations under the License.
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
buildv1alpha1 "github.com/kairos-io/osbuilder/api/v1alpha1"
|
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *OSArtifactReconciler) genConfigMap(artifact buildv1alpha1.OSArtifact) *v1.ConfigMap {
|
func (r *OSArtifactReconciler) genConfigMap(artifact *osbuilder.OSArtifact) *v1.ConfigMap {
|
||||||
return &v1.ConfigMap{
|
return &v1.ConfigMap{
|
||||||
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: artifact.Name,
|
Name: artifact.Name,
|
||||||
Namespace: artifact.Namespace,
|
Namespace: artifact.Namespace,
|
||||||
OwnerReferences: genOwner(artifact),
|
|
||||||
},
|
},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"config": artifact.Spec.CloudConfig,
|
|
||||||
"grub.cfg": artifact.Spec.GRUBConfig,
|
"grub.cfg": artifact.Spec.GRUBConfig,
|
||||||
"os-release": artifact.Spec.OSRelease,
|
"os-release": artifact.Spec.OSRelease,
|
||||||
}}
|
}}
|
||||||
|
@ -17,35 +17,17 @@ limitations under the License.
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
buildv1alpha1 "github.com/kairos-io/osbuilder/api/v1alpha1"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
batchv1 "k8s.io/api/batch/v1"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||||
"k8s.io/client-go/tools/remotecommand"
|
corev1 "k8s.io/api/core/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genJobLabel(s string) map[string]string {
|
func unpackContainer(id, containerImage, pullImage string) corev1.Container {
|
||||||
return map[string]string{
|
return corev1.Container{
|
||||||
"osbuild": "workload" + s,
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handle registry auth
|
|
||||||
// TODO: This shells out, but needs ENV_VAR with key refs mapping
|
|
||||||
// TODO: Cache downloaded images?
|
|
||||||
func unpackContainer(id, containerImage, pullImage string, pullOptions buildv1alpha1.Pull) v1.Container {
|
|
||||||
return v1.Container{
|
|
||||||
ImagePullPolicy: v1.PullAlways,
|
|
||||||
Name: fmt.Sprintf("pull-image-%s", id),
|
Name: fmt.Sprintf("pull-image-%s", id),
|
||||||
Image: containerImage,
|
Image: containerImage,
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
Command: []string{"/bin/bash", "-cxe"},
|
||||||
@ -56,7 +38,7 @@ func unpackContainer(id, containerImage, pullImage string, pullOptions buildv1al
|
|||||||
"/rootfs",
|
"/rootfs",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
VolumeMounts: []v1.VolumeMount{
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
{
|
{
|
||||||
Name: "rootfs",
|
Name: "rootfs",
|
||||||
MountPath: "/rootfs",
|
MountPath: "/rootfs",
|
||||||
@ -65,19 +47,19 @@ func unpackContainer(id, containerImage, pullImage string, pullOptions buildv1al
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushImageName(artifact buildv1alpha1.OSArtifact) string {
|
func pushImageName(artifact *osbuilder.OSArtifact) string {
|
||||||
pushName := artifact.Spec.PushOptions.ImageName
|
pushName := artifact.Spec.ImageName
|
||||||
if pushName != "" {
|
if pushName != "" {
|
||||||
return pushName
|
return pushName
|
||||||
}
|
}
|
||||||
return artifact.Name
|
return artifact.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func createImageContainer(containerImage string, artifact buildv1alpha1.OSArtifact) v1.Container {
|
func createImageContainer(containerImage string, artifact *osbuilder.OSArtifact) corev1.Container {
|
||||||
imageName := pushImageName(artifact)
|
imageName := pushImageName(artifact)
|
||||||
|
|
||||||
return v1.Container{
|
return corev1.Container{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
Name: "create-image",
|
Name: "create-image",
|
||||||
Image: containerImage,
|
Image: containerImage,
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
Command: []string{"/bin/bash", "-cxe"},
|
||||||
@ -88,7 +70,7 @@ func createImageContainer(containerImage string, artifact buildv1alpha1.OSArtifa
|
|||||||
artifact.Name,
|
artifact.Name,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
VolumeMounts: []v1.VolumeMount{
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
{
|
{
|
||||||
Name: "rootfs",
|
Name: "rootfs",
|
||||||
MountPath: "/rootfs",
|
MountPath: "/rootfs",
|
||||||
@ -101,39 +83,16 @@ func createImageContainer(containerImage string, artifact buildv1alpha1.OSArtifa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPushToServerImageContainer(containerImage string, artifactPodInfo ArtifactPodInfo) v1.Container {
|
func osReleaseContainer(containerImage string) corev1.Container {
|
||||||
command := fmt.Sprintf("tar cf - -C artifacts/ . | kubectl exec -i -n %s $(kubectl get pods -l %s -n %s --no-headers -o custom-columns=\":metadata.name\" | head -n1) -- tar xf - -C %s", artifactPodInfo.Namespace, artifactPodInfo.Label, artifactPodInfo.Namespace, artifactPodInfo.Path)
|
return corev1.Container{
|
||||||
fmt.Printf("command = %+v\n", command)
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
|
|
||||||
return v1.Container{
|
|
||||||
ImagePullPolicy: v1.PullAlways,
|
|
||||||
Name: "push-to-server",
|
|
||||||
Image: containerImage,
|
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
|
||||||
Args: []string{command},
|
|
||||||
VolumeMounts: []v1.VolumeMount{
|
|
||||||
{
|
|
||||||
Name: "rootfs",
|
|
||||||
MountPath: "/rootfs",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "artifacts",
|
|
||||||
MountPath: "/artifacts",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func osReleaseContainer(containerImage string) v1.Container {
|
|
||||||
return v1.Container{
|
|
||||||
ImagePullPolicy: v1.PullAlways,
|
|
||||||
Name: "os-release",
|
Name: "os-release",
|
||||||
Image: containerImage,
|
Image: containerImage,
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
Command: []string{"/bin/bash", "-cxe"},
|
||||||
Args: []string{
|
Args: []string{
|
||||||
"cp -rfv /etc/os-release /rootfs/etc/os-release",
|
"cp -rfv /etc/os-release /rootfs/etc/os-release",
|
||||||
},
|
},
|
||||||
VolumeMounts: []v1.VolumeMount{
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
{
|
{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
MountPath: "/etc/os-release",
|
MountPath: "/etc/os-release",
|
||||||
@ -147,18 +106,38 @@ func osReleaseContainer(containerImage string) v1.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv1.Job {
|
func (r *OSArtifactReconciler) newArtifactPVC(artifact *osbuilder.OSArtifact) *corev1.PersistentVolumeClaim {
|
||||||
objMeta := genObjectMeta(artifact)
|
if artifact.Spec.Volume == nil {
|
||||||
|
artifact.Spec.Volume = &corev1.PersistentVolumeClaimSpec{
|
||||||
|
AccessModes: []corev1.PersistentVolumeAccessMode{
|
||||||
|
corev1.ReadWriteOnce,
|
||||||
|
},
|
||||||
|
Resources: corev1.ResourceRequirements{
|
||||||
|
Requests: map[corev1.ResourceName]resource.Quantity{
|
||||||
|
"storage": resource.MustParse("2Gi"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
privileged := false
|
pvc := &corev1.PersistentVolumeClaim{
|
||||||
serviceAccount := true
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: artifact.Name + "-artifacts",
|
||||||
|
Namespace: artifact.Namespace,
|
||||||
|
},
|
||||||
|
Spec: *artifact.Spec.Volume,
|
||||||
|
}
|
||||||
|
|
||||||
|
return pvc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder.OSArtifact) *corev1.Pod {
|
||||||
cmd := fmt.Sprintf(
|
cmd := fmt.Sprintf(
|
||||||
"/entrypoint.sh --debug --name %s build-iso --date=false --output /artifacts dir:/rootfs",
|
"/entrypoint.sh --debug --name %s build-iso --date=false --output /artifacts dir:/rootfs",
|
||||||
artifact.Name,
|
artifact.Name,
|
||||||
)
|
)
|
||||||
|
|
||||||
volumeMounts := []v1.VolumeMount{
|
volumeMounts := []corev1.VolumeMount{
|
||||||
{
|
{
|
||||||
Name: "artifacts",
|
Name: "artifacts",
|
||||||
MountPath: "/artifacts",
|
MountPath: "/artifacts",
|
||||||
@ -170,7 +149,7 @@ func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv
|
|||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.GRUBConfig != "" {
|
if artifact.Spec.GRUBConfig != "" {
|
||||||
volumeMounts = append(volumeMounts, v1.VolumeMount{
|
volumeMounts = append(volumeMounts, corev1.VolumeMount{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
MountPath: "/iso/iso-overlay/boot/grub2/grub.cfg",
|
MountPath: "/iso/iso-overlay/boot/grub2/grub.cfg",
|
||||||
SubPath: "grub.cfg",
|
SubPath: "grub.cfg",
|
||||||
@ -182,26 +161,26 @@ func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv
|
|||||||
artifact.Name,
|
artifact.Name,
|
||||||
)
|
)
|
||||||
|
|
||||||
if artifact.Spec.CloudConfig != "" {
|
if artifact.Spec.CloudConfigRef != nil {
|
||||||
volumeMounts = append(volumeMounts, v1.VolumeMount{
|
volumeMounts = append(volumeMounts, corev1.VolumeMount{
|
||||||
Name: "config",
|
Name: "cloudconfig",
|
||||||
MountPath: "/iso/iso-overlay/cloud_config.yaml",
|
MountPath: "/iso/iso-overlay/cloud_config.yaml",
|
||||||
SubPath: "config",
|
SubPath: artifact.Spec.CloudConfigRef.Key,
|
||||||
})
|
})
|
||||||
|
|
||||||
cloudImgCmd += " /iso/iso-overlay/cloud_config.yaml"
|
cloudImgCmd += " /iso/iso-overlay/cloud_config.yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.CloudConfig != "" || artifact.Spec.GRUBConfig != "" {
|
if artifact.Spec.CloudConfigRef != nil || artifact.Spec.GRUBConfig != "" {
|
||||||
cmd = fmt.Sprintf(
|
cmd = fmt.Sprintf(
|
||||||
"/entrypoint.sh --debug --name %s build-iso --date=false --overlay-iso /iso/iso-overlay --output /artifacts dir:/rootfs",
|
"/entrypoint.sh --debug --name %s build-iso --date=false --overlay-iso /iso/iso-overlay --output /artifacts dir:/rootfs",
|
||||||
artifact.Name,
|
artifact.Name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildIsoContainer := v1.Container{
|
buildIsoContainer := corev1.Container{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
SecurityContext: &v1.SecurityContext{Privileged: &privileged},
|
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||||
Name: "build-iso",
|
Name: "build-iso",
|
||||||
Image: r.ToolImage,
|
Image: r.ToolImage,
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
Command: []string{"/bin/bash", "-cxe"},
|
||||||
@ -211,9 +190,9 @@ func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv
|
|||||||
VolumeMounts: volumeMounts,
|
VolumeMounts: volumeMounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCloudImageContainer := v1.Container{
|
buildCloudImageContainer := corev1.Container{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
SecurityContext: &v1.SecurityContext{Privileged: &privileged},
|
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||||
Name: "build-cloud-image",
|
Name: "build-cloud-image",
|
||||||
Image: r.ToolImage,
|
Image: r.ToolImage,
|
||||||
|
|
||||||
@ -225,19 +204,19 @@ func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv
|
|||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.DiskSize != "" {
|
if artifact.Spec.DiskSize != "" {
|
||||||
buildCloudImageContainer.Env = []v1.EnvVar{{
|
buildCloudImageContainer.Env = []corev1.EnvVar{{
|
||||||
Name: "EXTEND",
|
Name: "EXTEND",
|
||||||
Value: artifact.Spec.DiskSize,
|
Value: artifact.Spec.DiskSize,
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
extractNetboot := v1.Container{
|
extractNetboot := corev1.Container{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
SecurityContext: &v1.SecurityContext{Privileged: &privileged},
|
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||||
Name: "build-netboot",
|
Name: "build-netboot",
|
||||||
Image: r.ToolImage,
|
Image: r.ToolImage,
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
Command: []string{"/bin/bash", "-cxe"},
|
||||||
Env: []v1.EnvVar{{
|
Env: []corev1.EnvVar{{
|
||||||
Name: "URL",
|
Name: "URL",
|
||||||
Value: artifact.Spec.NetbootURL,
|
Value: artifact.Spec.NetbootURL,
|
||||||
}},
|
}},
|
||||||
@ -251,9 +230,9 @@ func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv
|
|||||||
VolumeMounts: volumeMounts,
|
VolumeMounts: volumeMounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
buildAzureCloudImageContainer := v1.Container{
|
buildAzureCloudImageContainer := corev1.Container{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
SecurityContext: &v1.SecurityContext{Privileged: &privileged},
|
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||||
Name: "build-azure-cloud-image",
|
Name: "build-azure-cloud-image",
|
||||||
Image: r.ToolImage,
|
Image: r.ToolImage,
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
Command: []string{"/bin/bash", "-cxe"},
|
||||||
@ -267,9 +246,9 @@ func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv
|
|||||||
VolumeMounts: volumeMounts,
|
VolumeMounts: volumeMounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
buildGCECloudImageContainer := v1.Container{
|
buildGCECloudImageContainer := corev1.Container{
|
||||||
ImagePullPolicy: v1.PullAlways,
|
ImagePullPolicy: corev1.PullAlways,
|
||||||
SecurityContext: &v1.SecurityContext{Privileged: &privileged},
|
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||||
Name: "build-gce-cloud-image",
|
Name: "build-gce-cloud-image",
|
||||||
Image: r.ToolImage,
|
Image: r.ToolImage,
|
||||||
Command: []string{"/bin/bash", "-cxe"},
|
Command: []string{"/bin/bash", "-cxe"},
|
||||||
@ -283,251 +262,93 @@ func (r *OSArtifactReconciler) genJob(artifact buildv1alpha1.OSArtifact) *batchv
|
|||||||
VolumeMounts: volumeMounts,
|
VolumeMounts: volumeMounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
pod := v1.PodSpec{
|
podSpec := corev1.PodSpec{
|
||||||
AutomountServiceAccountToken: &serviceAccount,
|
AutomountServiceAccountToken: ptr(false),
|
||||||
ServiceAccountName: objMeta.Name,
|
RestartPolicy: corev1.RestartPolicyNever,
|
||||||
RestartPolicy: v1.RestartPolicyNever,
|
Volumes: []corev1.Volume{
|
||||||
Volumes: []v1.Volume{
|
|
||||||
{
|
{
|
||||||
Name: "artifacts",
|
Name: "artifacts",
|
||||||
VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}},
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||||
|
ClaimName: pvcName,
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "rootfs",
|
Name: "rootfs",
|
||||||
VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}},
|
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
VolumeSource: v1.VolumeSource{
|
VolumeSource: corev1.VolumeSource{
|
||||||
ConfigMap: &v1.ConfigMapVolumeSource{
|
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||||
LocalObjectReference: v1.LocalObjectReference{Name: artifact.Name}}},
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: artifact.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pod.InitContainers = []v1.Container{unpackContainer("baseimage", r.ToolImage, artifact.Spec.ImageName, artifact.Spec.PullOptions)}
|
if artifact.Spec.CloudConfigRef != nil {
|
||||||
|
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{
|
||||||
|
Name: "cloudconfig",
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
Secret: &corev1.SecretVolumeSource{
|
||||||
|
SecretName: artifact.Spec.CloudConfigRef.Name,
|
||||||
|
Optional: ptr(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range artifact.Spec.ImagePullSecrets {
|
||||||
|
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, artifact.Spec.ImagePullSecrets[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
podSpec.InitContainers = []corev1.Container{unpackContainer("baseimage", r.ToolImage, artifact.Spec.ImageName)}
|
||||||
|
|
||||||
for i, bundle := range artifact.Spec.Bundles {
|
for i, bundle := range artifact.Spec.Bundles {
|
||||||
pod.InitContainers = append(pod.InitContainers, unpackContainer(fmt.Sprint(i), r.ToolImage, bundle, artifact.Spec.PullOptions))
|
podSpec.InitContainers = append(podSpec.InitContainers, unpackContainer(fmt.Sprint(i), r.ToolImage, bundle))
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.OSRelease != "" {
|
if artifact.Spec.OSRelease != "" {
|
||||||
pod.InitContainers = append(pod.InitContainers, osReleaseContainer(r.ToolImage))
|
podSpec.InitContainers = append(podSpec.InitContainers, osReleaseContainer(r.ToolImage))
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.ISO || artifact.Spec.Netboot {
|
if artifact.Spec.ISO || artifact.Spec.Netboot {
|
||||||
pod.InitContainers = append(pod.InitContainers, buildIsoContainer)
|
podSpec.Containers = append(podSpec.Containers, buildIsoContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.Netboot {
|
if artifact.Spec.Netboot {
|
||||||
pod.InitContainers = append(pod.InitContainers, extractNetboot)
|
podSpec.Containers = append(podSpec.Containers, extractNetboot)
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.CloudImage || artifact.Spec.AzureImage || artifact.Spec.GCEImage {
|
if artifact.Spec.CloudImage {
|
||||||
pod.InitContainers = append(pod.InitContainers, buildCloudImageContainer)
|
podSpec.Containers = append(podSpec.Containers, buildCloudImageContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.AzureImage {
|
if artifact.Spec.AzureImage {
|
||||||
pod.InitContainers = append(pod.InitContainers, buildAzureCloudImageContainer)
|
podSpec.Containers = append(podSpec.Containers, buildAzureCloudImageContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if artifact.Spec.GCEImage {
|
if artifact.Spec.GCEImage {
|
||||||
pod.InitContainers = append(pod.InitContainers, buildGCECloudImageContainer)
|
podSpec.Containers = append(podSpec.Containers, buildGCECloudImageContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pod.InitContainers = append(pod.InitContainers, createImageContainer(r.ToolImage, artifact))
|
podSpec.Containers = append(podSpec.Containers, createImageContainer(r.ToolImage, artifact))
|
||||||
|
|
||||||
pod.Containers = []v1.Container{
|
return &corev1.Pod{
|
||||||
createPushToServerImageContainer(r.CopierImage, r.ArtifactPodInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
jobLabels := genJobLabel(artifact.Name)
|
|
||||||
|
|
||||||
job := batchv1.Job{
|
|
||||||
ObjectMeta: objMeta,
|
|
||||||
Spec: batchv1.JobSpec{
|
|
||||||
Template: v1.PodTemplateSpec{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Labels: jobLabels,
|
|
||||||
},
|
|
||||||
Spec: pod,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &job
|
|
||||||
}
|
|
||||||
|
|
||||||
// createServiceAccount creates a service account that has the permissions to
|
|
||||||
// copy the artifacts to the http server Pod. This service account is used for
|
|
||||||
// the "push to server" container.
|
|
||||||
func (r *OSArtifactReconciler) createCopierServiceAccount(ctx context.Context, objMeta metav1.ObjectMeta) error {
|
|
||||||
sa, err := r.clientSet.CoreV1().
|
|
||||||
ServiceAccounts(objMeta.Namespace).Get(ctx, objMeta.Name, metav1.GetOptions{})
|
|
||||||
if sa == nil || apierrors.IsNotFound(err) {
|
|
||||||
t := true
|
|
||||||
_, err := r.clientSet.CoreV1().ServiceAccounts(objMeta.Namespace).Create(ctx,
|
|
||||||
&v1.ServiceAccount{
|
|
||||||
ObjectMeta: objMeta,
|
|
||||||
AutomountServiceAccountToken: &t,
|
|
||||||
}, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (r *OSArtifactReconciler) createCopierRole(ctx context.Context, objMeta metav1.ObjectMeta) error {
|
|
||||||
// role, err := r.clientSet.RbacV1().
|
|
||||||
// Roles(objMeta.Namespace).
|
|
||||||
// Get(ctx, objMeta.Name, metav1.GetOptions{})
|
|
||||||
// if role == nil || apierrors.IsNotFound(err) {
|
|
||||||
// _, err := r.clientSet.RbacV1().Roles(objMeta.Namespace).Create(ctx,
|
|
||||||
// &rbacv1.Role{
|
|
||||||
// ObjectMeta: objMeta,
|
|
||||||
// Rules: []rbacv1.PolicyRule{
|
|
||||||
// // TODO: The actual permissions we need is that to copy to a Pod.
|
|
||||||
// // The Pod is on another namespace, so we need a cluster wide permission.
|
|
||||||
// // This can get viral because the controller needs to have the permissions
|
|
||||||
// // if it is to grant them to the Job.
|
|
||||||
// {
|
|
||||||
// Verbs: []string{"list"},
|
|
||||||
// APIGroups: []string{""},
|
|
||||||
// Resources: []string{"pods"},
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// metav1.CreateOptions{},
|
|
||||||
// )
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (r *OSArtifactReconciler) createCopierRoleBinding(ctx context.Context, objMeta metav1.ObjectMeta) error {
|
|
||||||
newrb := &rbacv1.RoleBinding{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: objMeta.Name,
|
GenerateName: artifact.Name + "-",
|
||||||
Namespace: r.ArtifactPodInfo.Namespace,
|
Namespace: artifact.Namespace,
|
||||||
// TODO: We can't have cross-namespace owners. The role binding will have to deleted explicitly by the reconciler (finalizer?)
|
|
||||||
// OwnerReferences: objMeta.OwnerReferences,
|
|
||||||
},
|
|
||||||
RoleRef: rbacv1.RoleRef{
|
|
||||||
APIGroup: "rbac.authorization.k8s.io",
|
|
||||||
Kind: "Role",
|
|
||||||
Name: r.ArtifactPodInfo.Role,
|
|
||||||
},
|
|
||||||
Subjects: []rbacv1.Subject{
|
|
||||||
{
|
|
||||||
Kind: "ServiceAccount",
|
|
||||||
Name: objMeta.Name,
|
|
||||||
Namespace: objMeta.Namespace,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
Spec: podSpec,
|
||||||
}
|
}
|
||||||
|
|
||||||
rb, err := r.clientSet.RbacV1().
|
|
||||||
RoleBindings(r.ArtifactPodInfo.Namespace).
|
|
||||||
Get(ctx, objMeta.Name, metav1.GetOptions{})
|
|
||||||
if rb == nil || apierrors.IsNotFound(err) {
|
|
||||||
_, err := r.clientSet.RbacV1().
|
|
||||||
RoleBindings(r.ArtifactPodInfo.Namespace).
|
|
||||||
Create(ctx, newrb, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRBAC creates a ServiceAccount, and a binding to the CopierRole so that
|
func ptr[T any](val T) *T {
|
||||||
// the container that copies the artifacts to the http server Pod has the
|
return &val
|
||||||
// permissions to do so.
|
|
||||||
func (r *OSArtifactReconciler) createRBAC(ctx context.Context, artifact buildv1alpha1.OSArtifact) error {
|
|
||||||
objMeta := genObjectMeta(artifact)
|
|
||||||
|
|
||||||
err := r.createCopierServiceAccount(ctx, objMeta)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "creating a service account")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.createCopierRoleBinding(ctx, objMeta)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "creating a role binding for the copy-role")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeRBAC deletes the role binding between the service account of this artifact
|
|
||||||
// and the CopierRole. The ServiceAccount is removed automatically through the Owner
|
|
||||||
// relationship with the OSArtifact. The RoleBinding can't have it as an owner
|
|
||||||
// because it is in a different Namespace.
|
|
||||||
func (r *OSArtifactReconciler) removeRBAC(ctx context.Context, artifact buildv1alpha1.OSArtifact) error {
|
|
||||||
err := r.clientSet.RbacV1().RoleBindings(r.ArtifactPodInfo.Namespace).
|
|
||||||
Delete(ctx, artifact.Name, metav1.DeleteOptions{})
|
|
||||||
// Ignore not found. No need to do anything.
|
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OSArtifactReconciler) removeArtifacts(ctx context.Context, artifact buildv1alpha1.OSArtifact) error {
|
|
||||||
//Finding Pods using labels
|
|
||||||
fmt.Printf("r.ArtifactPodInfo = %+v\n", r.ArtifactPodInfo.Label)
|
|
||||||
pods, err := r.clientSet.CoreV1().Pods(r.ArtifactPodInfo.Namespace).
|
|
||||||
List(ctx, metav1.ListOptions{LabelSelector: r.ArtifactPodInfo.Label})
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, fmt.Sprintf("listing pods with label %s in namespace %s", r.ArtifactPodInfo.Label, r.ArtifactPodInfo.Namespace))
|
|
||||||
}
|
|
||||||
if len(pods.Items) < 1 {
|
|
||||||
return errors.New("No artifact pod found")
|
|
||||||
}
|
|
||||||
pod := pods.Items[0]
|
|
||||||
|
|
||||||
stdout, stderr, err := r.executeRemoteCommand(r.ArtifactPodInfo.Namespace, pod.Name, fmt.Sprintf("rm -rf %s/%s.*", r.ArtifactPodInfo.Path, artifact.Name))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, fmt.Sprintf("%s\n%s", stdout, stderr))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OSArtifactReconciler) executeRemoteCommand(namespace, podName, command string) (string, string, error) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
errBuf := &bytes.Buffer{}
|
|
||||||
request := r.clientSet.CoreV1().RESTClient().
|
|
||||||
Post().
|
|
||||||
Namespace(namespace).
|
|
||||||
Resource("pods").
|
|
||||||
Name(podName).
|
|
||||||
SubResource("exec").
|
|
||||||
VersionedParams(&v1.PodExecOptions{
|
|
||||||
Command: []string{"/bin/sh", "-c", command},
|
|
||||||
Stdin: false,
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
TTY: true,
|
|
||||||
}, scheme.ParameterCodec)
|
|
||||||
|
|
||||||
exec, err := remotecommand.NewSPDYExecutor(r.restConfig, "POST", request.URL())
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
err = exec.Stream(remotecommand.StreamOptions{
|
|
||||||
Stdout: buf,
|
|
||||||
Stderr: errBuf,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("%w Failed executing command %s on %v/%v", err, command, namespace, podName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.String(), errBuf.String(), nil
|
|
||||||
}
|
}
|
||||||
|
@ -19,230 +19,286 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||||
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
buildv1alpha1 "github.com/kairos-io/osbuilder/api/v1alpha1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"github.com/pkg/errors"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
"sigs.k8s.io/cluster-api/util/patch"
|
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FinalizerName = "build.kairos.io/osbuilder-finalizer"
|
const (
|
||||||
|
FinalizerName = "build.kairos.io/osbuilder-finalizer"
|
||||||
type ArtifactPodInfo struct {
|
artifactLabel = "build.kairos.io/artifact"
|
||||||
Label string
|
artifactExporterIndexAnnotation = "build.kairos.io/export-index"
|
||||||
Namespace string
|
)
|
||||||
Path string
|
|
||||||
Role string
|
|
||||||
}
|
|
||||||
|
|
||||||
// OSArtifactReconciler reconciles a OSArtifact object
|
// OSArtifactReconciler reconciles a OSArtifact object
|
||||||
type OSArtifactReconciler struct {
|
type OSArtifactReconciler struct {
|
||||||
client.Client
|
client.Client
|
||||||
Scheme *runtime.Scheme
|
|
||||||
restConfig *rest.Config
|
|
||||||
clientSet *kubernetes.Clientset
|
|
||||||
ServingImage, ToolImage, CopierImage string
|
ServingImage, ToolImage, CopierImage string
|
||||||
ArtifactPodInfo ArtifactPodInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func genObjectMeta(artifact buildv1alpha1.OSArtifact) metav1.ObjectMeta {
|
func (r *OSArtifactReconciler) InjectClient(c client.Client) error {
|
||||||
return metav1.ObjectMeta{
|
r.Client = c
|
||||||
Name: artifact.Name,
|
|
||||||
Namespace: artifact.Namespace,
|
|
||||||
OwnerReferences: genOwner(artifact),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func genOwner(artifact buildv1alpha1.OSArtifact) []metav1.OwnerReference {
|
return nil
|
||||||
return []metav1.OwnerReference{
|
|
||||||
*metav1.NewControllerRef(&artifact.ObjectMeta, schema.GroupVersionKind{
|
|
||||||
Group: buildv1alpha1.GroupVersion.Group,
|
|
||||||
Version: buildv1alpha1.GroupVersion.Version,
|
|
||||||
Kind: "OSArtifact",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//+kubebuilder:rbac:groups=build.kairos.io,resources=osartifacts,verbs=get;list;watch;create;update;patch;delete
|
//+kubebuilder:rbac:groups=build.kairos.io,resources=osartifacts,verbs=get;list;watch;create;update;patch;delete
|
||||||
//+kubebuilder:rbac:groups=build.kairos.io,resources=osartifacts/status,verbs=get;update;patch
|
//+kubebuilder:rbac:groups=build.kairos.io,resources=osartifacts/status,verbs=get;update;patch
|
||||||
//+kubebuilder:rbac:groups=build.kairos.io,resources=osartifacts/finalizers,verbs=update
|
//+kubebuilder:rbac:groups=build.kairos.io,resources=osartifacts/finalizers,verbs=update
|
||||||
|
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;delete
|
||||||
|
//+kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;create;delete;watch
|
||||||
|
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;
|
||||||
|
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;create;
|
||||||
|
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;delete
|
||||||
|
|
||||||
// TODO: Is this ^ how I should have created rbac permissions for the controller?
|
|
||||||
// - git commit all changes
|
|
||||||
// - generate code with kubebuilder
|
|
||||||
// - check if my permissions were removed
|
|
||||||
// - do it properly
|
|
||||||
|
|
||||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
|
||||||
// move the current state of the cluster closer to the desired state.
|
|
||||||
// TODO(user): Modify the Reconcile function to compare the state specified by
|
|
||||||
// the OSArtifact object against the actual cluster state, and then
|
|
||||||
// perform operations to make the cluster state reflect the state specified by
|
|
||||||
// the user.
|
|
||||||
//
|
|
||||||
// For more details, check Reconcile and its Result here:
|
|
||||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.1/pkg/reconcile
|
|
||||||
func (r *OSArtifactReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
func (r *OSArtifactReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
var osbuild buildv1alpha1.OSArtifact
|
var artifact osbuilder.OSArtifact
|
||||||
if err := r.Get(ctx, req.NamespacedName, &osbuild); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, &artifact); err != nil {
|
||||||
if apierrors.IsNotFound(err) {
|
if apierrors.IsNotFound(err) {
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{Requeue: true}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("Reconciling %v", osbuild))
|
if artifact.DeletionTimestamp != nil {
|
||||||
|
controllerutil.RemoveFinalizer(&artifact, FinalizerName)
|
||||||
stop, err := r.handleFinalizer(ctx, &osbuild)
|
return ctrl.Result{}, r.Update(ctx, &artifact)
|
||||||
if err != nil || stop {
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate configmap required for building a custom image
|
if !controllerutil.ContainsFinalizer(&artifact, FinalizerName) {
|
||||||
desiredConfigMap := r.genConfigMap(osbuild)
|
controllerutil.AddFinalizer(&artifact, FinalizerName)
|
||||||
logger.Info(fmt.Sprintf("Checking configmap %v", osbuild))
|
if err := r.Update(ctx, &artifact); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
cfgMap, err := r.clientSet.CoreV1().ConfigMaps(req.Namespace).Get(ctx, desiredConfigMap.Name, metav1.GetOptions{})
|
|
||||||
if cfgMap == nil || apierrors.IsNotFound(err) {
|
|
||||||
logger.Info(fmt.Sprintf("Creating config map %v", desiredConfigMap))
|
|
||||||
|
|
||||||
_, err = r.clientSet.CoreV1().ConfigMaps(req.Namespace).Create(ctx, desiredConfigMap, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err, "Failed while creating config map")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("Reconciling %s/%s", artifact.Namespace, artifact.Name))
|
||||||
|
|
||||||
|
switch artifact.Status.Phase {
|
||||||
|
case osbuilder.Exporting:
|
||||||
|
return r.checkExport(ctx, &artifact)
|
||||||
|
case osbuilder.Ready, osbuilder.Error:
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
default:
|
||||||
|
return r.checkBuild(ctx, &artifact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OSArtifactReconciler) startBuild(ctx context.Context, artifact *osbuilder.OSArtifact) (ctrl.Result, error) {
|
||||||
|
// generate configmap required for building a custom image
|
||||||
|
cm := r.genConfigMap(artifact)
|
||||||
|
if cm.Labels == nil {
|
||||||
|
cm.Labels = map[string]string{}
|
||||||
|
}
|
||||||
|
cm.Labels[artifactLabel] = artifact.Name
|
||||||
|
if err := controllerutil.SetOwnerReference(artifact, cm, r.Scheme()); err != nil {
|
||||||
return ctrl.Result{Requeue: true}, err
|
return ctrl.Result{Requeue: true}, err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err := r.Create(ctx, cm); err != nil && !apierrors.IsAlreadyExists(err) {
|
||||||
return ctrl.Result{Requeue: true}, err
|
return ctrl.Result{Requeue: true}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("Checking deployment %v", osbuild))
|
pvc := r.newArtifactPVC(artifact)
|
||||||
|
if pvc.Labels == nil {
|
||||||
err = r.createRBAC(ctx, osbuild)
|
pvc.Labels = map[string]string{}
|
||||||
if err != nil {
|
}
|
||||||
|
pvc.Labels[artifactLabel] = artifact.Name
|
||||||
|
if err := controllerutil.SetOwnerReference(artifact, pvc, r.Scheme()); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
if err := r.Create(ctx, pvc); err != nil {
|
||||||
return ctrl.Result{Requeue: true}, err
|
return ctrl.Result{Requeue: true}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
desiredJob := r.genJob(osbuild)
|
pod := r.newBuilderPod(pvc.Name, artifact)
|
||||||
job, err := r.clientSet.BatchV1().Jobs(req.Namespace).Get(ctx, desiredJob.Name, metav1.GetOptions{})
|
if pod.Labels == nil {
|
||||||
if job == nil || apierrors.IsNotFound(err) {
|
pod.Labels = map[string]string{}
|
||||||
logger.Info(fmt.Sprintf("Creating Job %v", job))
|
}
|
||||||
|
pod.Labels[artifactLabel] = artifact.Name
|
||||||
|
if err := controllerutil.SetOwnerReference(artifact, pod, r.Scheme()); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
if err := r.Create(ctx, pod); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = r.clientSet.BatchV1().Jobs(req.Namespace).Create(ctx, desiredJob, metav1.CreateOptions{})
|
artifact.Status.Phase = osbuilder.Building
|
||||||
if err != nil {
|
if err := r.Status().Update(ctx, artifact); err != nil {
|
||||||
logger.Error(err, "Failed while creating job")
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OSArtifactReconciler) checkBuild(ctx context.Context, artifact *osbuilder.OSArtifact) (ctrl.Result, error) {
|
||||||
|
var pods corev1.PodList
|
||||||
|
if err := r.List(ctx, &pods, &client.ListOptions{
|
||||||
|
LabelSelector: labels.SelectorFromSet(labels.Set{
|
||||||
|
artifactLabel: artifact.Name,
|
||||||
|
}),
|
||||||
|
}); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pod := range pods.Items {
|
||||||
|
switch pod.Status.Phase {
|
||||||
|
case corev1.PodSucceeded:
|
||||||
|
artifact.Status.Phase = osbuilder.Exporting
|
||||||
|
return ctrl.Result{Requeue: true}, r.Status().Update(ctx, artifact)
|
||||||
|
case corev1.PodFailed:
|
||||||
|
artifact.Status.Phase = osbuilder.Error
|
||||||
|
return ctrl.Result{Requeue: true}, r.Status().Update(ctx, artifact)
|
||||||
|
case corev1.PodPending, corev1.PodRunning:
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctrl.Result{Requeue: true}, nil
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
|
return r.startBuild(ctx, artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OSArtifactReconciler) checkExport(ctx context.Context, artifact *osbuilder.OSArtifact) (ctrl.Result, error) {
|
||||||
|
var jobs batchv1.JobList
|
||||||
|
if err := r.List(ctx, &jobs, &client.ListOptions{
|
||||||
|
LabelSelector: labels.SelectorFromSet(labels.Set{
|
||||||
|
artifactLabel: artifact.Name,
|
||||||
|
}),
|
||||||
|
}); err != nil {
|
||||||
return ctrl.Result{Requeue: true}, err
|
return ctrl.Result{Requeue: true}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("Updating state %v", osbuild))
|
indexedJobs := make(map[string]*batchv1.Job, len(artifact.Spec.Exporters))
|
||||||
|
for _, job := range jobs.Items {
|
||||||
copy := osbuild.DeepCopy()
|
if job.GetAnnotations() != nil {
|
||||||
|
if idx, ok := job.GetAnnotations()[artifactExporterIndexAnnotation]; ok {
|
||||||
helper, err := patch.NewHelper(&osbuild, r.Client)
|
indexedJobs[idx] = &job
|
||||||
if err != nil {
|
}
|
||||||
return ctrl.Result{}, err
|
}
|
||||||
}
|
|
||||||
if job.Status.Succeeded > 0 {
|
|
||||||
copy.Status.Phase = "Ready"
|
|
||||||
} else if copy.Status.Phase != "Building" {
|
|
||||||
copy.Status.Phase = "Building"
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := helper.Patch(ctx, copy); err != nil {
|
|
||||||
return ctrl.Result{}, errors.Wrapf(err, "couldn't patch osbuild %q", copy.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for _, c := range append(pod.Status.ContainerStatuses, pod.Status.InitContainerStatuses...) {
|
var pvcs corev1.PersistentVolumeClaimList
|
||||||
// if c.State.Terminated != nil && c.State.Terminated.ExitCode != 0 {
|
var pvc *corev1.PersistentVolumeClaim
|
||||||
// packageBuildCopy.Status.State = "Failed"
|
if err := r.List(ctx, &pvcs, &client.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{artifactLabel: artifact.Name})}); err != nil {
|
||||||
// }
|
return ctrl.Result{Requeue: true}, err
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
for _, item := range pvcs.Items {
|
||||||
|
pvc = &item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if pvc == nil {
|
||||||
|
log.FromContext(ctx).Error(nil, "failed to locate pvc for artifact, this should not happen")
|
||||||
|
return ctrl.Result{}, fmt.Errorf("failed to locate artifact pvc")
|
||||||
|
}
|
||||||
|
|
||||||
|
var succeeded int
|
||||||
|
for i := range artifact.Spec.Exporters {
|
||||||
|
idx := fmt.Sprintf("%d", i)
|
||||||
|
|
||||||
|
job := indexedJobs[idx]
|
||||||
|
if job == nil {
|
||||||
|
job = &batchv1.Job{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("%s-export-%s", artifact.Name, idx),
|
||||||
|
Namespace: artifact.Namespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
artifactExporterIndexAnnotation: idx,
|
||||||
|
},
|
||||||
|
Labels: map[string]string{
|
||||||
|
artifactLabel: artifact.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: artifact.Spec.Exporters[i],
|
||||||
|
}
|
||||||
|
|
||||||
|
job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
|
||||||
|
Name: "artifacts",
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||||
|
ClaimName: pvc.Name,
|
||||||
|
ReadOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := controllerutil.SetOwnerReference(artifact, job, r.Scheme()); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Create(ctx, job); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if job.Spec.Completions == nil || *job.Spec.Completions == 1 {
|
||||||
|
if job.Status.Succeeded > 0 {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
} else if *job.Spec.BackoffLimit <= job.Status.Failed {
|
||||||
|
artifact.Status.Phase = osbuilder.Error
|
||||||
|
if err := r.Status().Update(ctx, artifact); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if succeeded == len(artifact.Spec.Exporters) {
|
||||||
|
artifact.Status.Phase = osbuilder.Ready
|
||||||
|
if err := r.Status().Update(ctx, artifact); err != nil {
|
||||||
|
return ctrl.Result{Requeue: true}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *OSArtifactReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *OSArtifactReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
|
|
||||||
cfg := mgr.GetConfig()
|
|
||||||
clientset, err := kubernetes.NewForConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.restConfig = cfg
|
|
||||||
r.clientSet = clientset
|
|
||||||
|
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&buildv1alpha1.OSArtifact{}).
|
For(&osbuilder.OSArtifact{}).
|
||||||
|
Owns(&osbuilder.OSArtifact{}).
|
||||||
|
Watches(
|
||||||
|
&source.Kind{Type: &corev1.Pod{}},
|
||||||
|
handler.EnqueueRequestsFromMapFunc(r.findOwningArtifact),
|
||||||
|
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
|
||||||
|
).
|
||||||
|
Watches(
|
||||||
|
&source.Kind{Type: &batchv1.Job{}},
|
||||||
|
handler.EnqueueRequestsFromMapFunc(r.findOwningArtifact),
|
||||||
|
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
|
||||||
|
).
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if reconciliation should stop or false otherwise
|
func (r *OSArtifactReconciler) findOwningArtifact(obj client.Object) []reconcile.Request {
|
||||||
func (r *OSArtifactReconciler) handleFinalizer(ctx context.Context, osbuild *buildv1alpha1.OSArtifact) (bool, error) {
|
if obj.GetLabels() == nil {
|
||||||
// examine DeletionTimestamp to determine if object is under deletion
|
return nil
|
||||||
if osbuild.DeletionTimestamp.IsZero() {
|
|
||||||
// The object is not being deleted, so if it does not have our finalizer,
|
|
||||||
// then lets add the finalizer and update the object. This is equivalent
|
|
||||||
// registering our finalizer.
|
|
||||||
if !controllerutil.ContainsFinalizer(osbuild, FinalizerName) {
|
|
||||||
controllerutil.AddFinalizer(osbuild, FinalizerName)
|
|
||||||
if err := r.Update(ctx, osbuild); err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The object is being deleted
|
|
||||||
if controllerutil.ContainsFinalizer(osbuild, FinalizerName) {
|
|
||||||
// our finalizer is present, so lets handle any external dependency
|
|
||||||
if err := r.finalize(ctx, osbuild); err != nil {
|
|
||||||
// if fail to delete the external dependency here, return with error
|
|
||||||
// so that it can be retried
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove our finalizer from the list and update it.
|
|
||||||
controllerutil.RemoveFinalizer(osbuild, FinalizerName)
|
|
||||||
if err := r.Update(ctx, osbuild); err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop reconciliation as the item is being deleted
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
if artifactName, ok := obj.GetLabels()[artifactLabel]; ok {
|
||||||
}
|
return []reconcile.Request{
|
||||||
|
{
|
||||||
// - Remove artifacts from the server Pod
|
NamespacedName: types.NamespacedName{
|
||||||
// - Delete role-binding (because it doesn't have the OSArtifact as an owner and won't be deleted automatically)
|
Name: artifactName,
|
||||||
func (r *OSArtifactReconciler) finalize(ctx context.Context, osbuild *buildv1alpha1.OSArtifact) error {
|
Namespace: obj.GetNamespace(),
|
||||||
if err := r.removeRBAC(ctx, *osbuild); err != nil {
|
},
|
||||||
return err
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.removeArtifacts(ctx, *osbuild); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
3
go.sum
3
go.sum
@ -191,7 +191,6 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
|||||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/gobuffalo/flect v0.2.4 h1:BSYA8+T60cdyq+vynaSUjqSVI9mDEg9ZfQUXKmfjo4I=
|
github.com/gobuffalo/flect v0.2.4 h1:BSYA8+T60cdyq+vynaSUjqSVI9mDEg9ZfQUXKmfjo4I=
|
||||||
github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8=
|
github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8=
|
||||||
@ -280,7 +279,6 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
@ -831,7 +829,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 h1:hI3jKY4Hpf63ns040onEbB3dAkR/H/P83hw1TG8dD3Y=
|
|
||||||
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
12
main.go
12
main.go
@ -31,7 +31,7 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||||
|
|
||||||
buildv1alpha1 "github.com/kairos-io/osbuilder/api/v1alpha1"
|
buildv1alpha2 "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||||
"github.com/kairos-io/osbuilder/controllers"
|
"github.com/kairos-io/osbuilder/controllers"
|
||||||
//+kubebuilder:scaffold:imports
|
//+kubebuilder:scaffold:imports
|
||||||
)
|
)
|
||||||
@ -44,7 +44,7 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||||
|
|
||||||
utilruntime.Must(buildv1alpha1.AddToScheme(scheme))
|
utilruntime.Must(buildv1alpha2.AddToScheme(scheme))
|
||||||
//+kubebuilder:scaffold:scheme
|
//+kubebuilder:scaffold:scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,17 +105,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = (&controllers.OSArtifactReconciler{
|
if err = (&controllers.OSArtifactReconciler{
|
||||||
Client: mgr.GetClient(),
|
|
||||||
ServingImage: serveImage,
|
ServingImage: serveImage,
|
||||||
ToolImage: toolImage,
|
ToolImage: toolImage,
|
||||||
CopierImage: copierImage,
|
CopierImage: copierImage,
|
||||||
ArtifactPodInfo: controllers.ArtifactPodInfo{
|
|
||||||
Label: copyToPodLabel,
|
|
||||||
Namespace: copyToNamespace,
|
|
||||||
Path: copyToPath,
|
|
||||||
Role: copierRole,
|
|
||||||
},
|
|
||||||
Scheme: mgr.GetScheme(),
|
|
||||||
}).SetupWithManager(mgr); err != nil {
|
}).SetupWithManager(mgr); err != nil {
|
||||||
setupLog.Error(err, "unable to create controller", "controller", "OSArtifact")
|
setupLog.Error(err, "unable to create controller", "controller", "OSArtifact")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -1,114 +1,157 @@
|
|||||||
package e2e_test
|
package e2e_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"context"
|
||||||
"fmt"
|
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
kubectl "github.com/rancher-sandbox/ele-testhelpers/kubectl"
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("ISO build test", func() {
|
var _ = Describe("ISO build test", func() {
|
||||||
//k := kubectl.New()
|
k8s := dynamic.NewForConfigOrDie(ctrl.GetConfigOrDie())
|
||||||
Context("registration", func() {
|
scheme := runtime.NewScheme()
|
||||||
|
_ = osbuilder.AddToScheme(scheme)
|
||||||
|
|
||||||
AfterEach(func() {
|
var artifactName string
|
||||||
kubectl.New().Delete("osartifacts", "-n", "default", "hello-kairos")
|
artifacts := k8s.Resource(schema.GroupVersionResource{Group: osbuilder.GroupVersion.Group, Version: osbuilder.GroupVersion.Version, Resource: "osartifacts"}).Namespace("default")
|
||||||
|
pods := k8s.Resource(schema.GroupVersionResource{Group: corev1.GroupName, Version: corev1.SchemeGroupVersion.Version, Resource: "pods"}).Namespace("default")
|
||||||
|
pvcs := k8s.Resource(schema.GroupVersionResource{Group: corev1.GroupName, Version: corev1.SchemeGroupVersion.Version, Resource: "persistentvolumeclaims"}).Namespace("default")
|
||||||
|
jobs := k8s.Resource(schema.GroupVersionResource{Group: batchv1.GroupName, Version: batchv1.SchemeGroupVersion.Version, Resource: "jobs"}).Namespace("default")
|
||||||
|
|
||||||
|
artifact := &osbuilder.OSArtifact{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "OSArtifact",
|
||||||
|
APIVersion: osbuilder.GroupVersion.String(),
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "simple-",
|
||||||
|
},
|
||||||
|
Spec: osbuilder.OSArtifactSpec{
|
||||||
|
ImageName: "quay.io/kairos/core-opensuse:latest",
|
||||||
|
ISO: true,
|
||||||
|
DiskSize: "",
|
||||||
|
Exporters: []batchv1.JobSpec{
|
||||||
|
{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
RestartPolicy: corev1.RestartPolicyNever,
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "test",
|
||||||
|
Image: "debian:latest",
|
||||||
|
Command: []string{"bash"},
|
||||||
|
Args: []string{"-xec", "[ -f /artifacts/*.iso ]"},
|
||||||
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "artifacts",
|
||||||
|
ReadOnly: true,
|
||||||
|
MountPath: "/artifacts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
uArtifact := unstructured.Unstructured{}
|
||||||
|
uArtifact.Object, _ = runtime.DefaultUnstructuredConverter.ToUnstructured(artifact)
|
||||||
|
resp, err := artifacts.Create(context.TODO(), &uArtifact, metav1.CreateOptions{})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
artifactName = resp.GetName()
|
||||||
|
|
||||||
|
Context("simple", func() {
|
||||||
|
artifactLabelSelectorReq, _ := labels.NewRequirement("build.kairos.io/artifact", selection.Equals, []string{artifactName})
|
||||||
|
artifactLabelSelector := labels.NewSelector().Add(*artifactLabelSelectorReq)
|
||||||
|
|
||||||
|
It("starts the build", func() {
|
||||||
|
Eventually(func(g Gomega) {
|
||||||
|
w, err := pods.Watch(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
var stopped bool
|
||||||
|
for !stopped {
|
||||||
|
event, ok := <-w.ResultChan()
|
||||||
|
|
||||||
|
stopped = event.Type != watch.Deleted && event.Type != watch.Error || !ok
|
||||||
|
}
|
||||||
|
}).Should(Succeed())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("creates a simple iso", func() {
|
It("exports the artifacts", func() {
|
||||||
err := kubectl.Apply("", "../../tests/fixtures/simple.yaml")
|
Eventually(func(g Gomega) {
|
||||||
|
w, err := jobs.Watch(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
var stopped bool
|
||||||
|
for !stopped {
|
||||||
|
event, ok := <-w.ResultChan()
|
||||||
|
|
||||||
|
stopped = event.Type != watch.Deleted && event.Type != watch.Error || !ok
|
||||||
|
}
|
||||||
|
}).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("artifact successfully builds", func() {
|
||||||
|
Eventually(func(g Gomega) {
|
||||||
|
w, err := artifacts.Watch(context.TODO(), metav1.ListOptions{})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
var artifact osbuilder.OSArtifact
|
||||||
|
var stopped bool
|
||||||
|
for !stopped {
|
||||||
|
event, ok := <-w.ResultChan()
|
||||||
|
stopped = !ok
|
||||||
|
|
||||||
|
if event.Type == watch.Modified && event.Object.(*unstructured.Unstructured).GetName() == artifactName {
|
||||||
|
err := scheme.Convert(event.Object, &artifact, nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
stopped = artifact.Status.Phase == osbuilder.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("cleans up resources on deleted", func() {
|
||||||
|
err := artifacts.Delete(context.TODO(), artifactName, metav1.DeleteOptions{})
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
itHasTheCorrectImage()
|
Eventually(func(g Gomega) int {
|
||||||
itHasTheCorrectLabels()
|
res, err := artifacts.List(context.TODO(), metav1.ListOptions{})
|
||||||
itCopiesTheArtifacts()
|
|
||||||
|
|
||||||
By("deleting the custom resource", func() {
|
|
||||||
err = kubectl.New().Delete("osartifacts", "-n", "default", "hello-kairos")
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
return len(res.Items)
|
||||||
|
}).WithTimeout(time.Minute).Should(Equal(0))
|
||||||
itCleansUpRoleBindings()
|
Eventually(func(g Gomega) int {
|
||||||
itDeletesTheArtifacts()
|
res, err := pods.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
return len(res.Items)
|
||||||
|
}).WithTimeout(time.Minute).Should(Equal(0))
|
||||||
|
Eventually(func(g Gomega) int {
|
||||||
|
res, err := pvcs.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
return len(res.Items)
|
||||||
|
}).WithTimeout(time.Minute).Should(Equal(0))
|
||||||
|
Eventually(func(g Gomega) int {
|
||||||
|
res, err := jobs.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
return len(res.Items)
|
||||||
|
}).WithTimeout(time.Minute).Should(Equal(0))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
func itHasTheCorrectImage() {
|
|
||||||
Eventually(func() string {
|
|
||||||
b, _ := kubectl.GetData("default", "osartifacts", "hello-kairos", "jsonpath={.spec.imageName}")
|
|
||||||
fmt.Printf("looking for image core-opensuse:latest = %+v\n", string(b))
|
|
||||||
return string(b)
|
|
||||||
}, 2*time.Minute, 2*time.Second).Should(Equal("quay.io/kairos/core-opensuse:latest"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func itHasTheCorrectLabels() {
|
|
||||||
Eventually(func() string {
|
|
||||||
b, _ := kubectl.GetData("default", "jobs", "hello-kairos", "jsonpath={.spec.template.metadata.labels.osbuild}")
|
|
||||||
fmt.Printf("looking for label workloadhello-kairos = %+v\n", string(b))
|
|
||||||
return string(b)
|
|
||||||
}, 2*time.Minute, 2*time.Second).Should(Equal("workloadhello-kairos"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func itCopiesTheArtifacts() {
|
|
||||||
nginxNamespace := "osartifactbuilder-operator-system"
|
|
||||||
Eventually(func() string {
|
|
||||||
podName := strings.TrimSpace(findPodsWithLabel(nginxNamespace, "app.kubernetes.io/name=osbuilder-nginx"))
|
|
||||||
|
|
||||||
out, _ := kubectl.RunCommandWithOutput(nginxNamespace, podName, "ls /usr/share/nginx/html")
|
|
||||||
|
|
||||||
return out
|
|
||||||
}, 15*time.Minute, 2*time.Second).Should(MatchRegexp("hello-kairos.iso"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func itCleansUpRoleBindings() {
|
|
||||||
nginxNamespace := "osartifactbuilder-operator-system"
|
|
||||||
Eventually(func() string {
|
|
||||||
rb := findRoleBindings(nginxNamespace)
|
|
||||||
|
|
||||||
return rb
|
|
||||||
}, 3*time.Minute, 2*time.Second).ShouldNot(MatchRegexp("hello-kairos"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func itDeletesTheArtifacts() {
|
|
||||||
nginxNamespace := "osartifactbuilder-operator-system"
|
|
||||||
Eventually(func() string {
|
|
||||||
podName := findPodsWithLabel(nginxNamespace, "app.kubernetes.io/name=osbuilder-nginx")
|
|
||||||
|
|
||||||
out, err := kubectl.RunCommandWithOutput(nginxNamespace, podName, "ls /usr/share/nginx/html")
|
|
||||||
Expect(err).ToNot(HaveOccurred(), out)
|
|
||||||
|
|
||||||
return out
|
|
||||||
}, 3*time.Minute, 2*time.Second).ShouldNot(MatchRegexp("hello-kairos.iso"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func findPodsWithLabel(namespace, label string) string {
|
|
||||||
kubectlCommand := fmt.Sprintf("kubectl get pods -n %s -l %s --no-headers -o custom-columns=\":metadata.name\" | head -n1", namespace, label)
|
|
||||||
cmd := exec.Command("bash", "-c", kubectlCommand)
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
Expect(err).ToNot(HaveOccurred(), stderr.String())
|
|
||||||
|
|
||||||
return strings.TrimSpace(out.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func findRoleBindings(namespace string) string {
|
|
||||||
kubectlCommand := fmt.Sprintf("kubectl get rolebindings -n %s --no-headers -o custom-columns=\":metadata.name\"", namespace)
|
|
||||||
cmd := exec.Command("bash", "-c", kubectlCommand)
|
|
||||||
var out bytes.Buffer
|
|
||||||
var stderr bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
Expect(err).ToNot(HaveOccurred(), stderr.String())
|
|
||||||
|
|
||||||
return strings.TrimSpace(out.String())
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user