Skip to content

Commit

Permalink
chore(release): v1.2.1 (#198)
Browse files Browse the repository at this point in the history
* fix(timeout): avoid revalidation when patching Spinnaker Status. (#192)

* fix(timeout): avoid revalidation when patching Spinnaker Status.

* fix(timeout): add unit test.

* fix(timeout): add unit test.

(cherry picked from commit 2e09945)

* chore(cve): fix for CVE-2020-13757 (#193)

(cherry picked from commit e8de226)

* feat(validator/aws): add aws account validator. (#195)

* feat(validator/aws): add aws account validator.

* feat(validator/aws): solve PR comments.

(cherry picked from commit 123572c)

* chore(halyard): Updated halyard version (#197)

(cherry picked from commit 7eb2a58)

Co-authored-by: Jossue <jossue@gmail.com>
  • Loading branch information
cristhian-castaneda and Jossuecito committed Nov 16, 2020
1 parent ef06aeb commit e334645
Show file tree
Hide file tree
Showing 16 changed files with 633 additions and 68 deletions.
44 changes: 25 additions & 19 deletions build-tools/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
ARG BUILDER
FROM ${BUILDER} as builder

FROM python:2.7.18-alpine3.11
FROM python:3.7-alpine3.12

ENV OPERATOR=/usr/local/bin/spinnaker-operator \
USER_UID=1001 \
USER_NAME=spinnaker-operator \
AWS_BINARY_RELEASE_DATE=2019-08-22 \
KUBECTL_RELEASE=1.14.6 \
AWS_CLI_RELEASE=1.18.58 \
OPERATOR_HOME=/opt/spinnaker-operator
AWS_AIM_AUTHENTICATOR_VERSION=0.4.0 \
KUBECTL_RELEASE=1.17.7 \
AWS_CLI_VERSION=1.18.109 \
OPERATOR_HOME=/opt/spinnaker-operator \
GOOGLE_CLOUD_SDK_VERSION=313.0.1 \
PATH="$PATH:/usr/local/bin/:/opt/google-cloud-sdk/bin/:/usr/local/bin/aws-iam-authenticator"

EXPOSE 8383
RUN apk update \
&& apk add ca-certificates bash curl wget unzip \
&& adduser -D -u ${USER_UID} ${USER_NAME} \
&& apk upgrade

# Install aws iam authenticator
RUN curl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/${KUBECTL_RELEASE}/${AWS_BINARY_RELEASE_DATE}/bin/linux/amd64/aws-iam-authenticator && \
chmod +x ./aws-iam-authenticator && \
mv ./aws-iam-authenticator /usr/local/bin/aws-iam-authenticator
ENV PATH "$PATH:/usr/local/bin/aws-iam-authenticator"
# Google cloud SDK with anthos removed for CVE and because we don't need it
RUN wget -nv https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-${GOOGLE_CLOUD_SDK_VERSION}-linux-x86_64.tar.gz \
&& mkdir -p /opt && cd /opt \
&& tar -xzf /google-cloud-sdk-${GOOGLE_CLOUD_SDK_VERSION}-linux-x86_64.tar.gz \
&& rm /google-cloud-sdk-${GOOGLE_CLOUD_SDK_VERSION}-linux-x86_64.tar.gz \
&& CLOUDSDK_PYTHON="python3" /opt/google-cloud-sdk/install.sh --usage-reporting=false --bash-completion=false --additional-components app-engine-java app-engine-go \
&& rm -rf ~/.config/gcloud \
&& gcloud components remove --quiet anthoscli \
&& rm -rf /opt/google-cloud-sdk/.install/.backup

# kubectl + AWS IAM authenticator
RUN wget https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_RELEASE}/bin/linux/amd64/kubectl \
&& chmod +x kubectl \
&& mv ./kubectl /usr/local/bin/kubectl \
&& wget -O aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v${AWS_AIM_AUTHENTICATOR_VERSION}/aws-iam-authenticator_${AWS_AIM_AUTHENTICATOR_VERSION}_linux_amd64 \
&& chmod +x ./aws-iam-authenticator \
&& mv ./aws-iam-authenticator /usr/local/bin/aws-iam-authenticator

# Install aws-cli
RUN pip install --upgrade awscli==${AWS_CLI_RELEASE} \
RUN pip install --upgrade awscli==${AWS_CLI_VERSION} \
&& pip uninstall -y pip

# Install gcloud
RUN wget -nv https://dl.google.com/dl/cloudsdk/release/google-cloud-sdk.zip && \
unzip -qq google-cloud-sdk.zip -d /opt && \
rm google-cloud-sdk.zip && \
CLOUDSDK_PYTHON="python2.7" /opt/google-cloud-sdk/install.sh --usage-reporting=false --bash-completion=false && \
rm -rf ~/.config/gcloud
ENV PATH=$PATH:/opt/google-cloud-sdk/bin/

USER ${USER_NAME}

# Everything after this line is never cached
Expand Down
2 changes: 1 addition & 1 deletion halyard-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
operator-c1d641c
operator-8e0406f
7 changes: 7 additions & 0 deletions pkg/apis/spinnaker/interfaces/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,10 @@ func (s *SpinnakerValidation) GetValidationSettings() *ValidationSetting {
FrequencySeconds: f,
}
}

func (s *SpinnakerValidation) IsProviderValidationEnabled(key string) bool {
if provider, ok := s.Providers[key]; ok {
return provider.Enabled
}
return true
}
31 changes: 11 additions & 20 deletions pkg/controller/spinnakerservice/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package spinnakerservice

import (
"context"
"errors"
"github.com/armory/spinnaker-operator/pkg/apis/spinnaker/interfaces"
"github.com/armory/spinnaker-operator/pkg/util"
"github.com/go-logr/logr"
Expand Down Expand Up @@ -80,10 +79,6 @@ func (s *statusChecker) checks(instance interfaces.SpinnakerService) error {
return err
}

if Updating == status.Status {
return errors.New("spinnaker still updating")
}

return nil
}

Expand All @@ -95,6 +90,7 @@ func (s *statusChecker) getStatus(instance interfaces.SpinnakerService, pods []v
return Na, nil
}

var podsRunningOk []v1.Pod
for _, p := range pods {
switch p.Status.Phase {
case v1.PodRunning:
Expand All @@ -106,18 +102,12 @@ func (s *statusChecker) getStatus(instance interfaces.SpinnakerService, pods []v
s.evtRecorder.Eventf(instance, v1.EventTypeWarning, "DeployFailed", "Pod %s exceeds the time limit", p.Name)
return Failure, nil
}

for _, cs := range p.Status.ContainerStatuses {
if cs.State.Terminated != nil {
s.evtRecorder.Eventf(instance, v1.EventTypeWarning, "DeployInProgress", "Pod %s is in Phase: %s. Message: %s", p.Name, p.Status.Phase, cs.State.Terminated.Reason)
return Updating, nil
}
if cs.State.Waiting != nil {
s.evtRecorder.Eventf(instance, v1.EventTypeWarning, "DeployInProgress", "Pod %s is in Phase: %s. Message: %s", p.Name, p.Status.Phase, cs.State.Waiting.Reason)
return Updating, nil
}
if !cs.Ready {
s.evtRecorder.Eventf(instance, v1.EventTypeWarning, "DeployInProgress", "Pod %s is in Phase: %s. Message: %s", p.Name, p.Status.Phase, p.Status.Reason)
return Updating, nil
if cs.State.Running != nil {
if p.DeletionTimestamp == nil {
podsRunningOk = append(podsRunningOk, p)
}
}
}
break
Expand All @@ -138,10 +128,6 @@ func (s *statusChecker) getStatus(instance interfaces.SpinnakerService, pods []v
s.evtRecorder.Eventf(instance, v1.EventTypeWarning, "DeployFailed", "Pod %s has not been able to reach a healthy state is in Phase: %s. Message: %s", p.Name, p.Status.Phase, cs.State.Waiting.Reason)
return Failure, nil
}
if !cs.Ready {
s.evtRecorder.Eventf(instance, v1.EventTypeWarning, "DeployInProgress", "Pod %s is in Phase: %s. Message: %s", p.Name, p.Status.Phase, p.Status.Reason)
return Updating, nil
}
}
break
case v1.PodFailed, v1.PodUnknown:
Expand All @@ -151,5 +137,10 @@ func (s *statusChecker) getStatus(instance interfaces.SpinnakerService, pods []v
break
}
}

if instance.GetStatus().ServiceCount != len(podsRunningOk) {
status = Updating
}

return status, nil
}
42 changes: 41 additions & 1 deletion pkg/controller/spinnakerservice/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import (
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"testing"
"time"
)

func Test_statusChecker_checks(t *testing.T) {
Expand Down Expand Up @@ -47,6 +49,15 @@ func Test_statusChecker_checks(t *testing.T) {
mockedPods: []v1.Pod{{
Status: v1.PodStatus{
Phase: v1.PodRunning,
ContainerStatuses: []v1.ContainerStatus{
{
State: v1.ContainerState{
Running: &v1.ContainerStateRunning{
StartedAt: metav1.Time{Time: time.Now()},
},
},
},
},
},
}},
mockedDeployments: []appsv1.Deployment{{}},
Expand Down Expand Up @@ -93,7 +104,7 @@ func Test_statusChecker_checks(t *testing.T) {
mockedDeployments: []appsv1.Deployment{{}},
mockedExceededTime: false,
},
wantErr: true,
wantErr: false,
status: Updating,
},
{
Expand Down Expand Up @@ -140,6 +151,35 @@ func Test_statusChecker_checks(t *testing.T) {
wantErr: false,
status: Na,
},
{
name: "Spinsvc should have Updating status because pods are terminating",
fields: fields{},
args: args{
instance: spinSvc,
mockedPods: []v1.Pod{{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: &metav1.Time{Time: time.Now()},
},
Status: v1.PodStatus{
Phase: v1.PodRunning,
ContainerStatuses: []v1.ContainerStatus{
{
State: v1.ContainerState{
Running: &v1.ContainerStateRunning{
StartedAt: metav1.Time{Time: time.Now()},
},
},
},
},
},
},
},
mockedDeployments: []appsv1.Deployment{{}},
mockedExceededTime: false,
},
wantErr: false,
status: Updating,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/spinnakerservice/testdata/spinsvc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ spec:
status:
apiUrl: http://acme.com
uiUrl: http://acme.com
serviceCount: 1
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ package spinnakervalidating

import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/armory/spinnaker-operator/pkg/apis/spinnaker/interfaces"
"github.com/armory/spinnaker-operator/pkg/controller/webhook"
"github.com/armory/spinnaker-operator/pkg/halyard"
"github.com/armory/spinnaker-operator/pkg/secrets"
"github.com/armory/spinnaker-operator/pkg/validate"
"gomodules.xyz/jsonpatch/v2"
"k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
Expand All @@ -20,6 +24,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"time"
)

const (
ValidationConfigHashKey = "validation"
DefaultValidationFreqSeconds = 10
)

// +kubebuilder:webhook:path=/validate-v1-spinnakerservice,mutating=false,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=vpod.kb.io
Expand Down Expand Up @@ -65,6 +75,13 @@ func (v *spinnakerValidatingController) Handle(ctx context.Context, req admissio
return admission.ValidationResponse(true, "")
}

hc := svc.GetStatus().GetHash(ValidationConfigHashKey)
if hc != nil {
if !v.NeedsValidation(hc.LastUpdatedAt) {
return admission.Allowed("")
}
}

opts := validate.Options{
Ctx: secrets.NewContext(ctx, v.restConfig, req.Namespace),
Client: v.client,
Expand All @@ -86,6 +103,7 @@ func (v *spinnakerValidatingController) Handle(ctx context.Context, req admissio
// Update the status with any admission status change, only if there's already an existing SpinnakerService
if req.AdmissionRequest.Operation == v1beta1.Update {
if len(validationResult.StatusPatches) > 0 {
validationResult.StatusPatches = append(validationResult.StatusPatches, v.addLastValidation(svc))
log.Info(fmt.Sprintf("patching SpinnakerService status with %v", validationResult.StatusPatches), "metadata.name", svc.GetName())
if err := v.client.Status().Patch(ctx, svc, &precomputedPatch{validationResult}); err != nil {
return admission.Errored(http.StatusInternalServerError, err)
Expand All @@ -96,6 +114,31 @@ func (v *spinnakerValidatingController) Handle(ctx context.Context, req admissio
return admission.ValidationResponse(true, "")
}

func (v *spinnakerValidatingController) NeedsValidation(lastValid metav1.Time) bool {
if lastValid.IsZero() {
return true
}
n := lastValid.Time.Add(time.Duration(DefaultValidationFreqSeconds) * time.Second)
return time.Now().After(n)
}

func (v *spinnakerValidatingController) getHash(config interface{}) (string, error) {
data, err := json.Marshal(config)
if err != nil {
return "", err
}
m := md5.Sum(data)
return hex.EncodeToString(m[:]), nil
}

func (v *spinnakerValidatingController) addLastValidation(svc interfaces.SpinnakerService) jsonpatch.JsonPatchOperation {
hash, _ := v.getHash(svc.GetStatus())
return jsonpatch.NewPatch("replace", fmt.Sprintf("/status/lastDeployed/%s", ValidationConfigHashKey), interfaces.HashStatus{
Hash: hash,
LastUpdatedAt: metav1.NewTime(time.Now()),
})
}

// InjectClient injects the client.
func (v *spinnakerValidatingController) InjectClient(c client.Client) error {
v.client = c
Expand Down
1 change: 1 addition & 0 deletions pkg/halyard/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var validationsToSkip = []string{
"KubernetesAccountValidator",
"DeploymentConfigurationValidator",
"DockerRegistryAccountValidator",
"AwsAccountValidator",
}

type validationEnableRule struct {
Expand Down

0 comments on commit e334645

Please sign in to comment.