Compare commits

...

14 Commits

Author SHA1 Message Date
Alex Collins
e2756210d9 dep ensure 2019-06-21 16:08:54 -07:00
Alex Collins
97565c0895 Merged from master 2019-06-21 16:02:22 -07:00
Alex Collins
9a6f9ff824 Update manifests to v1.1.0-rc2 2019-06-21 16:00:06 -07:00
dthomson25
290cefaedd Use correct healthcheck for Rollout with empty steps list (#1776) 2019-06-21 15:04:19 -07:00
Jesse Suen
69e49d708f Move remarshaling to happen only during comparison, instead of manifest generation (#1788) 2019-06-21 15:01:07 -07:00
Jesse Suen
e27be81947 Server side rotation of cluster bearer tokens (#1744) 2019-06-21 13:41:14 -07:00
Alexander Matyushentsev
4febc66a64 Add health check to the controller deployment (#1785) 2019-06-21 11:02:43 -07:00
Alexander Matyushentsev
a984af76f1 Make status fields as optional fields (#1779) 2019-06-21 11:02:37 -07:00
Alexander Matyushentsev
be6a0fc21f Sync status button should be hidden if there is no sync operation (#1770) 2019-06-21 11:02:31 -07:00
Alexander Matyushentsev
122729e2a4 UI should allow editing repo URL (#1763) 2019-06-21 11:02:25 -07:00
Alex Collins
84635d4dbe Fixes a bug where cluster objs could leave app is running op state. C… (#1796) 2019-06-21 10:52:43 -07:00
Alex Collins
46550e009b Update manifests to v1.1.0-rc1 2019-06-14 11:16:08 -07:00
Alex Collins
683b9072b8 codegen 2019-06-14 11:15:09 -07:00
Alex Collins
33e66dcf5e Update manifests to v1.1.0-rc1 2019-06-14 11:13:23 -07:00
44 changed files with 889 additions and 217 deletions

2
Gopkg.lock generated
View File

@@ -1496,7 +1496,7 @@
version = "v0.1.0"
[[projects]]
digest = "1:aae856b28533bfdb39112514290f7722d00a55a99d2d0b897c6ee82e6feb87b3"
digest = "1:6fb17dac788a9f25fc454d8324180779687c46b60686c6e64136183c8134111e"
name = "k8s.io/kube-openapi"
packages = [
"cmd/openapi-gen",

View File

@@ -1 +1 @@
1.0.0
1.1.0-rc2

View File

@@ -868,6 +868,31 @@
}
}
},
"/api/v1/clusters/{server}/rotate-auth": {
"post": {
"tags": [
"ClusterService"
],
"summary": "RotateAuth returns a cluster by server address",
"operationId": "RotateAuth",
"parameters": [
{
"type": "string",
"name": "server",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "(empty)",
"schema": {
"$ref": "#/definitions/clusterClusterResponse"
}
}
}
}
},
"/api/v1/projects": {
"get": {
"tags": [

View File

@@ -22,6 +22,7 @@ import (
clusterpkg "github.com/argoproj/argo-cd/pkg/apiclient/cluster"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/clusterauth"
)
// NewClusterCommand returns a new instance of an `argocd cluster` command
@@ -39,6 +40,7 @@ func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientc
command.AddCommand(NewClusterGetCommand(clientOpts))
command.AddCommand(NewClusterListCommand(clientOpts))
command.AddCommand(NewClusterRemoveCommand(clientOpts))
command.AddCommand(NewClusterRotateAuthCommand(clientOpts))
return command
}
@@ -86,7 +88,7 @@ func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clie
// Install RBAC resources for managing the cluster
clientset, err := kubernetes.NewForConfig(conf)
errors.CheckError(err)
managerBearerToken, err = common.InstallClusterManagerRBAC(clientset, systemNamespace)
managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, systemNamespace)
errors.CheckError(err)
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
@@ -178,7 +180,7 @@ func NewCluster(name string, conf *rest.Config, managerBearerToken string, awsAu
// NewClusterGetCommand returns a new instance of an `argocd cluster get` command
func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "get",
Use: "get CLUSTER",
Short: "Get cluster information",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
@@ -202,7 +204,7 @@ func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
// NewClusterRemoveCommand returns a new instance of an `argocd cluster list` command
func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rm",
Use: "rm CLUSTER",
Short: "Remove cluster credentials",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
@@ -217,7 +219,7 @@ func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comm
for _, clusterName := range args {
// TODO(jessesuen): find the right context and remove manager RBAC artifacts
// err := common.UninstallClusterManagerRBAC(clientset)
// err := clusterauth.UninstallClusterManagerRBAC(clientset)
// errors.CheckError(err)
_, err := clusterIf.Delete(context.Background(), &clusterpkg.ClusterQuery{Server: clusterName})
errors.CheckError(err)
@@ -247,3 +249,26 @@ func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
}
return command
}
// NewClusterRotateAuthCommand returns a new instance of an `argocd cluster rotate-auth` command
func NewClusterRotateAuthCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rotate-auth CLUSTER",
Short: fmt.Sprintf("%s cluster rotate-auth CLUSTER", cliName),
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
conn, clusterIf := argocdclient.NewClientOrDie(clientOpts).NewClusterClientOrDie()
defer util.Close(conn)
clusterQuery := clusterpkg.ClusterQuery{
Server: args[0],
}
_, err := clusterIf.RotateAuth(context.Background(), &clusterQuery)
errors.CheckError(err)
fmt.Printf("Cluster '%s' rotated auth\n", clusterQuery.Server)
},
}
return command
}

View File

@@ -113,7 +113,10 @@ func NewApplicationController(
appInformer, appLister := ctrl.newApplicationInformerAndLister()
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, cache.Indexers{})
metricsAddr := fmt.Sprintf("0.0.0.0:%d", metricsPort)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister, func() error {
_, err := kubeClientset.Discovery().ServerVersion()
return err
})
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, ctrl.metricsServer, ctrl.handleAppUpdated)
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settings, stateCache, projInformer, ctrl.metricsServer)
ctrl.appInformer = appInformer

View File

@@ -13,6 +13,7 @@ import (
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
applister "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/healthz"
)
type MetricsServer struct {
@@ -59,12 +60,13 @@ var (
)
// NewMetricsServer returns a new prometheus server which collects application metrics
func NewMetricsServer(addr string, appLister applister.ApplicationLister) *MetricsServer {
func NewMetricsServer(addr string, appLister applister.ApplicationLister, healthCheck func() error) *MetricsServer {
mux := http.NewServeMux()
appRegistry := NewAppRegistry(appLister)
appRegistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
appRegistry.MustRegister(prometheus.NewGoCollector())
mux.Handle(MetricsPath, promhttp.HandlerFor(appRegistry, promhttp.HandlerOpts{}))
healthz.ServeHealthCheck(mux, healthCheck)
syncCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{

View File

@@ -104,6 +104,10 @@ argocd_app_sync_status{name="my-app",namespace="argocd",project="default",sync_s
argocd_app_sync_status{name="my-app",namespace="argocd",project="default",sync_status="Unknown"} 0
`
var noOpHealthCheck = func() error {
return nil
}
func newFakeApp(fakeApp string) *argoappv1.Application {
var app argoappv1.Application
err := yaml.Unmarshal([]byte(fakeApp), &app)
@@ -133,7 +137,7 @@ func newFakeLister(fakeApp ...string) (context.CancelFunc, applister.Application
func testApp(t *testing.T, fakeApp string, expectedResponse string) {
cancel, appLister := newFakeLister(fakeApp)
defer cancel()
metricsServ := NewMetricsServer("localhost:8082", appLister)
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
req, err := http.NewRequest("GET", "/metrics", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
@@ -176,7 +180,7 @@ argocd_app_sync_total{name="my-app",namespace="argocd",phase="Succeeded",project
func TestMetricsSyncCounter(t *testing.T) {
cancel, appLister := newFakeLister()
defer cancel()
metricsServ := NewMetricsServer("localhost:8082", appLister)
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
fakeApp := newFakeApp(fakeApp)
metricsServ.IncSync(fakeApp, &argoappv1.OperationState{Phase: argoappv1.OperationRunning})
@@ -217,7 +221,7 @@ argocd_app_reconcile_count{name="my-app",namespace="argocd",project="important-p
func TestReconcileMetrics(t *testing.T) {
cancel, appLister := newFakeLister()
defer cancel()
metricsServ := NewMetricsServer("localhost:8082", appLister)
metricsServ := NewMetricsServer("localhost:8082", appLister, noOpHealthCheck)
fakeApp := newFakeApp(fakeApp)
metricsServ.IncReconcile(fakeApp, 5*time.Second)

View File

@@ -457,7 +457,8 @@ func (sc *syncContext) liveObj(obj *unstructured.Unstructured) *unstructured.Uns
for _, resource := range sc.compareResult.managedResources {
if resource.Group == obj.GroupVersionKind().Group &&
resource.Kind == obj.GetKind() &&
resource.Namespace == obj.GetNamespace() &&
// cluster scoped objects will not have a namespace, even if the user has defined it
(resource.Namespace == "" || resource.Namespace == obj.GetNamespace()) &&
resource.Name == obj.GetName() {
return resource.Live
}

View File

@@ -2,6 +2,7 @@ package controller
import (
"fmt"
"reflect"
"testing"
log "github.com/sirupsen/logrus"
@@ -526,3 +527,37 @@ func Test_syncContext_isSelectiveSync(t *testing.T) {
})
}
}
func Test_syncContext_liveObj(t *testing.T) {
type fields struct {
compareResult *comparisonResult
}
type args struct {
obj *unstructured.Unstructured
}
obj := test.NewPod()
obj.SetNamespace("my-ns")
found := test.NewPod()
tests := []struct {
name string
fields fields
args args
want *unstructured.Unstructured
}{
{"None", fields{compareResult: &comparisonResult{managedResources: []managedResource{}}}, args{obj: &unstructured.Unstructured{}}, nil},
{"Found", fields{compareResult: &comparisonResult{managedResources: []managedResource{{Group: obj.GroupVersionKind().Group, Kind: obj.GetKind(), Namespace: obj.GetNamespace(), Name: obj.GetName(), Live: found}}}}, args{obj: obj}, found},
{"EmptyNamespace", fields{compareResult: &comparisonResult{managedResources: []managedResource{{Group: obj.GroupVersionKind().Group, Kind: obj.GetKind(), Name: obj.GetName(), Live: found}}}}, args{obj: obj}, found},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sc := &syncContext{
compareResult: tt.fields.compareResult,
}
if got := sc.liveObj(tt.args.obj); !reflect.DeepEqual(got, tt.want) {
t.Errorf("syncContext.liveObj() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -30,7 +30,14 @@ spec:
ports:
- containerPort: 8082
readinessProbe:
tcpSocket:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10

View File

@@ -12,7 +12,7 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.1.0-rc2
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v1.1.0-rc2

View File

@@ -689,7 +689,6 @@ spec:
- revision
- deployedAt
- id
- source
type: object
type: array
observedAt: {}
@@ -1164,7 +1163,6 @@ spec:
type: object
required:
- revision
- source
type: object
required:
- operation

View File

@@ -17,7 +17,7 @@ patchesStrategicMerge:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.1.0-rc2
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v1.1.0-rc2

View File

@@ -690,7 +690,6 @@ spec:
- revision
- deployedAt
- id
- source
type: object
type: array
observedAt: {}
@@ -1165,7 +1164,6 @@ spec:
type: object
required:
- revision
- source
type: object
required:
- operation
@@ -2197,16 +2195,23 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
name: argocd-application-controller
ports:
- containerPort: 8082
readinessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8082
serviceAccountName: argocd-application-controller
---
apiVersion: apps/v1
@@ -2244,7 +2249,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2299,7 +2304,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2361,7 +2366,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2388,7 +2393,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.1.0-rc2
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -690,7 +690,6 @@ spec:
- revision
- deployedAt
- id
- source
type: object
type: array
observedAt: {}
@@ -1165,7 +1164,6 @@ spec:
type: object
required:
- revision
- source
type: object
required:
- operation
@@ -2112,16 +2110,23 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
name: argocd-application-controller
ports:
- containerPort: 8082
readinessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8082
serviceAccountName: argocd-application-controller
---
apiVersion: apps/v1
@@ -2159,7 +2164,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2214,7 +2219,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2276,7 +2281,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2303,7 +2308,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.1.0-rc2
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -690,7 +690,6 @@ spec:
- revision
- deployedAt
- id
- source
type: object
type: array
observedAt: {}
@@ -1165,7 +1164,6 @@ spec:
type: object
required:
- revision
- source
type: object
required:
- operation
@@ -1961,16 +1959,23 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
name: argocd-application-controller
ports:
- containerPort: 8082
readinessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8082
serviceAccountName: argocd-application-controller
---
apiVersion: apps/v1
@@ -2008,7 +2013,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2071,7 +2076,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2110,7 +2115,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2137,7 +2142,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.1.0-rc2
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -690,7 +690,6 @@ spec:
- revision
- deployedAt
- id
- source
type: object
type: array
observedAt: {}
@@ -1165,7 +1164,6 @@ spec:
type: object
required:
- revision
- source
type: object
required:
- operation
@@ -1876,16 +1874,23 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
name: argocd-application-controller
ports:
- containerPort: 8082
readinessProbe:
httpGet:
path: /healthz
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
tcpSocket:
port: 8082
serviceAccountName: argocd-application-controller
---
apiVersion: apps/v1
@@ -1923,7 +1928,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -1986,7 +1991,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2025,7 +2030,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.1.0-rc2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2052,7 +2057,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v1.1.0-rc2
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -45,7 +45,7 @@ func (m *ClusterQuery) Reset() { *m = ClusterQuery{} }
func (m *ClusterQuery) String() string { return proto.CompactTextString(m) }
func (*ClusterQuery) ProtoMessage() {}
func (*ClusterQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_cluster_63d48da351ddefcc, []int{0}
return fileDescriptor_cluster_66eaae81439ad97b, []int{0}
}
func (m *ClusterQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -91,7 +91,7 @@ func (m *ClusterResponse) Reset() { *m = ClusterResponse{} }
func (m *ClusterResponse) String() string { return proto.CompactTextString(m) }
func (*ClusterResponse) ProtoMessage() {}
func (*ClusterResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_cluster_63d48da351ddefcc, []int{1}
return fileDescriptor_cluster_66eaae81439ad97b, []int{1}
}
func (m *ClusterResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -132,7 +132,7 @@ func (m *ClusterCreateRequest) Reset() { *m = ClusterCreateRequest{} }
func (m *ClusterCreateRequest) String() string { return proto.CompactTextString(m) }
func (*ClusterCreateRequest) ProtoMessage() {}
func (*ClusterCreateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_cluster_63d48da351ddefcc, []int{2}
return fileDescriptor_cluster_66eaae81439ad97b, []int{2}
}
func (m *ClusterCreateRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -186,7 +186,7 @@ func (m *ClusterUpdateRequest) Reset() { *m = ClusterUpdateRequest{} }
func (m *ClusterUpdateRequest) String() string { return proto.CompactTextString(m) }
func (*ClusterUpdateRequest) ProtoMessage() {}
func (*ClusterUpdateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_cluster_63d48da351ddefcc, []int{3}
return fileDescriptor_cluster_66eaae81439ad97b, []int{3}
}
func (m *ClusterUpdateRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -250,6 +250,8 @@ type ClusterServiceClient interface {
Update(ctx context.Context, in *ClusterUpdateRequest, opts ...grpc.CallOption) (*v1alpha1.Cluster, error)
// Delete deletes a cluster
Delete(ctx context.Context, in *ClusterQuery, opts ...grpc.CallOption) (*ClusterResponse, error)
// RotateAuth returns a cluster by server address
RotateAuth(ctx context.Context, in *ClusterQuery, opts ...grpc.CallOption) (*ClusterResponse, error)
}
type clusterServiceClient struct {
@@ -305,6 +307,15 @@ func (c *clusterServiceClient) Delete(ctx context.Context, in *ClusterQuery, opt
return out, nil
}
func (c *clusterServiceClient) RotateAuth(ctx context.Context, in *ClusterQuery, opts ...grpc.CallOption) (*ClusterResponse, error) {
out := new(ClusterResponse)
err := c.cc.Invoke(ctx, "/cluster.ClusterService/RotateAuth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for ClusterService service
type ClusterServiceServer interface {
@@ -318,6 +329,8 @@ type ClusterServiceServer interface {
Update(context.Context, *ClusterUpdateRequest) (*v1alpha1.Cluster, error)
// Delete deletes a cluster
Delete(context.Context, *ClusterQuery) (*ClusterResponse, error)
// RotateAuth returns a cluster by server address
RotateAuth(context.Context, *ClusterQuery) (*ClusterResponse, error)
}
func RegisterClusterServiceServer(s *grpc.Server, srv ClusterServiceServer) {
@@ -414,6 +427,24 @@ func _ClusterService_Delete_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler)
}
func _ClusterService_RotateAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ClusterQuery)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ClusterServiceServer).RotateAuth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/cluster.ClusterService/RotateAuth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ClusterServiceServer).RotateAuth(ctx, req.(*ClusterQuery))
}
return interceptor(ctx, in, info, handler)
}
var _ClusterService_serviceDesc = grpc.ServiceDesc{
ServiceName: "cluster.ClusterService",
HandlerType: (*ClusterServiceServer)(nil),
@@ -438,6 +469,10 @@ var _ClusterService_serviceDesc = grpc.ServiceDesc{
MethodName: "Delete",
Handler: _ClusterService_Delete_Handler,
},
{
MethodName: "RotateAuth",
Handler: _ClusterService_RotateAuth_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "server/cluster/cluster.proto",
@@ -1061,39 +1096,41 @@ var (
)
func init() {
proto.RegisterFile("server/cluster/cluster.proto", fileDescriptor_cluster_63d48da351ddefcc)
proto.RegisterFile("server/cluster/cluster.proto", fileDescriptor_cluster_66eaae81439ad97b)
}
var fileDescriptor_cluster_63d48da351ddefcc = []byte{
// 475 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x94, 0x4f, 0x8b, 0x13, 0x31,
0x18, 0xc6, 0xc9, 0xaa, 0xa3, 0x46, 0xf1, 0x4f, 0x58, 0xa5, 0x8e, 0x6b, 0xd9, 0x9d, 0x83, 0x2c,
0xa2, 0x09, 0xad, 0x17, 0xf1, 0x22, 0xec, 0x8a, 0x22, 0x78, 0xb1, 0xe2, 0x45, 0x16, 0x24, 0x3b,
0x7d, 0xc9, 0xc6, 0x8e, 0x93, 0x98, 0x64, 0x06, 0x44, 0x44, 0xd0, 0xab, 0x78, 0xf1, 0x03, 0x78,
0xf5, 0xa3, 0x78, 0x14, 0xfc, 0x02, 0x52, 0xfc, 0x20, 0x32, 0x99, 0xa4, 0xdd, 0xed, 0x50, 0x10,
0x2c, 0x9e, 0x9a, 0xbc, 0x49, 0x9f, 0xf7, 0x97, 0x27, 0xcf, 0x04, 0x6f, 0x58, 0x30, 0x35, 0x18,
0x96, 0x17, 0x95, 0x75, 0xf3, 0x5f, 0xaa, 0x8d, 0x72, 0x8a, 0x9c, 0x0c, 0xd3, 0x74, 0x5d, 0x28,
0xa1, 0x7c, 0x8d, 0x35, 0xa3, 0x76, 0x39, 0xdd, 0x10, 0x4a, 0x89, 0x02, 0x18, 0xd7, 0x92, 0xf1,
0xb2, 0x54, 0x8e, 0x3b, 0xa9, 0x4a, 0x1b, 0x56, 0xb3, 0xc9, 0x1d, 0x4b, 0xa5, 0xf2, 0xab, 0xb9,
0x32, 0xc0, 0xea, 0x01, 0x13, 0x50, 0x82, 0xe1, 0x0e, 0xc6, 0x61, 0xcf, 0x23, 0x21, 0xdd, 0x41,
0xb5, 0x4f, 0x73, 0xf5, 0x8a, 0x71, 0xe3, 0x5b, 0xbc, 0xf4, 0x83, 0x5b, 0xf9, 0x98, 0xe9, 0x89,
0x68, 0xfe, 0x6c, 0x19, 0xd7, 0xba, 0x90, 0xb9, 0x17, 0x67, 0xf5, 0x80, 0x17, 0xfa, 0x80, 0x77,
0xa4, 0xb2, 0xeb, 0xf8, 0xec, 0x6e, 0x4b, 0xfb, 0xa4, 0x02, 0xf3, 0x86, 0x5c, 0xc6, 0x49, 0x7b,
0xb6, 0x1e, 0xda, 0x44, 0xdb, 0xa7, 0x47, 0x61, 0x96, 0x5d, 0xc4, 0xe7, 0xc3, 0xbe, 0x11, 0x58,
0xad, 0x4a, 0x0b, 0xd9, 0x27, 0x84, 0xd7, 0x43, 0x6d, 0xd7, 0x00, 0x77, 0x30, 0x82, 0xd7, 0x15,
0x58, 0x47, 0xf6, 0x70, 0x74, 0xc0, 0x8b, 0x9c, 0x19, 0xee, 0xd0, 0x39, 0x30, 0x8d, 0xc0, 0x7e,
0xf0, 0x22, 0x1f, 0x53, 0x3d, 0x11, 0xb4, 0x01, 0xa6, 0x87, 0x80, 0x69, 0x04, 0xa6, 0xb1, 0x6b,
0x94, 0x6c, 0x08, 0x2b, 0x6d, 0xc1, 0xb8, 0xde, 0xda, 0x26, 0xda, 0x3e, 0x35, 0x0a, 0xb3, 0xcc,
0xcd, 0x68, 0x9e, 0xe9, 0xf1, 0xff, 0xa2, 0x19, 0x7e, 0x3b, 0x81, 0xcf, 0x85, 0xe2, 0x53, 0x30,
0xb5, 0xcc, 0x81, 0xbc, 0xc7, 0xc7, 0x1f, 0x4b, 0xeb, 0xc8, 0x25, 0x1a, 0x63, 0x71, 0xd8, 0xe1,
0xf4, 0xc1, 0xbf, 0xb7, 0x6f, 0xe4, 0xb3, 0xde, 0x87, 0x9f, 0xbf, 0xbf, 0xac, 0x11, 0x72, 0xc1,
0x47, 0xa5, 0x1e, 0xc4, 0x10, 0x5a, 0xf2, 0x19, 0xe1, 0xa4, 0xbd, 0x11, 0x72, 0x6d, 0x91, 0xe1,
0xc8, 0x4d, 0xa5, 0x2b, 0xb0, 0x22, 0xdb, 0xf2, 0x1c, 0x57, 0xb3, 0x0e, 0xc7, 0xdd, 0xd9, 0x95,
0x7d, 0x44, 0xf8, 0xd8, 0x43, 0x58, 0xea, 0xc8, 0x0a, 0x29, 0xc8, 0x95, 0x45, 0x0a, 0xf6, 0xb6,
0x4d, 0xf0, 0x3b, 0xf2, 0x15, 0xe1, 0xa4, 0x8d, 0x46, 0xd7, 0x96, 0x23, 0x91, 0x59, 0x09, 0xd0,
0xd0, 0x03, 0xdd, 0x4c, 0xb7, 0xba, 0x40, 0xb1, 0x77, 0x00, 0x9b, 0xfb, 0xb4, 0x87, 0x93, 0xfb,
0x50, 0x80, 0x83, 0x65, 0x4e, 0xf5, 0x16, 0xcb, 0xb3, 0x8f, 0x31, 0x9c, 0xff, 0xc6, 0xf2, 0xf3,
0xef, 0xdc, 0xfb, 0x3e, 0xed, 0xa3, 0x1f, 0xd3, 0x3e, 0xfa, 0x35, 0xed, 0xa3, 0xe7, 0x83, 0xbf,
0x78, 0x43, 0xf2, 0x42, 0x42, 0xe9, 0xa2, 0xd4, 0x7e, 0xe2, 0x9f, 0x8c, 0xdb, 0x7f, 0x02, 0x00,
0x00, 0xff, 0xff, 0x9f, 0x92, 0x8b, 0xe2, 0xfe, 0x04, 0x00, 0x00,
var fileDescriptor_cluster_66eaae81439ad97b = []byte{
// 502 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x94, 0x4f, 0x8b, 0x13, 0x3f,
0x18, 0xc7, 0xc9, 0xfe, 0x7e, 0x8c, 0x1a, 0xc5, 0x3f, 0x61, 0x95, 0x3a, 0xae, 0x65, 0x37, 0xa0,
0x2e, 0x62, 0x13, 0x5a, 0x2f, 0xe2, 0x45, 0xdc, 0x15, 0x45, 0xf0, 0xe2, 0x88, 0x17, 0x59, 0x90,
0xec, 0xf4, 0x61, 0x3a, 0x76, 0x9c, 0xc4, 0x24, 0x33, 0x20, 0x22, 0x82, 0x5e, 0xc5, 0x8b, 0xe0,
0xd5, 0xb7, 0xe3, 0x51, 0xf0, 0x0d, 0x48, 0xf1, 0x85, 0xc8, 0x64, 0x92, 0x76, 0xb7, 0xa5, 0xa2,
0x58, 0x3c, 0x35, 0x79, 0x92, 0x7e, 0xbf, 0x9f, 0x7c, 0xf3, 0x4c, 0xf0, 0x86, 0x01, 0x5d, 0x83,
0xe6, 0x69, 0x51, 0x19, 0x3b, 0xfb, 0x65, 0x4a, 0x4b, 0x2b, 0xc9, 0x11, 0x3f, 0x8d, 0xd7, 0x33,
0x99, 0x49, 0x57, 0xe3, 0xcd, 0xa8, 0x5d, 0x8e, 0x37, 0x32, 0x29, 0xb3, 0x02, 0xb8, 0x50, 0x39,
0x17, 0x65, 0x29, 0xad, 0xb0, 0xb9, 0x2c, 0x8d, 0x5f, 0xa5, 0xe3, 0x1b, 0x86, 0xe5, 0xd2, 0xad,
0xa6, 0x52, 0x03, 0xaf, 0xfb, 0x3c, 0x83, 0x12, 0xb4, 0xb0, 0x30, 0xf4, 0x7b, 0xee, 0x67, 0xb9,
0x1d, 0x55, 0xfb, 0x2c, 0x95, 0xcf, 0xb9, 0xd0, 0xce, 0xe2, 0x99, 0x1b, 0xf4, 0xd2, 0x21, 0x57,
0xe3, 0xac, 0xf9, 0xb3, 0xe1, 0x42, 0xa9, 0x22, 0x4f, 0x9d, 0x38, 0xaf, 0xfb, 0xa2, 0x50, 0x23,
0xb1, 0x20, 0x45, 0x2f, 0xe3, 0x13, 0xbb, 0x2d, 0xed, 0xc3, 0x0a, 0xf4, 0x4b, 0x72, 0x0e, 0x47,
0xed, 0xd9, 0x3a, 0x68, 0x13, 0x6d, 0x1f, 0x4b, 0xfc, 0x8c, 0x9e, 0xc1, 0xa7, 0xfc, 0xbe, 0x04,
0x8c, 0x92, 0xa5, 0x01, 0xfa, 0x1e, 0xe1, 0x75, 0x5f, 0xdb, 0xd5, 0x20, 0x2c, 0x24, 0xf0, 0xa2,
0x02, 0x63, 0xc9, 0x1e, 0x0e, 0x09, 0x38, 0x91, 0xe3, 0x83, 0x1d, 0x36, 0x03, 0x66, 0x01, 0xd8,
0x0d, 0x9e, 0xa6, 0x43, 0xa6, 0xc6, 0x19, 0x6b, 0x80, 0xd9, 0x01, 0x60, 0x16, 0x80, 0x59, 0x70,
0x0d, 0x92, 0x0d, 0x61, 0xa5, 0x0c, 0x68, 0xdb, 0x59, 0xdb, 0x44, 0xdb, 0x47, 0x13, 0x3f, 0xa3,
0x76, 0x4a, 0xf3, 0x58, 0x0d, 0xff, 0x15, 0xcd, 0xe0, 0x53, 0x84, 0x4f, 0xfa, 0xe2, 0x23, 0xd0,
0x75, 0x9e, 0x02, 0x79, 0x83, 0xff, 0x7f, 0x90, 0x1b, 0x4b, 0xce, 0xb2, 0xd0, 0x16, 0x07, 0x13,
0x8e, 0xef, 0xfe, 0xbd, 0x7d, 0x23, 0x4f, 0x3b, 0x6f, 0xbf, 0xfd, 0xf8, 0xb8, 0x46, 0xc8, 0x69,
0xd7, 0x2a, 0x75, 0x3f, 0x34, 0xa1, 0x21, 0x1f, 0x10, 0x8e, 0xda, 0x1b, 0x21, 0x17, 0xe7, 0x19,
0x0e, 0xdd, 0x54, 0xbc, 0x82, 0x28, 0xe8, 0x96, 0xe3, 0xb8, 0x40, 0x17, 0x38, 0x6e, 0x4e, 0xaf,
0xec, 0x1d, 0xc2, 0xff, 0xdd, 0x83, 0xa5, 0x89, 0xac, 0x90, 0x82, 0x9c, 0x9f, 0xa7, 0xe0, 0xaf,
0xda, 0x0e, 0x7e, 0x4d, 0x3e, 0x23, 0x1c, 0xb5, 0xad, 0xb1, 0x18, 0xcb, 0xa1, 0x96, 0x59, 0x09,
0xd0, 0xc0, 0x01, 0x5d, 0x8b, 0xb7, 0x16, 0x81, 0x82, 0xb7, 0x07, 0x9b, 0xe5, 0xb4, 0x87, 0xa3,
0x3b, 0x50, 0x80, 0x85, 0x65, 0x49, 0x75, 0xe6, 0xcb, 0xd3, 0x8f, 0xd1, 0x9f, 0xff, 0xea, 0x2f,
0xce, 0x5f, 0x60, 0x9c, 0x34, 0x8f, 0x0d, 0xdc, 0xae, 0xec, 0xe8, 0xcf, 0x1d, 0x7a, 0xce, 0xe1,
0x0a, 0xbd, 0xb4, 0xd4, 0x81, 0x6b, 0x27, 0xdf, 0x13, 0x95, 0x1d, 0xed, 0xdc, 0xfa, 0x32, 0xe9,
0xa2, 0xaf, 0x93, 0x2e, 0xfa, 0x3e, 0xe9, 0xa2, 0x27, 0xfd, 0xdf, 0x78, 0xb1, 0xd2, 0x22, 0x87,
0xd2, 0x06, 0xd9, 0xfd, 0xc8, 0x3d, 0x50, 0xd7, 0x7f, 0x06, 0x00, 0x00, 0xff, 0xff, 0x4e, 0xde,
0x52, 0x19, 0x6c, 0x05, 0x00, 0x00,
}

View File

@@ -151,6 +151,33 @@ func request_ClusterService_Delete_0(ctx context.Context, marshaler runtime.Mars
}
func request_ClusterService_RotateAuth_0(ctx context.Context, marshaler runtime.Marshaler, client ClusterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClusterQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["server"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "server")
}
protoReq.Server, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "server", err)
}
msg, err := client.RotateAuth(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
// RegisterClusterServiceHandlerFromEndpoint is same as RegisterClusterServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterClusterServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
@@ -334,6 +361,35 @@ func RegisterClusterServiceHandlerClient(ctx context.Context, mux *runtime.Serve
})
mux.Handle("POST", pattern_ClusterService_RotateAuth_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
if cn, ok := w.(http.CloseNotifier); ok {
go func(done <-chan struct{}, closed <-chan bool) {
select {
case <-done:
case <-closed:
cancel()
}
}(ctx.Done(), cn.CloseNotify())
}
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ClusterService_RotateAuth_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_ClusterService_RotateAuth_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -347,6 +403,8 @@ var (
pattern_ClusterService_Update_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "clusters", "cluster.server"}, ""))
pattern_ClusterService_Delete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "clusters", "server"}, ""))
pattern_ClusterService_RotateAuth_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "clusters", "server", "rotate-auth"}, ""))
)
var (
@@ -359,4 +417,6 @@ var (
forward_ClusterService_Update_0 = runtime.ForwardResponseMessage
forward_ClusterService_Delete_0 = runtime.ForwardResponseMessage
forward_ClusterService_RotateAuth_0 = runtime.ForwardResponseMessage
)

View File

@@ -326,7 +326,7 @@ func schema_pkg_apis_application_v1alpha1_Application(ref common.ReferenceCallba
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
@@ -2442,7 +2442,7 @@ func schema_pkg_apis_application_v1alpha1_RevisionHistory(ref common.ReferenceCa
},
},
},
Required: []string{"revision", "deployedAt", "id", "source"},
Required: []string{"revision", "deployedAt", "id"},
},
},
Dependencies: []string{
@@ -2577,7 +2577,7 @@ func schema_pkg_apis_application_v1alpha1_SyncOperationResult(ref common.Referen
},
},
},
Required: []string{"revision", "source"},
Required: []string{"revision"},
},
},
Dependencies: []string{

View File

@@ -30,7 +30,7 @@ type Application struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"`
Spec ApplicationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
Status ApplicationStatus `json:"status" protobuf:"bytes,3,opt,name=status"`
Status ApplicationStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
Operation *Operation `json:"operation,omitempty" protobuf:"bytes,4,opt,name=operation"`
}
@@ -394,7 +394,7 @@ type SyncOperationResult struct {
// Revision holds the git commit SHA of the sync
Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
// Source records the application source information of the sync, used for comparing auto-sync
Source ApplicationSource `json:"source" protobuf:"bytes,3,opt,name=source"`
Source ApplicationSource `json:"source,omitempty" protobuf:"bytes,3,opt,name=source"`
}
type ResultCode string
@@ -476,7 +476,7 @@ type RevisionHistory struct {
Revision string `json:"revision" protobuf:"bytes,2,opt,name=revision"`
DeployedAt metav1.Time `json:"deployedAt" protobuf:"bytes,4,opt,name=deployedAt"`
ID int64 `json:"id" protobuf:"bytes,5,opt,name=id"`
Source ApplicationSource `json:"source" protobuf:"bytes,6,opt,name=source"`
Source ApplicationSource `json:"source,omitempty" protobuf:"bytes,6,opt,name=source"`
}
// ApplicationWatchEvent contains information about application change.

View File

@@ -99,7 +99,7 @@ if obj.status ~= nil then
end
if obj.spec.strategy.canary ~= nil then
currentRSIsStable = obj.status.canary.stableRS == obj.status.currentPodHash
if obj.spec.strategy.canary.steps ~= nil then
if obj.spec.strategy.canary.steps ~= nil and table.getn(obj.spec.strategy.canary.steps) > 0 then
stepCount = table.getn(obj.spec.strategy.canary.steps)
if obj.status.currentStepIndex ~= nil then
currentStepIndex = obj.status.currentStepIndex

View File

@@ -56,4 +56,8 @@ tests:
- healthStatus:
status: Healthy
message: The rollout has completed canary deployment
inputPath: testdata/canary/healthy_noSteps.yaml
inputPath: testdata/canary/healthy_noSteps.yaml
- healthStatus:
status: Healthy
message: The rollout has completed canary deployment
inputPath: testdata/canary/healthy_emptyStepsList.yaml

View File

@@ -0,0 +1,67 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"guestbook-canary","ksonnet.io/component":"guestbook-ui"},"name":"guestbook-canary","namespace":"default"},"spec":{"minReadySeconds":10,"replicas":5,"selector":{"matchLabels":{"app":"guestbook-canary"}},"strategy":{"canary":{"maxSurge":1,"maxUnavailable":0,"steps":[{"setWeight":20},{"pause":{"duration":30}},{"setWeight":40},{"pause":{}}]}},"template":{"metadata":{"labels":{"app":"guestbook-canary"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook-canary","ports":[{"containerPort":80}]}]}}}}
rollout.argoproj.io/revision: '2'
clusterName: ''
creationTimestamp: '2019-05-01T21:55:30Z'
generation: 1
labels:
app.kubernetes.io/instance: guestbook-canary
ksonnet.io/component: guestbook-ui
name: guestbook-canary
namespace: default
resourceVersion: '956205'
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/guestbook-canary
uid: d6105ccd-6c5b-11e9-b8d7-025000000001
spec:
minReadySeconds: 10
replicas: 5
selector:
matchLabels:
app: guestbook-canary
strategy:
canary:
maxSurge: 1
maxUnavailable: 0
steps: []
template:
metadata:
creationTimestamp: null
labels:
app: guestbook-canary
spec:
containers:
- image: 'gcr.io/heptio-images/ks-guestbook-demo:0.2'
name: guestbook-canary
ports:
- containerPort: 80
resources: {}
status:
HPAReplicas: 5
availableReplicas: 5
blueGreen: {}
canary:
stableRS: 567dd56d89
conditions:
- lastTransitionTime: '2019-05-01T22:00:16Z'
lastUpdateTime: '2019-05-01T22:00:16Z'
message: Rollout has minimum availability
reason: AvailableReason
status: 'True'
type: Available
- lastTransitionTime: '2019-05-01T21:55:30Z'
lastUpdateTime: '2019-05-01T22:00:16Z'
message: ReplicaSet "guestbook-canary-567dd56d89" has successfully progressed.
reason: NewReplicaSetAvailable
status: 'True'
type: Progressing
currentPodHash: 567dd56d89
currentStepHash: 6c9545789c
observedGeneration: 6886f85bff
readyReplicas: 5
replicas: 5
selector: app=guestbook-canary
updatedReplicas: 5

View File

@@ -17,6 +17,7 @@ import (
"github.com/argoproj/argo-cd/server/rbacpolicy"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/clusterauth"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/rbac"
@@ -174,6 +175,55 @@ func (s *Server) Delete(ctx context.Context, q *cluster.ClusterQuery) (*cluster.
return &cluster.ClusterResponse{}, err
}
// RotateAuth rotates the bearer token used for a cluster
func (s *Server) RotateAuth(ctx context.Context, q *cluster.ClusterQuery) (*cluster.ClusterResponse, error) {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, q.Server); err != nil {
return nil, err
}
logCtx := log.WithField("cluster", q.Server)
logCtx.Info("Rotating auth")
clust, err := s.db.GetCluster(ctx, q.Server)
if err != nil {
return nil, err
}
restCfg := clust.RESTConfig()
if restCfg.BearerToken == "" {
return nil, status.Errorf(codes.InvalidArgument, "Cluster '%s' does not use bearer token authentication", q.Server)
}
claims, err := clusterauth.ParseServiceAccountToken(restCfg.BearerToken)
if err != nil {
return nil, err
}
kubeclientset, err := kubernetes.NewForConfig(restCfg)
if err != nil {
return nil, err
}
newSecret, err := clusterauth.GenerateNewClusterManagerSecret(kubeclientset, claims)
if err != nil {
return nil, err
}
// we are using token auth, make sure we don't store client-cert information
clust.Config.KeyData = nil
clust.Config.CertData = nil
clust.Config.BearerToken = string(newSecret.Data["token"])
// Test the token we just created before persisting it
err = kube.TestConfig(clust.RESTConfig())
if err != nil {
return nil, err
}
_, err = s.db.UpdateCluster(ctx, clust)
if err != nil {
return nil, err
}
err = clusterauth.RotateServiceAccountSecrets(kubeclientset, claims, newSecret)
if err != nil {
return nil, err
}
logCtx.Infof("Rotated auth (old: %s, new: %s)", claims.SecretName, newSecret.Name)
return &cluster.ClusterResponse{}, nil
}
func redact(clust *appv1.Cluster) *appv1.Cluster {
if clust == nil {
return nil

View File

@@ -62,4 +62,9 @@ service ClusterService {
option (google.api.http).delete = "/api/v1/clusters/{server}";
}
// RotateAuth returns a cluster by server address
rpc RotateAuth(ClusterQuery) returns (ClusterResponse) {
option (google.api.http).post = "/api/v1/clusters/{server}/rotate-auth";
}
}

View File

@@ -0,0 +1,21 @@
package e2e
import (
"testing"
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
. "github.com/argoproj/argo-cd/test/e2e/fixture/app"
)
// ensure that cluster scoped objects, like a cluster role, as a hok, can be successfully deployed
func TestClusterRoleBinding(t *testing.T) {
Given(t).
Path("cluster-role").
When().
Create().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(HealthIs(HealthStatusHealthy)).
Expect(SyncStatusIs(SyncStatusCodeSynced))
}

View File

@@ -0,0 +1,41 @@
package e2e
import (
"os"
"testing"
. "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
. "github.com/argoproj/argo-cd/test/e2e/fixture/app"
)
// TestSyncOptionsValidateFalse verifies we can disable validation during kubectl apply, using the
// 'argocd.argoproj.io/sync-options: Validate=false' sync option
func TestSyncOptionsValidateFalse(t *testing.T) {
Given(t).
Path("sync-options-validate-false").
When().
Create().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded))
// NOTE: it is a bug that we do not detect this as OutOfSync. This is because we
// are dropping fields as part of remarshalling. See: https://github.com/argoproj/argo-cd/issues/1787
// Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
}
// TestSyncOptionsValidateTrue verifies when 'argocd.argoproj.io/sync-options: Validate=false' is
// not present, then validation is performed and we fail during the apply
func TestSyncOptionsValidateTrue(t *testing.T) {
// k3s does not validate at all, so this test does not work
if os.Getenv("ARGOCD_E2E_K3S") == "true" {
t.SkipNow()
}
Given(t).
Path("sync-options-validate-false").
When().
Create().
PatchFile("invalid-cm.yaml", `[{"op": "remove", "path": "/metadata/annotations"}]`).
Sync().
Then().
Expect(OperationPhaseIs(OperationFailed))
}

View File

@@ -0,0 +1,15 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
namespace: cert-manager
name: my-cluster-role-binding
annotations:
argocd.argoproj.io/hook: PreSync
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: default
namespace: default

12
test/e2e/testdata/cluster-role/pod.yaml vendored Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: main
image: alpine:latest
imagePullPolicy: IfNotPresent
command:
- "true"
restartPolicy: Never

View File

@@ -0,0 +1,8 @@
# This configmap fails when running `kubectl apply`, but succeeds when running `kubectl apply --validate=false`
apiVersion: v1
kind: ConfigMap
metadata:
name: invalid-cm
annotations:
argocd.argoproj.io/sync-options: Validate=false
invalidKey: this-fails

View File

@@ -246,7 +246,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{ na
services.repos.appDetails(src.repoURL, src.path, src.targetRevision, src.details)
.catch(() => ({ type: 'Directory' as appModels.AppSourceType, path: application.spec.source.path }))}>
{(details: appModels.RepoAppDetails) => <ApplicationParameters
save={(app) => services.applications.updateSpec(app.metadata.name, app.spec)} application={application} details={details} />}
save={(app) => this.updateApp(app)} application={application} details={details} />}
</DataLoader>
),
}, {
@@ -301,11 +301,11 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{ na
iconClassName: 'fa fa-sync',
title: <span className='show-for-medium'>Sync</span>,
action: () => this.showDeploy('all'),
}, {
}, ...(application.status.operationState && [{
iconClassName: 'fa fa-info-circle',
title: <span className='show-for-medium'>Sync Status</span>,
action: () => this.setOperationStatusVisible(true),
}, {
action: () => this.setOperationStatusVisible(true),
}] || []), {
iconClassName: 'fa fa-history',
title: <span className='show-for-medium'>History and rollback</span>,
action: () => this.setRollbackPanelVisible(0),
@@ -370,15 +370,8 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{ na
}
private async updateApp(app: appModels.Application) {
try {
await services.applications.updateSpec(app.metadata.name, app.spec);
this.refreshRequested.next({});
} catch (e) {
this.appContext.apis.notifications.show({
content: <ErrorNotification title='Unable to update application' e={e}/>,
type: NotificationType.Error,
});
}
await services.applications.updateSpec(app.metadata.name, app.spec);
this.refreshRequested.next({});
}
private groupAppNodesByKey(application: appModels.Application, tree: appModels.ApplicationTree) {

View File

@@ -126,7 +126,7 @@ export const ApplicationOperationState: React.StatelessComponent<Props> = ({appl
</div>
<div className='columns small-1'>
<utils.ResourceResultIcon
resource={resource}/> {resource.status || resource.hookPhase}
resource={resource}/> {resource.hookType ? resource.hookPhase : resource.status}
</div>
<div className='columns small-1'>
{resource.hookType}

View File

@@ -62,6 +62,7 @@ export const ApplicationSummary = (props: {
<i className='fa fa-external-link'/> {app.spec.source.repoURL}
</a>
),
edit: (formApi: FormApi) => <FormField formApi={formApi} field='spec.source.repoURL' component={Text}/>,
},
{
title: 'TARGET REVISION',

View File

@@ -171,7 +171,7 @@ export const ResourceResultIcon = ({resource}: { resource: appModels.ResourceRes
let color = COLORS.sync_result.unknown;
let icon = 'fa-question-circle';
if (resource.status) {
if (!resource.hookType && resource.status) {
switch (resource.status) {
case appModels.ResultCodes.Synced:
color = COLORS.sync_result.synced;
@@ -195,7 +195,7 @@ export const ResourceResultIcon = ({resource}: { resource: appModels.ResourceRes
}
return <i title={title} className={'fa ' + icon} style={{ color }} />;
}
if (resource.hookPhase) {
if (resource.hookType && resource.hookPhase) {
let className = '';
switch (resource.hookPhase) {
case appModels.OperationPhases.Running:

View File

@@ -1,3 +1,4 @@
import * as deepMerge from 'deepmerge';
import { Observable } from 'rxjs';
import * as models from '../models';
@@ -167,16 +168,19 @@ export class ApplicationsService {
}
private parseAppFields(data: any): models.Application {
const app = data as models.Application;
app.kind = app.kind || 'Application';
if (app.spec) {
app.spec.project = app.spec.project || 'default';
delete app.spec.source.componentParameterOverrides;
}
if (app.status) {
app.status.resources = app.status.resources || [];
app.status.summary = app.status.summary || {};
}
return app;
data = deepMerge({
apiVersion: 'argoproj.io/v1alpha1',
kind: 'Application',
spec: {
project: 'default',
},
status: {
resources: [],
summary: {},
},
}, data);
delete data.spec.source.componentParameterOverrides;
return data as models.Application;
}
}

View File

@@ -1,11 +1,13 @@
package common
package clusterauth
import (
"fmt"
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
apiv1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -39,7 +41,7 @@ func CreateServiceAccount(
serviceAccountName string,
namespace string,
) error {
serviceAccount := apiv1.ServiceAccount{
serviceAccount := corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ServiceAccount",
@@ -153,7 +155,7 @@ func InstallClusterManagerRBAC(clientset kubernetes.Interface, ns string) (strin
return "", err
}
var serviceAccount *apiv1.ServiceAccount
var serviceAccount *corev1.ServiceAccount
var secretName string
err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) {
serviceAccount, err = clientset.CoreV1().ServiceAccounts(ns).Get(ArgoCDManagerServiceAccount, metav1.GetOptions{})
@@ -215,3 +217,106 @@ func UninstallRBAC(clientset kubernetes.Interface, namespace, bindingName, roleN
}
return nil
}
type ServiceAccountClaims struct {
Sub string `json:"sub"`
Iss string `json:"iss"`
Namespace string `json:"kubernetes.io/serviceaccount/namespace"`
SecretName string `json:"kubernetes.io/serviceaccount/secret.name"`
ServiceAccountName string `json:"kubernetes.io/serviceaccount/service-account.name"`
ServiceAccountUID string `json:"kubernetes.io/serviceaccount/service-account.uid"`
}
// Valid satisfies the jwt.Claims interface to enable JWT parsing
func (sac *ServiceAccountClaims) Valid() error {
return nil
}
// ParseServiceAccountToken parses a Kubernetes service account token
func ParseServiceAccountToken(token string) (*ServiceAccountClaims, error) {
parser := &jwt.Parser{
SkipClaimsValidation: true,
}
var claims ServiceAccountClaims
_, _, err := parser.ParseUnverified(token, &claims)
if err != nil {
return nil, fmt.Errorf("Failed to parse service account token: %s", err)
}
return &claims, nil
}
// GenerateNewClusterManagerSecret creates a new secret derived with same metadata as existing one
// and waits until the secret is populated with a bearer token
func GenerateNewClusterManagerSecret(clientset kubernetes.Interface, claims *ServiceAccountClaims) (*corev1.Secret, error) {
secretsClient := clientset.CoreV1().Secrets(claims.Namespace)
existingSecret, err := secretsClient.Get(claims.SecretName, metav1.GetOptions{})
if err != nil {
return nil, err
}
var newSecret corev1.Secret
secretNameSplit := strings.Split(claims.SecretName, "-")
if len(secretNameSplit) > 0 {
secretNameSplit = secretNameSplit[:len(secretNameSplit)-1]
}
newSecret.Type = corev1.SecretTypeServiceAccountToken
newSecret.GenerateName = strings.Join(secretNameSplit, "-") + "-"
newSecret.Annotations = existingSecret.Annotations
// We will create an empty secret and let kubernetes populate the data
newSecret.Data = nil
created, err := secretsClient.Create(&newSecret)
if err != nil {
return nil, err
}
err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) {
created, err = secretsClient.Get(created.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
if len(created.Data) == 0 {
return false, nil
}
return true, nil
})
if err != nil {
return nil, fmt.Errorf("Timed out waiting for secret to generate new token")
}
return created, nil
}
// RotateServiceAccountSecrets rotates the entries in the service accounts secrets list
func RotateServiceAccountSecrets(clientset kubernetes.Interface, claims *ServiceAccountClaims, newSecret *corev1.Secret) error {
// 1. update service account secrets list with new secret name while also removing the old name
saClient := clientset.CoreV1().ServiceAccounts(claims.Namespace)
sa, err := saClient.Get(claims.ServiceAccountName, metav1.GetOptions{})
if err != nil {
return err
}
var newSecretsList []corev1.ObjectReference
alreadyPresent := false
for _, objRef := range sa.Secrets {
if objRef.Name == claims.SecretName {
continue
}
if objRef.Name == newSecret.Name {
alreadyPresent = true
}
newSecretsList = append(newSecretsList, objRef)
}
if !alreadyPresent {
sa.Secrets = append(newSecretsList, corev1.ObjectReference{Name: newSecret.Name})
}
_, err = saClient.Update(sa)
if err != nil {
return err
}
// 2. delete existing secret object
secretsClient := clientset.CoreV1().Secrets(claims.Namespace)
err = secretsClient.Delete(claims.SecretName, &metav1.DeleteOptions{})
if !apierr.IsNotFound(err) {
return err
}
return nil
}

View File

@@ -0,0 +1,102 @@
package clusterauth
import (
"io/ioutil"
"testing"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing"
"github.com/argoproj/argo-cd/errors"
)
const (
testToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhcmdvY2QtbWFuYWdlci10b2tlbi10ajc5ciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhcmdvY2QtbWFuYWdlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjkxZGQzN2NmLThkOTItMTFlOS1hMDkxLWQ2NWYyYWU3ZmE4ZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphcmdvY2QtbWFuYWdlciJ9.ytZjt2pDV8-A7DBMR06zQ3wt9cuVEfq262TQw7sdra-KRpDpMPnziMhc8bkwvgW-LGhTWUh5iu1y-1QhEx6mtbCt7vQArlBRxfvM5ys6ClFkplzq5c2TtZ7EzGSD0Up7tdxuG9dvR6TGXYdfFcG779yCdZo2H48sz5OSJfdEriduMEY1iL5suZd3ebOoVi1fGflmqFEkZX6SvxkoArl5mtNP6TvZ1eTcn64xh4ws152hxio42E-eSnl_CET4tpB5vgP5BVlSKW2xB7w2GJxqdETA5LJRI_OilY77dTOp8cMr_Ck3EOeda3zHfh4Okflg8rZFEeAuJYahQNeAILLkcA"
)
var (
testClaims = ServiceAccountClaims{
Sub: "system:serviceaccount:kube-system:argocd-manager",
Iss: "kubernetes/serviceaccount",
Namespace: "kube-system",
SecretName: "argocd-manager-token-tj79r",
ServiceAccountName: "argocd-manager",
ServiceAccountUID: "91dd37cf-8d92-11e9-a091-d65f2ae7fa8d",
}
)
func newServiceAccount() *corev1.ServiceAccount {
saBytes, err := ioutil.ReadFile("./testdata/argocd-manager-sa.yaml")
errors.CheckError(err)
var sa corev1.ServiceAccount
err = yaml.Unmarshal(saBytes, &sa)
errors.CheckError(err)
return &sa
}
func newServiceAccountSecret() *corev1.Secret {
secretBytes, err := ioutil.ReadFile("./testdata/argocd-manager-sa-token.yaml")
errors.CheckError(err)
var secret corev1.Secret
err = yaml.Unmarshal(secretBytes, &secret)
errors.CheckError(err)
return &secret
}
func TestParseServiceAccountToken(t *testing.T) {
claims, err := ParseServiceAccountToken(testToken)
assert.NoError(t, err)
assert.Equal(t, testClaims, *claims)
}
func TestGenerateNewClusterManagerSecret(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(newServiceAccountSecret())
kubeclientset.ReactionChain = nil
generatedSecret := newServiceAccountSecret()
generatedSecret.Name = "argocd-manager-token-abc123"
generatedSecret.Data = map[string][]byte{
"token": []byte("fake-token"),
}
kubeclientset.AddReactor("*", "secrets", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
return true, generatedSecret, nil
})
created, err := GenerateNewClusterManagerSecret(kubeclientset, &testClaims)
assert.NoError(t, err)
assert.Equal(t, "argocd-manager-token-abc123", created.Name)
assert.Equal(t, "fake-token", string(created.Data["token"]))
}
func TestRotateServiceAccountSecrets(t *testing.T) {
generatedSecret := newServiceAccountSecret()
generatedSecret.Name = "argocd-manager-token-abc123"
generatedSecret.Data = map[string][]byte{
"token": []byte("fake-token"),
}
kubeclientset := fake.NewSimpleClientset(newServiceAccount(), newServiceAccountSecret(), generatedSecret)
err := RotateServiceAccountSecrets(kubeclientset, &testClaims, generatedSecret)
assert.NoError(t, err)
// Verify service account references new secret and old secret is deleted
saClient := kubeclientset.CoreV1().ServiceAccounts(testClaims.Namespace)
sa, err := saClient.Get(testClaims.ServiceAccountName, metav1.GetOptions{})
assert.NoError(t, err)
assert.Equal(t, sa.Secrets, []corev1.ObjectReference{
{
Name: "argocd-manager-token-abc123",
},
})
secretsClient := kubeclientset.CoreV1().Secrets(testClaims.Namespace)
_, err = secretsClient.Get(testClaims.SecretName, metav1.GetOptions{})
assert.True(t, apierr.IsNotFound(err))
}

View File

@@ -0,0 +1,18 @@
apiVersion: v1
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRFNE1USXdOakF4TXpnME1Gb1hEVEk0TVRJd05EQXhNemcwTUZvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS2RICkxWSGkwSnh4M1dYVkpueVdJck15djJZUThPWll5YzJpSHBSSVZ4eHlGdENnTVJqVEo1T3IxcTVoUG9XeGhrb1YKeFduUThWWFBGdlpNNUtTcS9Ocis5UGJ2WHlrdFdBaDZaYkVrM2s3S29taXorQk9CSjhMdkh6OHNicDRMQ2VnZwpHLzZ4aGRNWlNUL1VhNlFYbjhTQzBoRFBTSE4vdVpDb1dxWHlqdE5sdnJCeU81di9LZ3dXWjkvcGFnbmtmck1sCk5Qemh5Q2taK0pHSTR5THBtc3VBMnBYMTQrRXdhY2N1OGZmWUhOYitkMnJnZWltSTFmNytPaGRHRUtlTG5lamEKQm90NTZodnpYRWNoTjRJNS9nOU1CbXZOenhabndPbmVrUllOVDQvTHlwaUJEZU5UR1JlZWhybHlzaUVBcldJQgpPb3U0ZFBYbE5RblVmYTBPVVprQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFBTldRcUE1Q2UrR1N4WVVmNTg4bm91ZzNhZVJZZnBLZXIvSnlvazM4TzFKeFlLK2IydApxWGtIdFU0VmpRWFdGNEp6RG9sMlo1bDRSYzRVUWl5QlVUQk1ieS8vY2NGUnVYcER5R3ROQTNnU1hURG9YMjkzCk5SUUlPZndDTlFDUEJjbEpCN3d5YzRqZlZLWWxheXpkOGRuN0V6LzhNNmJXYUlIRWwxcEd0L21XZXZMZXoxUjQKdEhzbXA2RlY5d1lIckFaQyttaFMzOUVFc1lBRjBBdlVtUkFseU5GN1J4ZVdzRG14ZVVDUG9iQnd2Z0ppeGdJWQpqakZiWEk1ang1cEVlSnZnTVcvQmFMRHNpQlVWVnMvZnYzRGdjZkwzMm0zR1hiUHVzRHN0OGs1ZmYvWjV1UkdVCkJaVGtFeUxuUG9vM1pVRDhXZmI5T2x1MXhNSklnYlY5d3NuYwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
namespace: a3ViZS1zeXN0ZW0=
token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklpSjkuZXlKcGMzTWlPaUpyZFdKbGNtNWxkR1Z6TDNObGNuWnBZMlZoWTJOdmRXNTBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5dVlXMWxjM0JoWTJVaU9pSnJkV0psTFhONWMzUmxiU0lzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVmpjbVYwTG01aGJXVWlPaUpoY21kdlkyUXRiV0Z1WVdkbGNpMTBiMnRsYmkxMGFqYzVjaUlzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVnlkbWxqWlMxaFkyTnZkVzUwTG01aGJXVWlPaUpoY21kdlkyUXRiV0Z1WVdkbGNpSXNJbXQxWW1WeWJtVjBaWE11YVc4dmMyVnlkbWxqWldGalkyOTFiblF2YzJWeWRtbGpaUzFoWTJOdmRXNTBMblZwWkNJNklqa3haR1F6TjJObUxUaGtPVEl0TVRGbE9TMWhNRGt4TFdRMk5XWXlZV1UzWm1FNFpDSXNJbk4xWWlJNkluTjVjM1JsYlRwelpYSjJhV05sWVdOamIzVnVkRHByZFdKbExYTjVjM1JsYlRwaGNtZHZZMlF0YldGdVlXZGxjaUo5Lnl0Wmp0MnBEVjgtQTdEQk1SMDZ6UTN3dDljdVZFZnEyNjJUUXc3c2RyYS1LUnBEcE1QbnppTWhjOGJrd3ZnVy1MR2hUV1VoNWl1MXktMVFoRXg2bXRiQ3Q3dlFBcmxCUnhmdk01eXM2Q2xGa3BsenE1YzJUdFo3RXpHU0QwVXA3dGR4dUc5ZHZSNlRHWFlkZkZjRzc3OXlDZFpvMkg0OHN6NU9TSmZkRXJpZHVNRVkxaUw1c3VaZDNlYk9vVmkxZkdmbG1xRkVrWlg2U3Z4a29Bcmw1bXROUDZUdloxZVRjbjY0eGg0d3MxNTJoeGlvNDJFLWVTbmxfQ0VUNHRwQjV2Z1A1QlZsU0tXMnhCN3cyR0p4cWRFVEE1TEpSSV9PaWxZNzdkVE9wOGNNcl9DazNFT2VkYTN6SGZoNE9rZmxnOHJaRkVlQXVKWWFoUU5lQUlMTGtjQQ==
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: argocd-manager
kubernetes.io/service-account.uid: 91dd37cf-8d92-11e9-a091-d65f2ae7fa8d
creationTimestamp: "2019-06-13T04:30:24Z"
generateName: argocd-manager-token-
name: argocd-manager-token-tj79r
namespace: kube-system
resourceVersion: "133010"
selfLink: /api/v1/namespaces/kube-system/secrets/argocd-manager-token-tj79r
uid: f657d67e-8d93-11e9-a091-d65f2ae7fa8d
type: kubernetes.io/service-account-token

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2019-06-13T04:20:26Z"
name: argocd-manager
namespace: kube-system
resourceVersion: "133015"
selfLink: /api/v1/namespaces/kube-system/serviceaccounts/argocd-manager
uid: 91dd37cf-8d92-11e9-a091-d65f2ae7fa8d
secrets:
- name: argocd-manager-token-tj79r

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"reflect"
log "github.com/sirupsen/logrus"
"github.com/yudai/gojsondiff"
@@ -36,11 +37,11 @@ type Normalizer interface {
// "kubectl.kubernetes.io/last-applied-configuration", then perform a three way diff.
func Diff(config, live *unstructured.Unstructured, normalizer Normalizer) *DiffResult {
if config != nil {
config = stripTypeInformation(config)
config = remarshal(config)
Normalize(config, normalizer)
}
if live != nil {
live = stripTypeInformation(live)
live = remarshal(live)
Normalize(live, normalizer)
}
orig := GetLastAppliedConfigAnnotation(live)
@@ -439,3 +440,39 @@ func toString(val interface{}) string {
}
return fmt.Sprintf("%s", val)
}
// remarshal checks resource kind and version and re-marshal using corresponding struct custom marshaller.
// This ensures that expected resource state is formatter same as actual resource state in kubernetes
// and allows to find differences between actual and target states more accurately.
// Remarshalling also strips any type information (e.g. float64 vs. int) from the unstructured
// object. This is important for diffing since it will cause godiff to report a false difference.
func remarshal(obj *unstructured.Unstructured) *unstructured.Unstructured {
obj = stripTypeInformation(obj)
data, err := json.Marshal(obj)
if err != nil {
panic(err)
}
gvk := obj.GroupVersionKind()
item, err := scheme.Scheme.New(obj.GroupVersionKind())
if err != nil {
// this is common. the scheme is not registered
log.Debugf("Could not create new object of type %s: %v", gvk, err)
return obj
}
// This will drop any omitempty fields, perform resource conversion etc...
unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
err = json.Unmarshal(data, &unmarshalledObj)
if err != nil {
// User may have specified an invalid spec in git. Return original object
log.Warnf("Could not unmarshal to object of type %s: %v", gvk, err)
return obj
}
unstrBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(unmarshalledObj)
if err != nil {
log.Warnf("Could not unmarshal to object of type %s: %v", gvk, err)
return obj
}
// remove all default values specified by custom formatter (e.g. creationTimestamp)
unstrBody = jsonutil.RemoveMapFields(obj.Object, unstrBody)
return &unstructured.Unstructured{Object: unstrBody}
}

View File

@@ -587,3 +587,48 @@ func TestHideSecretDataLastAppliedConfig(t *testing.T) {
assert.Equal(t, map[string]interface{}{"key1": replacement3}, secretData(lastAppliedSecret))
}
func TestRemarshal(t *testing.T) {
manifest := []byte(`
apiVersion: v1
kind: ServiceAccount
imagePullSecrets: []
metadata:
name: my-sa
`)
var un unstructured.Unstructured
err := yaml.Unmarshal(manifest, &un)
assert.NoError(t, err)
newUn := remarshal(&un)
_, ok := newUn.Object["imagePullSecrets"]
assert.False(t, ok)
metadata := newUn.Object["metadata"].(map[string]interface{})
_, ok = metadata["creationTimestamp"]
assert.False(t, ok)
}
func TestRemarshalResources(t *testing.T) {
manifest := []byte(`
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: nginx:1.7.9
name: nginx
resources:
requests:
cpu: 0.2
`)
un := unstructured.Unstructured{}
err := yaml.Unmarshal(manifest, &un)
assert.NoError(t, err)
requestsBefore := un.Object["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{})
log.Println(requestsBefore)
newUn := remarshal(&un)
requestsAfter := newUn.Object["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{})
log.Println(requestsAfter)
assert.Equal(t, float64(0.2), requestsBefore["cpu"])
assert.Equal(t, "200m", requestsAfter["cpu"])
}

View File

@@ -3,9 +3,7 @@ package kube
import (
"context"
"encoding/json"
"fmt"
"reflect"
"regexp"
"strings"
"time"
@@ -27,10 +25,8 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"github.com/argoproj/argo-cd/common"
jsonutil "github.com/argoproj/argo-cd/util/json"
)
const (
@@ -369,44 +365,11 @@ func SplitYAML(out string) ([]*unstructured.Unstructured, error) {
}
continue
}
remObj, err := Remarshal(&obj)
if err != nil {
log.Debugf("Failed to remarshal oject: %v", err)
} else {
obj = *remObj
}
objs = append(objs, &obj)
}
return objs, firstErr
}
// Remarshal checks resource kind and version and re-marshal using corresponding struct custom marshaller.
// This ensures that expected resource state is formatter same as actual resource state in kubernetes
// and allows to find differences between actual and target states more accurately.
func Remarshal(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
item, err := scheme.Scheme.New(obj.GroupVersionKind())
if err != nil {
return nil, err
}
// This will drop any omitempty fields, perform resource conversion etc...
unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
err = json.Unmarshal(data, &unmarshalledObj)
if err != nil {
return nil, err
}
unstrBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(unmarshalledObj)
if err != nil {
return nil, err
}
// remove all default values specified by custom formatter (e.g. creationTimestamp)
unstrBody = jsonutil.RemoveMapFields(obj.Object, unstrBody)
return &unstructured.Unstructured{Object: unstrBody}, nil
}
// WatchWithRetry returns channel of watch events or errors of failed to call watch API.
func WatchWithRetry(ctx context.Context, getWatch func() (watch.Interface, error)) chan struct {
*watch.Event

View File

@@ -202,53 +202,6 @@ func TestCleanKubectlOutput(t *testing.T) {
assert.Equal(t, cleanKubectlOutput(testString), `error validating data: ValidationError(Deployment.spec): missing required field "selector" in io.k8s.api.apps.v1beta2.DeploymentSpec`)
}
func TestRemarshal(t *testing.T) {
manifest := []byte(`
apiVersion: v1
kind: ServiceAccount
imagePullSecrets: []
metadata:
name: my-sa
`)
var un unstructured.Unstructured
err := yaml.Unmarshal(manifest, &un)
assert.NoError(t, err)
newUn, err := Remarshal(&un)
assert.NoError(t, err)
_, ok := newUn.Object["imagePullSecrets"]
assert.False(t, ok)
metadata := newUn.Object["metadata"].(map[string]interface{})
_, ok = metadata["creationTimestamp"]
assert.False(t, ok)
}
func TestRemarshalResources(t *testing.T) {
manifest := []byte(`
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: nginx:1.7.9
name: nginx
resources:
requests:
cpu: 0.2
`)
un := unstructured.Unstructured{}
err := yaml.Unmarshal(manifest, &un)
assert.NoError(t, err)
requestsBefore := un.Object["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{})
log.Println(requestsBefore)
newUn, err := Remarshal(&un)
assert.NoError(t, err)
requestsAfter := newUn.Object["spec"].(map[string]interface{})["containers"].([]interface{})[0].(map[string]interface{})["resources"].(map[string]interface{})["requests"].(map[string]interface{})
log.Println(requestsAfter)
assert.Equal(t, float64(0.2), requestsBefore["cpu"])
assert.Equal(t, "200m", requestsAfter["cpu"])
}
func TestInClusterKubeConfig(t *testing.T) {
restConfig := &rest.Config{}
kubeConfig := NewKubeConfig(restConfig, "")