Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Google Creds for Results #1652

Merged
merged 1 commit into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/TektonResult.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,31 @@ stringData:
S3_MULTI_PART_SIZE: "5242880"
```


### GCS specific Property
khrm marked this conversation as resolved.
Show resolved Hide resolved
The follow keys are needed for enabling GCS storage of logs:
```yaml
apiVersion: operator.tekton.dev/v1alpha1
kind: TektonResult
metadata:
name: result
spec:
gcs_creds_secret_name: <value>
gcc_creds_secret_key: <value>
gcs_bucket_name: <value>
```

We need to create a secret with google application creds for a bucket `foo-bar` like below:

```
kubectl create secret generic gcs-credentials --from-file=creds.json
```

To know more about Application Default Credentials in `creds.json` that is use to create above secret for GCS, please visit: https://cloud.google.com/docs/authentication/application-default-credentials

In the above example, our properties are:
```
gcs_creds_secret_name: gcs-credentials
gcc_creds_secret_key: creds.json
gcs_bucket_name: foo-bar
```
khrm marked this conversation as resolved.
Show resolved Hide resolved
8 changes: 6 additions & 2 deletions pkg/apis/operator/v1alpha1/tektonresult_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ type ResultsAPIProperties struct {
S3AccessKeyID string `json:"s3_access_key_id,omitempty"`
S3SecretAccessKey string `json:"s3_secret_access_key,omitempty"`
S3MultiPartSize int64 `json:"s3_multi_part_size,omitempty"`
LoggingPVCName string `json:"logging_pvc_name"`
LoggingPVCName string `json:"logging_pvc_name,omitempty"`
GcsBucketName string `json:"gcs_bucket_name,omitempty"`
StorageEmulatorHost string `json:"storage_emulator_host,omitempty"`
khrm marked this conversation as resolved.
Show resolved Hide resolved
// name of the secret used to get S3 credentials and
// pass it as environment variables to the "tekton-results-api" deployment under "api" container
SecretName string `json:"secret_name,omitempty"`
SecretName string `json:"secret_name,omitempty"`
GCSCredsSecretName string `json:"gcs_creds_secret_name,omitempty"`
GCSCredsSecretKey string `json:"gcs_creds_secret_key,omitempty"`
khrm marked this conversation as resolved.
Show resolved Hide resolved
}

// TektonResultStatus defines the observed state of TektonResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ data:
LOGS_TYPE=File
LOGS_BUFFER_SIZE=32768
LOGS_PATH=/logs
STORAGE_EMULATOR_HOST=
S3_BUCKET_NAME=
S3_ENDPOINT=
S3_HOSTNAME_IMMUTABLE=false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: tekton-results-api
app.kubernetes.io/part-of: tekton-results
app.kubernetes.io/version: 9f84a1f
name: tekton-results-api
namespace: tekton-pipelines
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: tekton-results-api
app.kubernetes.io/version: 9f84a1f
template:
metadata:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
labels:
app.kubernetes.io/name: tekton-results-api
app.kubernetes.io/version: 9f84a1f
spec:
containers:
- env:
- name: test
value: tekton-results-postgres-service.tekton-pipelines.svc.cluster.local
image: gcr.io/tekton-releases/github.com/tektoncd/results/cmd/api:9f84a1f@sha256:606816e51ebecb58fccc28f5a95699255ed8742470df673294ce25f69ffc451c
name: test
- env:
- name: DB_HOST
value: tekton-results-postgres-service.tekton-pipelines.svc.cluster.local
- name: DB_USER
valueFrom:
secretKeyRef:
key: POSTGRES_USER
name: tekton-results-postgres
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
key: POSTGRES_PASSWORD
name: tekton-results-postgres
- name: DB_NAME
value: tekton-results
- name: S3_BUCKET_NAME # used to verify that, this value replaced by transformer
value: foo
- name: GOOGLE_APPLICATION_CREDENTIALS
value: creds.json
image: gcr.io/tekton-releases/github.com/tektoncd/results/cmd/api:9f84a1f@sha256:606816e51ebecb58fccc28f5a95699255ed8742470df673294ce25f69ffc451c
name: api
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_BIND_SERVICE
drop:
- ALL
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /etc/tekton/results
name: config
readOnly: true
- mountPath: /etc/tls
name: tls
readOnly: true
- mountPath: /creds/google
name: google-creds
serviceAccountName: tekton-results-api
volumes:
- configMap:
name: tekton-results-api-config
name: config
- name: tls
secret:
secretName: tekton-results-tls
- name: google-creds
secret:
secretName: foo
items:
- key: bar
path: bar
107 changes: 100 additions & 7 deletions pkg/reconciler/kubernetes/tektonresult/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ import (

const (
// Results ConfigMap
configAPI = "tekton-results-api-config"
deploymentAPI = "tekton-results-api"
configINFO = "tekton-results-info"
configMetrics = "tekton-results-config-observability"
configPostgresDB = "tekton-results-postgres"
pvcLoggingVolume = "tekton-logs"
apiContainerName = "api"
configAPI = "tekton-results-api-config"
deploymentAPI = "tekton-results-api"
configINFO = "tekton-results-info"
configMetrics = "tekton-results-config-observability"
configPostgresDB = "tekton-results-postgres"
pvcLoggingVolume = "tekton-logs"
apiContainerName = "api"
googleAPPCredsEnvName = "GOOGLE_APPLICATION_CREDENTIALS"
googleCredsVolName = "google-creds"
googleCredsPath = "/creds/google"
)

var (
Expand Down Expand Up @@ -73,6 +76,7 @@ func (r *Reconciler) transform(ctx context.Context, manifest *mf.Manifest, comp
updateApiConfig(instance.Spec.ResultsAPIProperties),
enablePVCLogging(instance.Spec.ResultsAPIProperties),
updateEnvWithSecretName(instance.Spec.ResultsAPIProperties),
populateGoogleCreds(instance.Spec.ResultsAPIProperties),
common.AddDeploymentRestrictedPSA(),
common.AddStatefulSetRestrictedPSA(),
common.DeploymentImages(resultImgs),
Expand Down Expand Up @@ -227,6 +231,95 @@ func updateApiConfig(p interface{}) mf.Transformer {
}
}

func populateGoogleCreds(props v1alpha1.ResultsAPIProperties) mf.Transformer {
return func(u *unstructured.Unstructured) error {
if props.LogsType != "GCS" || props.GCSCredsSecretName == "" ||
props.GCSCredsSecretKey == "" || !props.LogsAPI ||
u.GetKind() != "Deployment" || u.GetName() != deploymentAPI {
return nil
}

d := &appsv1.Deployment{}
err := k8sruntime.DefaultUnstructuredConverter.FromUnstructured(u.Object, d)
if err != nil {
return err
}

// find the matching container and add env and secret name object
for i, container := range d.Spec.Template.Spec.Containers {
if container.Name != apiContainerName {
continue
}
add := true
vol := corev1.Volume{
Name: googleCredsVolName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: props.GCSCredsSecretName,
Items: []corev1.KeyToPath{{
Key: props.GCSCredsSecretKey,
Path: props.GCSCredsSecretKey,
}},
},
},
}
for k := 0; k < len(d.Spec.Template.Spec.Volumes); k++ {
if d.Spec.Template.Spec.Volumes[k].Name == googleCredsVolName {
d.Spec.Template.Spec.Volumes[k] = vol
add = false
}
}
if add {
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, vol)
}

volMount := corev1.VolumeMount{
Name: googleCredsVolName,
MountPath: googleCredsPath,
}

add = true
for k := 0; k < len(d.Spec.Template.Spec.Containers[i].VolumeMounts); k++ {
if d.Spec.Template.Spec.Containers[i].VolumeMounts[k].Name == googleCredsVolName {
d.Spec.Template.Spec.Containers[i].VolumeMounts[k] = volMount
add = false
}
}
if add {
d.Spec.Template.Spec.Containers[i].VolumeMounts = append(
d.Spec.Template.Spec.Containers[i].VolumeMounts, volMount)
}

path := googleCredsPath + "/" + props.GCSCredsSecretKey
newEnv := corev1.EnvVar{
Name: googleAPPCredsEnvName,
Value: path,
}
add = true
for k, env := range d.Spec.Template.Spec.Containers[i].Env {
if env.Name == googleAPPCredsEnvName {
d.Spec.Template.Spec.Containers[i].Env[k] = newEnv
add = false
break
}
}
if add {
d.Spec.Template.Spec.Containers[i].Env = append(
d.Spec.Template.Spec.Containers[i].Env, newEnv)
}

break
}

uObj, err := k8sruntime.DefaultUnstructuredConverter.ToUnstructured(d)
if err != nil {
return err
}
u.SetUnstructuredContent(uObj)
return nil
}
}

// updates env keys with the secret name into "tekton-results-api" deployment in "api" container
func updateEnvWithSecretName(props v1alpha1.ResultsAPIProperties) mf.Transformer {
return func(u *unstructured.Unstructured) error {
Expand Down
43 changes: 43 additions & 0 deletions pkg/reconciler/kubernetes/tektonresult/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func Test_updateApiConfig(t *testing.T) {
S3AccessKeyID: "secret",
S3SecretAccessKey: "secret",
S3MultiPartSize: 123,
StorageEmulatorHost: "http://localhost:9004",
}

manifest, err = manifest.Transform(updateApiConfig(prop))
Expand All @@ -114,6 +115,7 @@ LOGS_API=true
LOGS_TYPE=s3
LOGS_BUFFER_SIZE=12321
LOGS_PATH=/logs/test
STORAGE_EMULATOR_HOST=http://localhost:9004
S3_BUCKET_NAME=test
S3_ENDPOINT=test
S3_HOSTNAME_IMMUTABLE=true
Expand All @@ -123,6 +125,47 @@ S3_SECRET_ACCESS_KEY=secret
S3_MULTI_PART_SIZE=123`)
}

func Test_GoogleCred(t *testing.T) {
testData := []string{path.Join("testdata", "api-deployment-gcs.yaml"), path.Join("testdata", "api-deployment.yaml")}
for i := range testData {
manifest, err := mf.ManifestFrom(mf.Recursive(testData[i]))
assert.NilError(t, err)

deployment := &appsv1.Deployment{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(manifest.Resources()[0].Object, deployment)
assert.NilError(t, err)
prop := v1alpha1.ResultsAPIProperties{
LogsAPI: true,
LogsType: "GCS",
GCSCredsSecretName: "foo-test",
GCSCredsSecretKey: "bar-test",
}

manifest, err = manifest.Transform(populateGoogleCreds(prop))
assert.NilError(t, err)

err = runtime.DefaultUnstructuredConverter.FromUnstructured(manifest.Resources()[0].Object, deployment)
assert.NilError(t, err)

path := googleCredsPath + "/" + prop.GCSCredsSecretKey
newEnv := corev1.EnvVar{
Name: googleAPPCredsEnvName,
Value: path,
}

var i int
for i = range deployment.Spec.Template.Spec.Containers {
if deployment.Spec.Template.Spec.Containers[i].Name != apiContainerName {
continue
}
}

assert.Equal(t, deployment.Spec.Template.Spec.Volumes[2].Name, googleCredsVolName)
assert.Equal(t, deployment.Spec.Template.Spec.Containers[i].VolumeMounts[2].Name, googleCredsVolName)
assert.Equal(t, deployment.Spec.Template.Spec.Containers[i].Env[5], newEnv)
}
}

func TestUpdateEnvWithSecretName(t *testing.T) {
testData := path.Join("testdata", "api-deployment.yaml")
manifest, err := mf.ManifestFrom(mf.Recursive(testData))
Expand Down