<commit_before>package builderapi

import (
	"bufio"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	jwtgo "github.com/dgrijalva/jwt-go"
	"github.com/pkg/errors"
	batchv1 "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/kubernetes"

	"github.com/estafette/estafette-ci-api/api"
	"github.com/estafette/estafette-ci-api/clients/dockerhubapi"
	contracts "github.com/estafette/estafette-ci-contracts"
	crypt "github.com/estafette/estafette-ci-crypt"
	manifest "github.com/estafette/estafette-ci-manifest"
	"github.com/rs/zerolog/log"
)

var (
	// ErrJobNotFound is returned if a job can't be found
	ErrJobNotFound = errors.New("The job can't be found")
)

// Client is the interface for running kubernetes commands specific to this application
type Client interface {
	CreateCiBuilderJob(ctx context.Context, params CiBuilderParams) (job *batchv1.Job, err error)
	RemoveCiBuilderJob(ctx context.Context, jobName string) (err error)
	CancelCiBuilderJob(ctx context.Context, jobName string) (err error)
	RemoveCiBuilderConfigMap(ctx context.Context, configmapName string) (err error)
	RemoveCiBuilderSecret(ctx context.Context, secretName string) (err error)
	RemoveCiBuilderImagePullSecret(ctx context.Context, secretName string) (err error)
	TailCiBuilderJobLogs(ctx context.Context, jobName string, logChannel chan contracts.TailLogLine) (err error)
	GetJobName(ctx context.Context, jobType, repoOwner, repoName, id string) (jobname string)
}

// NewClient returns a new estafette.Client
func NewClient(config *api.APIConfig, encryptedConfig *api.APIConfig, secretHelper crypt.SecretHelper, kubeClientset *kubernetes.Clientset, dockerHubClient dockerhubapi.Client) Client {

	return &client{
		kubeClientset:   kubeClientset,
		dockerHubClient: dockerHubClient,
		config:          config,
		encryptedConfig: encryptedConfig,
		secretHelper:    secretHelper,
	}
}

type client struct {
	kubeClientset   *kubernetes.Clientset
	dockerHubClient dockerhubapi.Client
	config          *api.APIConfig
	encryptedConfig *api.APIConfig
	secretHelper    crypt.SecretHelper
}

// CreateCiBuilderJob creates an estafette-ci-builder job in Kubernetes to run the estafette build
func (c *client) CreateCiBuilderJob(ctx context.Context, ciBuilderParams CiBuilderParams) (job *batchv1.Job, err error) {

	// create job name of max 63 chars
	jobName := c.getCiBuilderJobName(ctx, ciBuilderParams)

	log.Info().Msgf("Creating job %v...", jobName)

	// extend builder config to parameterize the builder and replace all other envvars to improve security
	localBuilderConfig, err := c.getBuilderConfig(ctx, ciBuilderParams, jobName)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed creating job %v builder config...", jobName)
	}
	builderConfigJSONBytes, err := json.Marshal(localBuilderConfig)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed marshalling job %v builder config...", jobName)
	}
	builderConfigValue := string(builderConfigJSONBytes)
	builderConfigValue, newKey, err := c.secretHelper.ReencryptAllEnvelopes(builderConfigValue, ciBuilderParams.GetFullRepoPath(), false)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed re-encrypting job %v builder config secrets...", jobName)
	}

	// create configmap for builder config
	err = c.createCiBuilderConfigMap(ctx, ciBuilderParams, jobName, builderConfigValue)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed creating job %v configmap...", jobName)
	}

	// create secret for decryption key secret
	err = c.createCiBuilderSecret(ctx, ciBuilderParams, jobName, newKey)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed creating job %v secret...", jobName)
	}

	createImagePullSecret, err := c.createCiBuilderImagePullSecret(ctx, ciBuilderParams, jobName)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed creating job %v image pull secret...", jobName)
	}

	// other job config
	repository := "estafette/estafette-ci-builder"
	tag := ciBuilderParams.Track
	image := fmt.Sprintf("%v:%v", repository, tag)
	imagePullPolicy := v1.PullAlways
	digest, err := c.dockerHubClient.GetDigestCached(ctx, repository, tag)
	if err == nil && digest.Digest != "" {
		image = fmt.Sprintf("%v@%v", repository, digest.Digest)
		imagePullPolicy = v1.PullIfNotPresent
	}
	privileged := true

	volumes, volumeMounts := c.getCiBuilderJobVolumesAndMounts(ctx, ciBuilderParams, jobName)

	labels := map[string]string{
		"createdBy": "estafette",
		"jobType":   ciBuilderParams.JobType,
	}

	job = &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jobName,<commit_msg>create image pull secret, log error as warning and continue<commit_after>package builderapi

import (
	"bufio"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	jwtgo "github.com/dgrijalva/jwt-go"
	"github.com/pkg/errors"
	batchv1 "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/kubernetes"

	"github.com/estafette/estafette-ci-api/api"
	"github.com/estafette/estafette-ci-api/clients/dockerhubapi"
	contracts "github.com/estafette/estafette-ci-contracts"
	crypt "github.com/estafette/estafette-ci-crypt"
	manifest "github.com/estafette/estafette-ci-manifest"
	"github.com/rs/zerolog/log"
)

var (
	// ErrJobNotFound is returned if a job can't be found
	ErrJobNotFound = errors.New("The job can't be found")
)

// Client is the interface for running kubernetes commands specific to this application
type Client interface {
	CreateCiBuilderJob(ctx context.Context, params CiBuilderParams) (job *batchv1.Job, err error)
	RemoveCiBuilderJob(ctx context.Context, jobName string) (err error)
	CancelCiBuilderJob(ctx context.Context, jobName string) (err error)
	RemoveCiBuilderConfigMap(ctx context.Context, configmapName string) (err error)
	RemoveCiBuilderSecret(ctx context.Context, secretName string) (err error)
	RemoveCiBuilderImagePullSecret(ctx context.Context, secretName string) (err error)
	TailCiBuilderJobLogs(ctx context.Context, jobName string, logChannel chan contracts.TailLogLine) (err error)
	GetJobName(ctx context.Context, jobType, repoOwner, repoName, id string) (jobname string)
}

// NewClient returns a new estafette.Client
func NewClient(config *api.APIConfig, encryptedConfig *api.APIConfig, secretHelper crypt.SecretHelper, kubeClientset *kubernetes.Clientset, dockerHubClient dockerhubapi.Client) Client {

	return &client{
		kubeClientset:   kubeClientset,
		dockerHubClient: dockerHubClient,
		config:          config,
		encryptedConfig: encryptedConfig,
		secretHelper:    secretHelper,
	}
}

type client struct {
	kubeClientset   *kubernetes.Clientset
	dockerHubClient dockerhubapi.Client
	config          *api.APIConfig
	encryptedConfig *api.APIConfig
	secretHelper    crypt.SecretHelper
}

// CreateCiBuilderJob creates an estafette-ci-builder job in Kubernetes to run the estafette build
func (c *client) CreateCiBuilderJob(ctx context.Context, ciBuilderParams CiBuilderParams) (job *batchv1.Job, err error) {

	// create job name of max 63 chars
	jobName := c.getCiBuilderJobName(ctx, ciBuilderParams)

	log.Info().Msgf("Creating job %v...", jobName)

	// extend builder config to parameterize the builder and replace all other envvars to improve security
	localBuilderConfig, err := c.getBuilderConfig(ctx, ciBuilderParams, jobName)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed creating job %v builder config...", jobName)
	}
	builderConfigJSONBytes, err := json.Marshal(localBuilderConfig)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed marshalling job %v builder config...", jobName)
	}
	builderConfigValue := string(builderConfigJSONBytes)
	builderConfigValue, newKey, err := c.secretHelper.ReencryptAllEnvelopes(builderConfigValue, ciBuilderParams.GetFullRepoPath(), false)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed re-encrypting job %v builder config secrets...", jobName)
	}

	// create configmap for builder config
	err = c.createCiBuilderConfigMap(ctx, ciBuilderParams, jobName, builderConfigValue)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed creating job %v configmap...", jobName)
	}

	// create secret for decryption key secret
	err = c.createCiBuilderSecret(ctx, ciBuilderParams, jobName, newKey)
	if err != nil {
		return nil, errors.Wrapf(err, "Failed creating job %v secret...", jobName)
	}

	createImagePullSecret, err := c.createCiBuilderImagePullSecret(ctx, ciBuilderParams, jobName)
	if err != nil {
		log.Warn().Err(err).Msgf("Failed creating job %v image pull secret...", jobName)
		// return nil, errors.Wrapf(err, "Failed creating job %v image pull secret...", jobName)
	}

	// other job config
	repository := "estafette/estafette-ci-builder"
	tag := ciBuilderParams.Track
	image := fmt.Sprintf("%v:%v", repository, tag)
	imagePullPolicy := v1.PullAlways
	digest, err := c.dockerHubClient.GetDigestCached(ctx, repository, tag)
	if err == nil && digest.Digest != "" {
		image = fmt.Sprintf("%v@%v", repository, digest.Digest)
		imagePullPolicy = v1.PullIfNotPresent
	}
	privileged := true

	volumes, volumeMounts := c.getCiBuilderJobVolumesAndMounts(ctx, ciBuilderParams, jobName)

	labels := map[string]string{
		"createdBy": "estafette",
		"jobType":   ciBuilderParams.JobType,
	}

	job = &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jobName,
{'fraction_non_alphanumeric': 0.11299609294674069, 'fraction_numerical': 0.004935225169648365, 'mean_word_length': 5.000616903146206, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1526110', 'n_tokens_mistral': 3379, 'n_tokens_neox': 3232, 'n_words': 786}