From 1747ed3b4814872195f15fb95deb5d89503f2fbe Mon Sep 17 00:00:00 2001 From: Ludovic Cleroux Date: Wed, 7 Feb 2024 14:43:42 +0100 Subject: [PATCH 1/7] ROX-22235 add AWS SES cloudwatch-exporter job (#1634) --- .../templates/01-operator-03-config-map.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dp-terraform/helm/rhacs-terraform/charts/cloudwatch/templates/01-operator-03-config-map.yaml b/dp-terraform/helm/rhacs-terraform/charts/cloudwatch/templates/01-operator-03-config-map.yaml index b50ed5e72d..a4f99fd25b 100644 --- a/dp-terraform/helm/rhacs-terraform/charts/cloudwatch/templates/01-operator-03-config-map.yaml +++ b/dp-terraform/helm/rhacs-terraform/charts/cloudwatch/templates/01-operator-03-config-map.yaml @@ -40,3 +40,14 @@ data: - name: TransactionLogsDiskUsage - name: Deadlocks - name: BufferCacheHitRatio + - type: AWS/SES + regions: + - us-east-1 + statistics: + - Sum + metrics: + - name: Delivery + - name: Send + - name: Bounce + - name: Reputation.BounceRate + - name: Reputation.ComplaintRate From 924536c210321ec0bd213bbcf9aebce5610ffa5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl?= Date: Wed, 7 Feb 2024 14:51:47 +0100 Subject: [PATCH 2/7] feat: expose `expired-at` via Telemetry (#1640) --- internal/dinosaur/pkg/services/dinosaur.go | 13 +++-- internal/dinosaur/pkg/services/telemetry.go | 55 ++++++++++++++------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/internal/dinosaur/pkg/services/dinosaur.go b/internal/dinosaur/pkg/services/dinosaur.go index 24e57b029e..932af5465d 100644 --- a/internal/dinosaur/pkg/services/dinosaur.go +++ b/internal/dinosaur/pkg/services/dinosaur.go @@ -129,13 +129,14 @@ type dinosaurService struct { amsClient ocm.AMSClient iamConfig *iam.IAMConfig rhSSODynamicClientsAPI *dynamicClientAPI.AcsTenantsApiService + telemetry *Telemetry } // NewDinosaurService ... func NewDinosaurService(connectionFactory *db.ConnectionFactory, clusterService ClusterService, iamConfig *iam.IAMConfig, dinosaurConfig *config.CentralConfig, dataplaneClusterConfig *config.DataplaneClusterConfig, awsConfig *config.AWSConfig, quotaServiceFactory QuotaServiceFactory, awsClientFactory aws.ClientFactory, - clusterPlacementStrategy ClusterPlacementStrategy, amsClient ocm.AMSClient) DinosaurService { + clusterPlacementStrategy ClusterPlacementStrategy, amsClient ocm.AMSClient, telemetry *Telemetry) DinosaurService { return &dinosaurService{ connectionFactory: connectionFactory, clusterService: clusterService, @@ -148,6 +149,7 @@ func NewDinosaurService(connectionFactory *db.ConnectionFactory, clusterService clusterPlacementStrategy: clusterPlacementStrategy, amsClient: amsClient, rhSSODynamicClientsAPI: dynamicclients.NewDynamicClientsAPI(iamConfig.RedhatSSORealm), + telemetry: telemetry, } } @@ -689,7 +691,7 @@ func (k *dinosaurService) Update(dinosaurRequest *dbapi.CentralRequest) *errors. if err := dbConn.Updates(dinosaurRequest).Error; err != nil { return errors.NewWithCause(errors.ErrorGeneral, err, "Failed to update central") } - + k.telemetry.UpdateTenantProperties(dinosaurRequest) return nil } @@ -702,7 +704,10 @@ func (k *dinosaurService) Updates(dinosaurRequest *dbapi.CentralRequest, fields if err := dbConn.Updates(fields).Error; err != nil { return errors.NewWithCause(errors.ErrorGeneral, err, "Failed to update central") } - + // Get all request properties, not only the ones provided with fields. + if dinosaurRequest, svcErr := k.GetByID(dinosaurRequest.ID); svcErr == nil { + k.telemetry.UpdateTenantProperties(dinosaurRequest) + } return nil } @@ -750,7 +755,7 @@ func (k *dinosaurService) UpdateStatus(id string, status dinosaurConstants.Centr if err := dbConn.Model(&dbapi.CentralRequest{Meta: api.Meta{ID: id}}).Updates(update).Error; err != nil { return true, errors.NewWithCause(errors.ErrorGeneral, err, "Failed to update central status") } - + k.telemetry.UpdateTenantProperties(dinosaur) return true, nil } diff --git a/internal/dinosaur/pkg/services/telemetry.go b/internal/dinosaur/pkg/services/telemetry.go index 69698c75c1..650cc118c4 100644 --- a/internal/dinosaur/pkg/services/telemetry.go +++ b/internal/dinosaur/pkg/services/telemetry.go @@ -61,18 +61,8 @@ func (t *Telemetry) enabled() bool { return t != nil && t.config != nil && t.config.Enabled() } -// setTenantProperties emits a group event that captures meta data of the input central instance. -// Adds the token user to the tenant group. -func (t *Telemetry) setTenantProperties(ctx context.Context, central *dbapi.CentralRequest) { - if !t.enabled() { - return - } - - user, err := t.auth.getUserFromContext(ctx) - if err != nil { - glog.Error(errors.Wrap(err, "cannot get telemetry user from context claims")) - return - } +// getTenantProperties returns the tenant group properties map. +func (t *Telemetry) getTenantProperties(central *dbapi.CentralRequest) map[string]any { props := map[string]any{ "Cloud Account": central.CloudAccountID, "Cloud Provider": central.CloudProvider, @@ -80,13 +70,17 @@ func (t *Telemetry) setTenantProperties(ctx context.Context, central *dbapi.Cent "Organisation ID": central.OrganisationID, "Region": central.Region, "Tenant ID": central.ID, + "Status": central.Status, } - // Group call will issue a supporting Track event to force group properties - // update. - t.config.Telemeter().Group(props, - telemeter.WithUserID(user), - telemeter.WithGroups(TenantGroupName, central.ID), - ) + if central.ExpiredAt != nil { + props["Expired At"] = central.ExpiredAt.UTC().Format(time.RFC3339) + } else { + // An instance may loose its expiration date after quota is granted, so + // we need to reset the group property, hence never report nil time, as + // nil is not a value on Amplitude. + props["Expired At"] = time.Time{}.Format(time.RFC3339) + } + return props } // trackCreationRequested emits a track event that signals the creation request of a Central instance. @@ -123,7 +117,21 @@ func (t *Telemetry) trackCreationRequested(ctx context.Context, tenantID string, // RegisterTenant initializes the tenant group with the associated properties // and issues a following event tracking the central creation request. func (t *Telemetry) RegisterTenant(ctx context.Context, convCentral *dbapi.CentralRequest, isAdmin bool, err error) { - t.setTenantProperties(ctx, convCentral) + user, err := t.auth.getUserFromContext(ctx) + if err != nil { + glog.Error(errors.Wrap(err, "cannot get telemetry user from context claims")) + return + } + + props := t.getTenantProperties(convCentral) + // Adds the token user to the tenant group. + // Group call will issue a supporting Track event to force group properties + // update. + t.config.Telemeter().Group(props, + telemeter.WithUserID(user), + telemeter.WithGroups(TenantGroupName, convCentral.ID), + ) + go func() { // This is to raise the chances for the tenant group properties be // procesed by Segment: @@ -132,6 +140,15 @@ func (t *Telemetry) RegisterTenant(ctx context.Context, convCentral *dbapi.Centr }() } +// UpdateTenant updates tenant group properties. +func (t *Telemetry) UpdateTenantProperties(convCentral *dbapi.CentralRequest) { + props := t.getTenantProperties(convCentral) + // Update tenant group properties from the name of fleet-manager backend. + t.config.Telemeter().Group(props, + telemeter.WithGroups(TenantGroupName, convCentral.ID), + ) +} + // TrackDeletionRequested emits a track event that signals the deletion request of a Central instance. func (t *Telemetry) TrackDeletionRequested(ctx context.Context, tenantID string, isAdmin bool, requestErr error) { if !t.enabled() { From 129a4ce2b9ac8b363211adfa7ef8510935e09be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl?= Date: Wed, 7 Feb 2024 16:28:41 +0100 Subject: [PATCH 3/7] feat: expiration date in resource annotations (#1631) --- .../pkg/central/reconciler/reconciler.go | 34 ++++++++++++++++--- .../pkg/central/reconciler/reconciler_test.go | 9 ++++- .../pkg/api/admin/private/api/openapi.yaml | 1 + .../pkg/api/admin/private/api_default.go | 3 +- .../dinosaur/pkg/api/private/api/openapi.yaml | 4 +++ .../model_managed_central_all_of_metadata.go | 5 +++ .../dinosaur/pkg/presenters/managedcentral.go | 1 + openapi/fleet-manager-private-admin.yaml | 1 + openapi/fleet-manager-private.yaml | 4 +++ 9 files changed, 55 insertions(+), 7 deletions(-) diff --git a/fleetshard/pkg/central/reconciler/reconciler.go b/fleetshard/pkg/central/reconciler/reconciler.go index acad53ef5e..5d7b00c4a7 100644 --- a/fleetshard/pkg/central/reconciler/reconciler.go +++ b/fleetshard/pkg/central/reconciler/reconciler.go @@ -31,6 +31,7 @@ import ( "github.com/stackrox/rox/operator/apis/platform/v1alpha1" "github.com/stackrox/rox/pkg/declarativeconfig" "github.com/stackrox/rox/pkg/random" + "golang.org/x/exp/maps" "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" @@ -62,6 +63,7 @@ const ( instanceTypeLabelKey = "rhacs.redhat.com/instance-type" orgIDLabelKey = "rhacs.redhat.com/org-id" tenantIDLabelKey = "rhacs.redhat.com/tenant" + centralExpiredAtKey = "rhacs.redhat.com/expired-at" auditLogNotifierKey = "com.redhat.rhacs.auditLogNotifier" auditLogNotifierName = "Platform Audit Logs" @@ -200,7 +202,10 @@ func (r *CentralReconciler) Reconcile(ctx context.Context, remoteCentral private namespaceAnnotations := map[string]string{ orgNameAnnotationKey: remoteCentral.Spec.Auth.OwnerOrgName, } - if err := r.ensureNamespaceExists(remoteCentralNamespace, namespaceLabels, namespaceAnnotations); err != nil { + if remoteCentral.Metadata.ExpiredAt != nil { + namespaceAnnotations[centralExpiredAtKey] = remoteCentral.Metadata.ExpiredAt.Format(time.RFC3339) + } + if err := r.reconcileNamespace(ctx, remoteCentralNamespace, namespaceLabels, namespaceAnnotations); err != nil { return nil, errors.Wrapf(err, "unable to ensure that namespace %s exists", remoteCentralNamespace) } @@ -309,11 +314,11 @@ func (r *CentralReconciler) applyCentralConfig(remoteCentral *private.ManagedCen r.applyRoutes(central) r.applyProxyConfig(central) r.applyDeclarativeConfig(central) - r.applyAnnotations(central) + r.applyAnnotations(remoteCentral, central) return nil } -func (r *CentralReconciler) applyAnnotations(central *v1alpha1.Central) { +func (r *CentralReconciler) applyAnnotations(remoteCentral *private.ManagedCentral, central *v1alpha1.Central) { if central.Spec.Customize == nil { central.Spec.Customize = &v1alpha1.CustomizeSpec{} } @@ -322,6 +327,9 @@ func (r *CentralReconciler) applyAnnotations(central *v1alpha1.Central) { } central.Spec.Customize.Annotations[envAnnotationKey] = r.environment central.Spec.Customize.Annotations[clusterNameAnnotationKey] = r.clusterName + if remoteCentral.Metadata.ExpiredAt != nil { + central.Spec.Customize.Annotations[centralExpiredAtKey] = remoteCentral.Metadata.ExpiredAt.Format(time.RFC3339) + } } func (r *CentralReconciler) applyDeclarativeConfig(central *v1alpha1.Central) { @@ -668,6 +676,13 @@ func (r *CentralReconciler) reconcileCentral(ctx context.Context, remoteCentral centralExists = false } + if remoteCentral.Metadata.ExpiredAt != nil { + if central.GetAnnotations() == nil { + central.Annotations = map[string]string{} + } + central.Annotations[centralExpiredAtKey] = remoteCentral.Metadata.ExpiredAt.Format(time.RFC3339) + } + if !centralExists { if central.GetAnnotations() == nil { central.Annotations = map[string]string{} @@ -1170,15 +1185,24 @@ func (r *CentralReconciler) createTenantNamespace(ctx context.Context, namespace return nil } -func (r *CentralReconciler) ensureNamespaceExists(name string, labels map[string]string, annotations map[string]string) error { +func (r *CentralReconciler) reconcileNamespace(ctx context.Context, name string, labels map[string]string, annotations map[string]string) error { namespace, err := r.getNamespace(name) if err != nil { if apiErrors.IsNotFound(err) { namespace.Annotations = annotations namespace.Labels = labels - return r.createTenantNamespace(context.Background(), namespace) + return r.createTenantNamespace(ctx, namespace) } return fmt.Errorf("getting namespace %s: %w", name, err) + } else if !maps.Equal(labels, namespace.Labels) || + !maps.Equal(annotations, namespace.Annotations) { + namespace.Annotations = annotations + namespace.Labels = labels + if err = r.client.Update(ctx, namespace, &ctrlClient.UpdateOptions{ + FieldManager: "fleetshard-sync", + }); err != nil { + return fmt.Errorf("updating namespace %s: %w", name, err) + } } return nil } diff --git a/fleetshard/pkg/central/reconciler/reconciler_test.go b/fleetshard/pkg/central/reconciler/reconciler_test.go index ac1f007e34..8e2a9dec5c 100644 --- a/fleetshard/pkg/central/reconciler/reconciler_test.go +++ b/fleetshard/pkg/central/reconciler/reconciler_test.go @@ -2153,11 +2153,18 @@ func TestReconciler_applyAnnotations(t *testing.T) { }, }, } - r.applyAnnotations(c) + date := time.Date(2024, 01, 01, 0, 0, 0, 0, time.UTC) + rc := &private.ManagedCentral{ + Metadata: private.ManagedCentralAllOfMetadata{ + ExpiredAt: &date, + }, + } + r.applyAnnotations(rc, c) assert.Equal(t, map[string]string{ "rhacs.redhat.com/environment": "test", "rhacs.redhat.com/cluster-name": "test", "foo": "bar", + "rhacs.redhat.com/expired-at": "2024-01-01T00:00:00Z", }, c.Spec.Customize.Annotations) } diff --git a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml index 85bcfaee16..caaf9e40c1 100644 --- a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml +++ b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml @@ -342,6 +342,7 @@ paths: name: reason required: true schema: + format: date-time type: string style: form responses: diff --git a/internal/dinosaur/pkg/api/admin/private/api_default.go b/internal/dinosaur/pkg/api/admin/private/api_default.go index b25f54015b..16d4bba3d6 100644 --- a/internal/dinosaur/pkg/api/admin/private/api_default.go +++ b/internal/dinosaur/pkg/api/admin/private/api_default.go @@ -17,6 +17,7 @@ import ( _nethttp "net/http" _neturl "net/url" "strings" + "time" ) // Linger please @@ -885,7 +886,7 @@ UpdateCentralExpiredAtById Update `expired_at` central property @return Central */ -func (a *DefaultApiService) UpdateCentralExpiredAtById(ctx _context.Context, id string, reason string, localVarOptionals *UpdateCentralExpiredAtByIdOpts) (Central, *_nethttp.Response, error) { +func (a *DefaultApiService) UpdateCentralExpiredAtById(ctx _context.Context, id string, reason time.Time, localVarOptionals *UpdateCentralExpiredAtByIdOpts) (Central, *_nethttp.Response, error) { var ( localVarHTTPMethod = _nethttp.MethodPatch localVarPostBody interface{} diff --git a/internal/dinosaur/pkg/api/private/api/openapi.yaml b/internal/dinosaur/pkg/api/private/api/openapi.yaml index 6f653f67e1..7ec6b23486 100644 --- a/internal/dinosaur/pkg/api/private/api/openapi.yaml +++ b/internal/dinosaur/pkg/api/private/api/openapi.yaml @@ -444,6 +444,10 @@ components: additionalProperties: type: string type: object + expired-at: + format: date-time + nullable: true + type: string ManagedCentral_allOf_spec_auth: properties: clientSecret: diff --git a/internal/dinosaur/pkg/api/private/model_managed_central_all_of_metadata.go b/internal/dinosaur/pkg/api/private/model_managed_central_all_of_metadata.go index d50ae48c5e..8abc1c1245 100644 --- a/internal/dinosaur/pkg/api/private/model_managed_central_all_of_metadata.go +++ b/internal/dinosaur/pkg/api/private/model_managed_central_all_of_metadata.go @@ -10,6 +10,10 @@ // Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. package private +import ( + "time" +) + // ManagedCentralAllOfMetadata struct for ManagedCentralAllOfMetadata type ManagedCentralAllOfMetadata struct { Name string `json:"name,omitempty"` @@ -19,4 +23,5 @@ type ManagedCentralAllOfMetadata struct { DeletionTimestamp string `json:"deletionTimestamp,omitempty"` SecretsStored []string `json:"secretsStored,omitempty"` Secrets map[string]string `json:"secrets,omitempty"` + ExpiredAt *time.Time `json:"expired-at,omitempty"` } diff --git a/internal/dinosaur/pkg/presenters/managedcentral.go b/internal/dinosaur/pkg/presenters/managedcentral.go index df321ab66b..d87de99115 100644 --- a/internal/dinosaur/pkg/presenters/managedcentral.go +++ b/internal/dinosaur/pkg/presenters/managedcentral.go @@ -119,6 +119,7 @@ func (c *ManagedCentralPresenter) presentManagedCentral(gitopsConfig gitops.Conf }, Internal: from.Internal, SecretsStored: getSecretNames(from), // pragma: allowlist secret + ExpiredAt: from.ExpiredAt, }, Spec: private.ManagedCentralAllOfSpec{ Owners: []string{ diff --git a/openapi/fleet-manager-private-admin.yaml b/openapi/fleet-manager-private-admin.yaml index 0be614dd36..0974c72b2f 100644 --- a/openapi/fleet-manager-private-admin.yaml +++ b/openapi/fleet-manager-private-admin.yaml @@ -222,6 +222,7 @@ paths: name: reason schema: type: string + format: date-time required: true security: - Bearer: [ ] diff --git a/openapi/fleet-manager-private.yaml b/openapi/fleet-manager-private.yaml index a53d7c1daa..9b20766ca6 100644 --- a/openapi/fleet-manager-private.yaml +++ b/openapi/fleet-manager-private.yaml @@ -269,6 +269,10 @@ components: type: object additionalProperties: type: string + expired-at: + type: string + format: date-time + nullable: true spec: type: object properties: From cbd870af2c42571181aba35874089389cdf8ad7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl?= Date: Thu, 8 Feb 2024 10:24:00 +0100 Subject: [PATCH 4/7] ROX-21867: Central traits DB and API (#1599) --- .gitattributes | 1 + .secrets.baseline | 2 +- config/admin-authz-roles-dev.yaml | 5 + config/admin-authz-roles-prod.yaml | 4 + .../manifests/shared/03-configmap-config.yaml | 9 + .../pkg/api/admin/private/api/openapi.yaml | 198 ++++++++ .../pkg/api/admin/private/api_default.go | 441 ++++++++++++++++++ .../pkg/api/admin/private/model_central.go | 1 + .../admin/private/model_central_request.go | 1 + .../pkg/api/dbapi/central_request_types.go | 5 + .../dinosaur/pkg/api/public/api/openapi.yaml | 4 + .../pkg/api/public/model_central_request.go | 1 + .../pkg/converters/dinosaur_request.go | 2 + internal/dinosaur/pkg/generated/bindata.go | 4 +- .../dinosaur/pkg/handlers/admin_dinosaur.go | 89 ++++ .../20240112000000_add_central_traits.go | 31 ++ .../dinosaur/pkg/migrations/migrations.go | 1 + .../dinosaur/pkg/presenters/admin_dinosaur.go | 1 + internal/dinosaur/pkg/routes/route_loader.go | 13 + openapi/fleet-manager-private-admin.yaml | 152 ++++++ openapi/fleet-manager.yaml | 4 + pkg/auth/roles_authz.go | 2 +- pkg/handlers/validation.go | 15 +- pkg/server/api_server.go | 1 + 24 files changed, 981 insertions(+), 6 deletions(-) create mode 100644 .gitattributes create mode 100644 internal/dinosaur/pkg/migrations/20240112000000_add_central_traits.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..fa0bf56143 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +internal/dinosaur/pkg/api/admin/private/api/openapi.yaml linguist-generated diff --git a/.secrets.baseline b/.secrets.baseline index e790f26a49..9b2ff95eec 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -351,7 +351,7 @@ "filename": "internal/dinosaur/pkg/api/public/api/openapi.yaml", "hashed_secret": "5b455797b93de5b6a19633ba22127c8a610f5c1b", "is_verified": false, - "line_number": 1531 + "line_number": 1535 } ], "internal/dinosaur/pkg/services/dinosaurservice_moq.go": [ diff --git a/config/admin-authz-roles-dev.yaml b/config/admin-authz-roles-dev.yaml index ea0d6ae5d5..41a86c610a 100644 --- a/config/admin-authz-roles-dev.yaml +++ b/config/admin-authz-roles-dev.yaml @@ -19,3 +19,8 @@ roles: - "acs-general-engineering" - "acs-fleet-manager-admin-full" +- method: PUT + roles: + - "acs-general-engineering" # Will include all of ACS engineering. Available also within staging environment. + - "acs-fleet-manager-admin-full" # Prod rover group, will only include selected members + SREs. + - "acs-fleet-manager-admin-write" # Prod rover group, will only include selected members + SREs. diff --git a/config/admin-authz-roles-prod.yaml b/config/admin-authz-roles-prod.yaml index 1f3b70a7d1..9975418772 100644 --- a/config/admin-authz-roles-prod.yaml +++ b/config/admin-authz-roles-prod.yaml @@ -15,3 +15,7 @@ - method: POST roles: - "acs-fleet-manager-admin-full" +- method: PUT + roles: + - "acs-fleet-manager-admin-full" # Prod rover group, will only include selected members + SREs. + - "acs-fleet-manager-admin-write" # Prod rover group, will only include selected members + SREs. diff --git a/dev/env/manifests/shared/03-configmap-config.yaml b/dev/env/manifests/shared/03-configmap-config.yaml index 28dbb322cd..a95c017c99 100644 --- a/dev/env/manifests/shared/03-configmap-config.yaml +++ b/dev/env/manifests/shared/03-configmap-config.yaml @@ -223,6 +223,11 @@ data: - "acs-general-engineering" # Will include all of ACS engineering. Available also within staging environment. - "acs-fleet-manager-admin-full" # Prod rover group, will only include selected members + SREs. - "acs-fleet-manager-admin-write" # Prod rover group, will only include selected members + SREs. + - method: PUT + roles: + - "acs-general-engineering" # Will include all of ACS engineering. Available also within staging environment. + - "acs-fleet-manager-admin-full" # Prod rover group, will only include selected members + SREs. + - "acs-fleet-manager-admin-write" # Prod rover group, will only include selected members + SREs. admin-authz-roles-prod.yaml: |- --- - method: GET @@ -241,6 +246,10 @@ data: roles: - "acs-fleet-manager-admin-full" # Prod rover group, will only include selected members + SREs. - "acs-fleet-manager-admin-write" # Prod rover group, will only include selected members + SREs. + - method: PUT + roles: + - "acs-fleet-manager-admin-full" # Prod rover group, will only include selected members + SREs. + - "acs-fleet-manager-admin-write" # Prod rover group, will only include selected members + SREs. kind: ConfigMap metadata: name: config diff --git a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml index caaf9e40c1..68639fc283 100644 --- a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml +++ b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml @@ -559,7 +559,197 @@ paths: security: - Bearer: [] summary: Delete a Central directly in the Database by ID + /api/rhacs/v1/admin/centrals/{id}/traits: + get: + operationId: getCentralTraits + parameters: + - description: The ID of record + in: path + name: id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + items: + type: string + type: array + description: Central traits + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: User is not authorised to access the service + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: No Central found with the specified ID + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + summary: Returns a list of central traits. + /api/rhacs/v1/admin/centrals/{id}/traits/{trait}: + delete: + operationId: deleteCentralTrait + parameters: + - description: The ID of record + in: path + name: id + required: true + schema: + type: string + - description: A central trait + explode: false + in: path + name: trait + required: true + schema: + type: string + style: simple + responses: + "200": + description: Central trait deleted + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: User is not authorised to access the service + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: No Central found with the specified ID + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + summary: Deletes the central trait. + get: + operationId: getCentralTrait + parameters: + - description: The ID of record + in: path + name: id + required: true + schema: + type: string + - description: A central trait + explode: false + in: path + name: trait + required: true + schema: + type: string + style: simple + responses: + "200": + description: Trait exists. + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: User is not authorised to access the service + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: No Central found with the specified ID or no such trait. + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + summary: Returns central trait status. + put: + operationId: putCentralTrait + parameters: + - description: The ID of record + in: path + name: id + required: true + schema: + type: string + - description: A central trait + explode: false + in: path + name: trait + required: true + schema: + type: string + style: simple + responses: + "200": + description: Trait has been added or already exists. + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: User is not authorised to access the service + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: No Central found with the specified ID + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + summary: Adds a trait to a central. components: + parameters: + trait: + description: A central trait + explode: false + in: path + name: trait + required: true + schema: + type: string + style: simple schemas: Central: allOf: @@ -722,6 +912,10 @@ components: type: string namespace: type: string + traits: + items: + type: string + type: array CentralList_allOf: properties: items: @@ -776,6 +970,10 @@ components: type: string instance_type: type: string + traits: + items: + type: string + type: array required: - multi_az securitySchemes: diff --git a/internal/dinosaur/pkg/api/admin/private/api_default.go b/internal/dinosaur/pkg/api/admin/private/api_default.go index 16d4bba3d6..51398ffb78 100644 --- a/internal/dinosaur/pkg/api/admin/private/api_default.go +++ b/internal/dinosaur/pkg/api/admin/private/api_default.go @@ -510,6 +510,114 @@ func (a *DefaultApiService) DeleteCentralById(ctx _context.Context, id string, a return localVarReturnValue, localVarHTTPResponse, nil } +/* +DeleteCentralTrait Deletes the central trait. + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param id The ID of record + - @param trait A central trait +*/ +func (a *DefaultApiService) DeleteCentralTrait(ctx _context.Context, id string, trait string) (*_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodDelete + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/rhacs/v1/admin/centrals/{id}/traits/{trait}" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", _neturl.QueryEscape(parameterToString(id, "")), -1) + + localVarPath = strings.Replace(localVarPath, "{"+"trait"+"}", _neturl.QueryEscape(parameterToString(trait, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 403 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarHTTPResponse, newErr + } + + return localVarHTTPResponse, nil +} + /* DeleteDbCentralById Delete a Central directly in the Database by ID - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @@ -732,6 +840,231 @@ func (a *DefaultApiService) GetCentralById(ctx _context.Context, id string) (Cen return localVarReturnValue, localVarHTTPResponse, nil } +/* +GetCentralTrait Returns central trait status. + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param id The ID of record + - @param trait A central trait +*/ +func (a *DefaultApiService) GetCentralTrait(ctx _context.Context, id string, trait string) (*_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/rhacs/v1/admin/centrals/{id}/traits/{trait}" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", _neturl.QueryEscape(parameterToString(id, "")), -1) + + localVarPath = strings.Replace(localVarPath, "{"+"trait"+"}", _neturl.QueryEscape(parameterToString(trait, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 403 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarHTTPResponse, newErr + } + + return localVarHTTPResponse, nil +} + +/* +GetCentralTraits Returns a list of central traits. + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param id The ID of record + +@return []string +*/ +func (a *DefaultApiService) GetCentralTraits(ctx _context.Context, id string) ([]string, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []string + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/rhacs/v1/admin/centrals/{id}/traits" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", _neturl.QueryEscape(parameterToString(id, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 403 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + // GetCentralsOpts Optional parameters for the method 'GetCentrals' type GetCentralsOpts struct { Page optional.String @@ -871,6 +1204,114 @@ func (a *DefaultApiService) GetCentrals(ctx _context.Context, localVarOptionals return localVarReturnValue, localVarHTTPResponse, nil } +/* +PutCentralTrait Adds a trait to a central. + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param id The ID of record + - @param trait A central trait +*/ +func (a *DefaultApiService) PutCentralTrait(ctx _context.Context, id string, trait string) (*_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPut + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/rhacs/v1/admin/centrals/{id}/traits/{trait}" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", _neturl.QueryEscape(parameterToString(id, "")), -1) + + localVarPath = strings.Replace(localVarPath, "{"+"trait"+"}", _neturl.QueryEscape(parameterToString(trait, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 403 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarHTTPResponse, newErr + } + + return localVarHTTPResponse, nil +} + // UpdateCentralExpiredAtByIdOpts Optional parameters for the method 'UpdateCentralExpiredAtById' type UpdateCentralExpiredAtByIdOpts struct { Timestamp optional.String diff --git a/internal/dinosaur/pkg/api/admin/private/model_central.go b/internal/dinosaur/pkg/api/admin/private/model_central.go index bf613a9a13..a5445e1db6 100644 --- a/internal/dinosaur/pkg/api/admin/private/model_central.go +++ b/internal/dinosaur/pkg/api/admin/private/model_central.go @@ -43,4 +43,5 @@ type Central struct { RoutesCreated bool `json:"routes_created,omitempty"` ClusterId string `json:"cluster_id,omitempty"` Namespace string `json:"namespace,omitempty"` + Traits []string `json:"traits,omitempty"` } diff --git a/internal/dinosaur/pkg/api/admin/private/model_central_request.go b/internal/dinosaur/pkg/api/admin/private/model_central_request.go index 7d96250b0a..bf4dcc0d65 100644 --- a/internal/dinosaur/pkg/api/admin/private/model_central_request.go +++ b/internal/dinosaur/pkg/api/admin/private/model_central_request.go @@ -37,4 +37,5 @@ type CentralRequest struct { FailedReason string `json:"failed_reason,omitempty"` Version string `json:"version,omitempty"` InstanceType string `json:"instance_type,omitempty"` + Traits []string `json:"traits,omitempty"` } diff --git a/internal/dinosaur/pkg/api/dbapi/central_request_types.go b/internal/dinosaur/pkg/api/dbapi/central_request_types.go index 46fcf2b05c..5b46bc051e 100644 --- a/internal/dinosaur/pkg/api/dbapi/central_request_types.go +++ b/internal/dinosaur/pkg/api/dbapi/central_request_types.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/lib/pq" "github.com/stackrox/acs-fleet-manager/pkg/api" "gorm.io/gorm" ) @@ -90,6 +91,10 @@ type CentralRequest struct { // ExpiredAt contains the timestamp of when a Central instance's quota allowance was found to be expired. // After a grace period, the Central instance will be marked for deletion, its status will be set to 'deprovision'. ExpiredAt *time.Time `json:"expired_at"` + + // Traits is a set of random strings assigned to an instance. Some traits + // can be hardcoded, and change some processing parameters. + Traits pq.StringArray `json:"traits" gorm:"type:text[]"` } // CentralList ... diff --git a/internal/dinosaur/pkg/api/public/api/openapi.yaml b/internal/dinosaur/pkg/api/public/api/openapi.yaml index b720d63fd6..92b0bc534d 100644 --- a/internal/dinosaur/pkg/api/public/api/openapi.yaml +++ b/internal/dinosaur/pkg/api/public/api/openapi.yaml @@ -1485,6 +1485,10 @@ components: type: string instance_type: type: string + traits: + items: + type: string + type: array required: - multi_az CentralRequestList_allOf: diff --git a/internal/dinosaur/pkg/api/public/model_central_request.go b/internal/dinosaur/pkg/api/public/model_central_request.go index dcfe776573..2b7c22126e 100644 --- a/internal/dinosaur/pkg/api/public/model_central_request.go +++ b/internal/dinosaur/pkg/api/public/model_central_request.go @@ -37,4 +37,5 @@ type CentralRequest struct { FailedReason string `json:"failed_reason,omitempty"` Version string `json:"version,omitempty"` InstanceType string `json:"instance_type,omitempty"` + Traits []string `json:"traits,omitempty"` } diff --git a/internal/dinosaur/pkg/converters/dinosaur_request.go b/internal/dinosaur/pkg/converters/dinosaur_request.go index a4583adc59..f9fcd57cb7 100644 --- a/internal/dinosaur/pkg/converters/dinosaur_request.go +++ b/internal/dinosaur/pkg/converters/dinosaur_request.go @@ -6,6 +6,7 @@ import ( // ConvertDinosaurRequest ... func ConvertDinosaurRequest(request *dbapi.CentralRequest) []map[string]interface{} { + traits, _ := request.Traits.Value() return []map[string]interface{}{ { "id": request.ID, @@ -20,6 +21,7 @@ func ConvertDinosaurRequest(request *dbapi.CentralRequest) []map[string]interfac "created_at": request.Meta.CreatedAt, "updated_at": request.Meta.UpdatedAt, "deleted_at": request.Meta.DeletedAt.Time, + "traits": traits, }, } } diff --git a/internal/dinosaur/pkg/generated/bindata.go b/internal/dinosaur/pkg/generated/bindata.go index 8ce360e520..77d2688b13 100644 --- a/internal/dinosaur/pkg/generated/bindata.go +++ b/internal/dinosaur/pkg/generated/bindata.go @@ -79,7 +79,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _fleetManagerYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x7b\x53\x1b\xb9\xb2\xf8\xff\x7c\x8a\xfe\x39\xbf\x53\x9c\xdd\xc2\xc6\x36\x6f\xd7\xcd\xad\x22\x81\x24\xec\x49\x08\xcb\x63\xb3\xd9\xad\x53\x46\x9e\x91\x6d\x85\x19\x69\x90\x34\x06\xe7\x9e\xfb\xdd\x6f\x49\x9a\x87\xe6\xe9\x31\x84\x04\x76\xa1\x6a\x6b\xe3\x19\xa9\xa7\xdf\x6a\x49\xad\x16\x0b\x30\x45\x01\x19\xc0\x46\xa7\xdb\xe9\xc2\x0b\xa0\x18\xbb\x20\xa7\x44\x00\x12\x30\x26\x5c\x48\xf0\x08\xc5\x20\x19\x20\xcf\x63\x37\x20\x98\x8f\xe1\xe8\xe0\x50\xa8\x47\x57\x94\xdd\x98\xd6\xaa\x03\x85\x08\x1c\xb8\xcc\x09\x7d\x4c\x65\x67\xe5\x05\xec\x7b\x1e\x60\xea\x06\x8c\x50\x29\xc0\xc5\x63\x42\xb1\x0b\x53\xcc\x31\xdc\x10\xcf\x83\x11\x06\x97\x08\x87\xcd\x30\x47\x23\x0f\xc3\x68\xae\xbe\x04\xa1\xc0\x5c\x74\xe0\x68\x0c\x52\xb7\x55\x1f\x88\xb0\x63\x70\x85\x71\x60\x30\x49\x21\xb7\x02\x4e\x66\x48\xe2\xd6\x1a\x20\x57\xd1\x80\x7d\xd5\x54\x4e\x31\xb4\x7c\x44\xd1\x04\xbb\x6d\x81\xf9\x8c\x38\x58\xb4\x51\x40\xda\x51\xfb\xce\x1c\xf9\x5e\x0b\xc6\xc4\xc3\x2b\x84\x8e\xd9\x60\x05\x40\x12\xe9\xe1\x01\x9c\x62\x17\xde\x21\x09\xfb\xee\x0c\x51\x07\xbb\xf0\xda\x0b\x85\xc4\x1c\xce\xb0\x13\x72\x22\xe7\x70\x66\x00\xc2\x1b\x0f\x63\x09\x1f\xf4\x67\xf8\x0a\xc0\x0c\x73\x41\x18\x1d\x40\xaf\xd3\xef\x74\x57\x00\x5c\x2c\x1c\x4e\x02\xa9\x1f\x2e\x86\xfb\xcf\xd3\x77\xfb\xaf\xcf\x7e\x2a\x87\x6f\x78\x71\x8a\x85\x84\xfd\x93\x23\x45\xa4\xa1\x0f\x08\x15\x52\x01\x14\xc0\xc6\xb0\xff\xfa\x0c\x1c\xe6\x07\x8c\x62\x2a\x45\x67\x45\xd1\x8e\xb9\x50\xe4\xb5\x21\xe4\xde\x00\xa6\x52\x06\x62\xb0\xbe\x8e\x02\xd2\x51\x92\x13\x53\x32\x96\x1d\x87\xf9\x2b\x00\x39\x8c\x3f\x20\x42\xe1\x9f\x01\x67\x6e\xe8\xa8\x27\x3f\x81\x01\x57\x0e\x4c\x48\x34\xc1\x8b\x40\x9e\x49\x34\x21\x74\x52\x0a\x68\xb0\xbe\xee\x31\x07\x79\x53\x26\xe4\x60\xb7\xdb\xed\x16\xbb\x27\xef\xd3\x9e\xeb\xc5\x56\x4e\xc8\x39\xa6\x12\x5c\xe6\x23\x42\x57\x02\x24\xa7\x9a\x03\x0a\xcd\x75\x3e\x45\x8e\x58\x9f\xf5\x06\xba\xdf\x04\x4b\xf3\x0f\x50\x6a\xcc\x91\x02\x70\xe4\x0e\xd4\xf3\xdf\x8c\x34\x3f\x60\x89\x5c\x24\x51\xd4\x8a\x63\x11\x30\x2a\xb0\x88\xbb\x01\xb4\xfa\xdd\x6e\x2b\xfd\x09\xe0\x30\x2a\x31\x95\xf6\x23\x00\x14\x04\x1e\x71\xf4\x07\xd6\xbf\x08\x46\xb3\x6f\x01\x84\x33\xc5\x3e\xca\x3f\x05\xf8\xff\x1c\x8f\x07\xd0\x7a\xb1\x9e\x8a\x75\xdd\xb4\x15\xeb\x39\x14\x5b\x56\xe7\x0c\x43\xa2\x76\xe0\x67\x69\x11\xa1\xef\x23\x3e\x57\xaa\x29\x43\x4e\x85\x36\x9b\x59\xbe\x6d\x9e\x71\xeb\x98\x73\xc6\xc5\xfa\xff\x10\xf7\x7f\x17\x32\xf1\x50\xb5\x7d\x35\x3f\x72\x1f\x23\xfb\x34\x72\x95\x4c\x7b\x8b\x25\x68\x52\x95\x73\x4a\x08\x28\xe5\x59\xd2\x8c\xc4\xcd\x24\x9a\x58\x24\xb6\x4d\x0b\x11\x3d\x08\x10\x47\x3e\x96\x91\x5d\xc6\x4d\xca\x30\x4d\x5b\xae\x13\xb7\x55\x25\x8a\x66\x52\x10\x8f\x56\x04\xef\x89\x90\x95\x62\x50\x2f\x95\x67\x0b\x98\x10\x44\x0d\x15\x19\x56\x96\x8a\xc3\xcb\x77\x51\x0e\x33\xd3\xad\x42\x3c\x05\xfe\x0a\x89\x64\xb8\x98\xbf\x91\xc3\x3e\xd3\xad\x1f\x23\x9b\x33\x08\x56\xb2\xfa\xe3\x55\x8a\xea\x56\x0e\xd5\x4c\xc3\x0b\x8a\x6f\x03\xec\x48\xec\x46\xaa\xcf\x1c\xed\x73\xdd\x47\x61\xc5\xea\x0f\xdf\x22\x3f\xf0\x6c\xe6\xc7\x7f\x5b\xdd\xee\xa1\x79\x59\x7c\x57\xfe\xa1\x18\xd6\x7a\xda\xb5\x55\xa7\x7e\x46\x69\x94\x02\x72\x2c\x58\xc8\x1d\x2c\xd6\x40\x84\xce\x54\x45\x57\x37\x53\xac\x42\x1b\xf0\xd1\x2d\xf1\x43\x1f\xa2\xe0\x04\x1c\x14\x20\x47\x05\x01\x53\x24\x60\x84\x31\x05\x8e\x91\x33\x4d\x58\x2a\xa2\x20\xc1\xd6\xda\x57\x18\x71\xcc\x07\xf0\xe7\xbf\x0b\x8a\xeb\x60\x2a\x39\xf2\x1a\x7a\xe9\xd7\xa6\xb5\xe5\xa7\x33\xe2\x3e\x57\xb1\x5e\xd2\x47\x05\x22\x8c\x7a\x73\x40\xa1\x9c\x32\x4e\xbe\x9a\xe8\x4c\x87\x6e\x40\xa8\x61\x01\xf2\x31\x30\x3e\x41\x94\x08\xd3\x09\x19\xde\xb0\x1b\x8a\x79\xf6\x0d\x1b\x9b\x2e\x01\x76\xc8\x98\xa8\xb8\xc8\x60\xd3\x79\x8c\x86\x14\xe1\x76\x8a\xaf\x43\x9c\x75\x5a\xf5\x5a\x97\xed\xf7\x16\xcb\xd3\x88\xaa\xbb\xea\x62\x16\x60\x4e\x2d\x1b\x7c\xf7\x13\x91\xd3\x37\x88\x78\xd8\x7d\xcd\xb1\xe6\x91\x71\x0e\xdf\x06\x9f\x1a\xc8\x95\xde\x27\x82\x00\xdc\x80\x80\x31\x0b\xa9\xab\xc7\xde\x83\x54\xf0\x9b\xdd\xde\x23\x89\x15\xea\xe5\xbd\xd9\xed\xdd\x95\x93\x69\xd7\x4a\x56\xed\x87\x72\x0a\x92\x5d\x61\x6d\x8c\x84\xce\x90\x47\x5c\x9b\x49\x1b\x4f\x84\x49\x1b\x77\x67\xd2\xc6\x22\x26\x5d\x08\xcc\x81\x32\x99\xf3\x53\xc8\x71\xb0\x88\x1c\xb5\xf1\xbd\x36\xe3\x36\x9f\x08\xe3\x36\xef\xce\xb8\xcd\x45\x8c\x3b\x66\x05\x5b\xbc\x21\x72\x6a\x79\xe8\xa3\x03\xc0\xb7\x44\x48\x51\x1d\x2f\x3c\x56\xd6\x7d\xd3\xe1\xbf\xc0\xba\x45\x81\xd1\xc2\x51\x1c\xca\x82\x0a\x54\x90\x47\xea\x15\x5d\xec\x61\x89\x4b\x07\x76\xf3\x6a\xc1\xd8\xfe\x9f\x04\x93\x73\x35\x3c\xab\x71\xdd\x8c\xe4\x96\xd5\x8c\x19\x37\xeb\x3d\x69\x0c\x80\xb8\xc5\xbf\xde\x4f\xba\x33\x72\x7d\x42\x89\x90\x1c\x49\x45\xf9\xf8\xae\x03\x3e\x40\xdf\x00\x34\x7d\x15\x3a\x6b\x80\xa8\x6b\xb0\x23\x63\x20\x52\x2f\x86\x78\x82\xa9\xa9\x94\xbc\xc7\xa7\xca\x67\x62\x84\x0e\xe0\x3a\xc4\x7c\x6e\x89\x99\x22\x1f\x0f\x00\x89\x39\x75\xaa\x84\x7f\x82\xf9\x98\x71\x5f\x7f\x11\x39\x26\x54\xa2\x80\xa8\xe9\x35\xe5\x8c\xb2\x50\x80\x8f\x28\xd5\x2b\x1f\x75\x4a\x2f\xe7\x01\x1e\xc0\x88\x31\x0f\x23\x6a\xbd\x51\xf2\x27\x1c\xbb\x03\x90\x3c\xc4\xb5\x01\x52\xbf\x3a\x7c\x3f\xd0\x8a\x91\x19\x30\x9e\x86\xf1\x6e\x76\xbb\x1a\x77\xc2\xe8\xdd\xfd\x5f\x1e\x44\xf5\xaa\x89\x1a\x55\x8d\x1e\x99\xf9\x61\x71\x9a\xf3\x1c\x8f\x3c\xc7\x23\xcf\xf1\x88\x8a\x47\x8c\x4f\xb9\x47\x54\x92\x01\xf0\xb7\x8d\x4d\xee\xc7\xc6\x3c\x80\xbb\xc7\x29\x71\x08\x62\xc0\xd5\x87\x20\x8d\xc2\x9a\xe2\x48\xdb\x68\xc5\xb3\x6a\x5d\xc3\x00\x09\x98\x28\x5f\xd3\x70\xd4\xcc\x33\x0e\x7d\xca\xc2\x9e\x43\xe4\x4c\x21\x02\xa6\x97\x5c\x10\x08\x42\x27\x5e\x69\x14\xa1\x62\x8f\xdc\x7b\x15\x94\x74\x40\x4f\x70\xb1\xd9\xa3\xba\x49\x38\x24\xa7\x48\x07\x28\xaa\xa5\x9e\xc0\x2a\xdb\x56\x1d\x4c\x10\x93\x81\x1c\xca\x29\xa6\x52\xe9\x5d\x12\x67\xe1\x98\xc5\x7f\xb1\x20\x45\xd3\xf4\x8a\xb9\x96\x96\x94\xce\xff\xad\x0d\x8a\x52\x53\xad\x37\xd4\x72\x33\x6d\xbe\xa4\x73\x82\xe6\x1e\x43\x6e\xd6\x68\xab\x4c\xf6\xe2\xec\x14\x4f\x48\xd1\x57\x2c\x30\xd3\xb8\x5b\xc5\xaa\xcd\xe1\xc5\x9d\xa0\xc6\xdd\x0a\x50\xef\x1c\x34\x3e\xb1\x55\xb5\x13\x26\x1e\x7e\x59\x2d\x1b\xf7\x38\x0e\x0e\x9e\x6a\x24\x1d\xaf\xce\xdd\x23\x92\xce\x81\x78\x8e\xa4\x9f\x23\xe9\x98\x49\xdf\x38\x92\x4e\xc0\x7e\x40\xb7\xfb\x9e\xc7\x6e\xb0\x7b\x14\xe5\x3d\x9c\x9a\x7d\x92\x7b\x7c\x6f\x11\xcc\x52\x44\xce\x31\xf7\xc5\x31\x93\xb1\x0f\xb8\xc7\xf7\x2b\x40\xd5\xcf\x24\xc6\x8c\x8f\x88\xeb\x62\x0a\x98\xe8\x1d\xa5\x11\x76\x50\x28\x70\x1a\x6d\x10\xd1\x68\xba\x01\x2c\xdb\x37\xde\x99\xa2\xa1\x3f\xc2\x7a\x1d\x27\xcd\x30\xd1\xa1\x8d\x83\x28\x8c\x70\x14\x63\x45\x01\x0e\x11\xe6\x9b\xf9\xdd\xab\xce\x93\x9c\xcc\x3c\xe0\xe2\xea\x79\x1a\xdf\x61\x37\xd9\x20\x04\x97\x61\x41\x57\xa5\x99\xba\xd8\x3c\xdb\x7b\x22\x3c\xdb\x3b\x46\x3e\x7e\xcd\xe8\xd8\x23\x8e\xbc\x3b\xff\xca\xc0\x54\x3b\x4b\xc5\x0f\xdd\x32\xd5\x3b\x17\x4b\x33\xaf\x89\x76\x22\x9d\x68\x88\x32\x4b\x81\x44\x24\x2c\x7f\x92\xd3\xc3\x07\x5c\xba\xde\xa7\x10\x56\xcd\x0a\xe1\x66\x4a\xbc\x98\x97\x74\xa2\x19\x9b\x9b\x0f\x36\x9f\x09\x5a\xb3\xcb\x74\xfe\x54\x06\xcd\xda\xb0\x2e\x59\x12\x8f\x93\x3c\x72\x3d\x45\xd9\x6c\xef\x23\xf5\xe6\xc0\x93\x2d\x7a\x26\x70\x3c\xf7\x8b\x5c\x1a\xe2\x38\x3b\x5d\x2b\x5b\x45\x36\x53\xb8\x26\x33\xb6\x8a\xfd\x75\xb1\x0c\x93\x9a\x6c\x7b\xe7\xac\x61\x01\x4b\xe0\xc7\x85\xf4\xf9\x0c\x1f\x58\x22\xac\x57\x7d\xbf\x4d\x38\x6f\x41\x6a\x55\x87\xec\x19\xa6\xbe\x42\x6e\x4e\xc3\xbf\x2b\x17\x97\xf4\x10\x47\x26\x5e\xfc\x35\xc4\x7c\x7e\x8f\xb0\xbe\x04\x4c\xab\x3a\x50\x5f\x22\x7e\x7d\xbc\x9c\xfb\xc6\x51\xfd\xdf\x37\x50\xbf\xef\x92\xf7\x73\xde\x59\x93\xd1\xfb\x4e\x19\xa4\x01\x9a\x58\xa2\x5a\xd8\x5c\x90\xaf\xcb\x34\x67\xdc\xc5\xfc\xd5\x7c\x99\x0f\x60\xc4\x9d\x69\xc9\x1a\xaf\xc7\x42\x77\x18\x70\x36\x23\x2e\x2e\xc9\x6e\xad\xcd\xf9\x14\x61\x10\x30\xae\x34\x44\x83\x81\x04\x4c\xd5\xd0\xac\x5a\x9d\xe4\x1a\x3d\xcc\x00\x6d\xd0\xc5\x6e\x63\x5c\xe1\xbb\x0e\xd8\x36\x23\xb2\xe3\xf5\xb3\xcb\x6f\xe2\xf2\x9f\x3d\xd7\x63\xf3\x5c\xb5\x6e\x45\x67\xc6\xae\x73\xbd\x64\x7e\x67\x1f\x13\x75\x4f\xf2\x4c\x2a\x0c\xba\x89\xef\x31\x8b\xf7\x8f\xc4\x03\xc5\x84\xfd\x30\x47\x64\xb8\xf1\xec\x86\x9e\xdd\x50\xf2\xf7\xe3\xdd\x10\x71\x97\x70\x42\x0f\x1b\x6d\xc5\x4b\xb2\x43\x39\x0f\x2a\x7d\x1d\x72\x1c\x16\x52\xb9\xa4\x77\x33\x9e\x20\xee\x0b\x37\x53\xe2\x4c\x61\x84\x3d\x46\x27\x71\x9e\xff\xaa\x88\x16\x48\xbe\x6a\x8d\xa8\x73\x6f\xfb\x11\x9c\x26\x7e\x0d\xfe\x06\x8e\x2d\xe6\xc7\xb3\x6b\x7b\x76\x6d\xc9\xdf\x37\x73\x6d\x2f\xd4\x7f\x70\x3e\xc5\x02\xeb\x35\xcd\x78\xd9\xb2\x3d\x46\x0e\xa1\x13\xe0\xd8\xd3\xeb\x96\xc9\x01\xea\xa8\x4f\xcd\xe1\xa1\x75\x1f\x4b\x4e\x1c\xb1\xae\x93\x4d\x86\x1c\xd1\x09\x5e\xec\x50\xa2\x4e\x26\x39\x4b\x12\x1f\x0b\xcc\x09\x16\xa0\xbb\x9b\xbc\x15\x18\xcd\x93\x95\xca\x24\x93\x28\xef\x43\x3e\x18\x38\xaf\xe6\xa7\xaa\xe3\xaf\x56\xbe\xcb\x03\x47\x48\xbf\x9c\x7d\x3c\x06\xc4\x39\x9a\x2b\x77\x72\xc2\x99\x8f\xe5\x14\x87\x29\x65\x6c\xf4\x05\x3b\x52\xc0\x98\x33\x1f\xd8\x48\x60\x3e\x43\x92\x71\x12\xfa\x3f\x42\xe1\x22\x3e\xa5\x5c\x7a\xf6\x2f\xcf\xfe\x25\xf9\x7b\x62\xa1\x93\x1b\x1a\x1f\xb0\x54\x3c\x24\x95\x01\x7a\x4b\x74\x19\x13\x4f\xfd\xbf\x3a\xc7\xb0\xc4\xfd\x2d\xe9\xf8\x4c\x94\x26\xef\xe2\xef\x4c\x26\x82\x7c\xf6\x78\x0b\x3c\x9e\xcd\xa7\x67\x9f\xf7\xec\xf3\x92\xbf\x27\xe6\xf3\x96\xf4\x46\x63\xec\x2a\xc7\xd1\x20\x12\x43\x9e\x97\x58\x30\xa1\x20\x1c\x8e\x02\xac\x6b\xee\x8c\x19\xf7\x91\xd4\x7b\xd8\x08\x26\x64\x86\xe9\x02\xff\x14\x7f\x34\x32\xbd\xef\xe3\x96\x62\x94\x2c\x1a\x90\xed\x9d\x24\xbe\x95\x11\x29\x8b\xb4\x52\x35\x5d\x0f\x3c\x44\x1a\xeb\xa3\x49\x71\x16\x92\x13\x3a\xb1\x1d\xcb\x5f\x66\xc7\xf8\x03\x11\x82\xd0\xc9\x49\xac\x88\xf7\xd8\x35\xae\x00\xf5\xec\x90\x97\xdd\x39\xde\xac\x66\x52\x6a\x9f\x7a\x5b\x55\x1f\x91\x7f\x12\x3c\xfa\xa6\x29\x75\xcf\x83\xd6\xc3\x0e\x5a\x2b\xe9\x2b\xd5\x33\xa2\xc5\x00\xf9\xa8\x83\xc0\x53\x3c\xc6\x1c\x53\x27\x41\xd3\x38\x4a\x13\x21\xc6\x9f\xe7\x6a\xf0\x90\xc4\xa6\x93\xb8\x36\x5d\xa5\xde\xf5\x8a\xd0\xc5\x8d\xa6\x8a\x88\xba\x46\x2a\x14\x1c\x24\x63\x4f\x74\xfe\xc1\xe2\x82\xfa\x8a\xf5\x33\x40\x13\x6c\xfd\x14\xe4\xab\xfd\x53\x32\x99\x9c\x22\xd2\x47\x6f\x24\xf6\xc5\x72\x84\x37\xa2\x4a\x61\x51\x6c\xa4\xa6\x36\x13\xeb\x30\x8e\x42\x6e\x71\x2b\x8d\x73\x7d\x33\xad\x9b\x71\x13\xe4\x79\x1f\xc7\x8b\xf4\x24\xd6\xea\x9c\x12\xd8\x61\x4e\x09\x3f\xaa\x78\x02\xda\x10\xdd\x82\xaa\x97\xf2\x06\xb4\x20\x51\x89\x59\x56\x36\x4f\x62\x97\x61\x56\xed\x4a\x3b\x25\x85\xa8\xee\xc4\x90\xec\xcc\x63\x69\x2e\x68\x85\x2a\x47\x51\x4f\xc8\x72\x6f\x4a\x9b\x37\xf6\x43\xd9\x9c\xb8\xef\x24\xff\xa2\x0d\x9a\xe6\x7e\xe8\x49\x32\x44\x5f\x1b\x30\xc9\xae\xc5\x95\xfe\x65\x5c\x7f\xeb\x37\xe4\x85\x58\x0c\xe0\x4f\x14\xa5\xd2\xaf\x41\xc0\x71\x80\x94\x98\xd7\xcc\x26\x80\x20\x8c\xea\x5f\x1c\x23\x77\xbe\x06\x63\x5d\xaa\x66\x0d\x5c\x9c\xbc\x5e\x33\xa5\x13\x08\x9d\xfc\x1b\xf2\xbe\xbb\x52\xdb\xb2\xdb\xcb\xf5\x68\x1e\xeb\xf2\x48\x63\xd0\x3b\x02\x10\x0a\x93\x16\xe5\xe2\xc0\x63\xf3\x0e\xbc\x61\x3c\x1e\x22\x60\xff\xd3\xd9\x92\x18\x44\x1b\x37\x25\x3a\x9f\xc5\xc1\x7c\x3b\xda\x8e\x80\xa3\x83\xc6\x9f\x89\x45\x56\xae\xaf\xc5\xe3\x80\x10\xed\xb9\x34\x91\x5c\x52\x1c\xd4\xda\x58\x8f\x8e\xf6\x3a\xb9\x9d\x9c\x0c\x9f\x06\x10\x8a\x36\x46\x42\xb6\x7b\x7a\x3e\xb3\x0c\xdb\xf4\x81\xce\xc6\x4e\x45\x1f\xb1\x6c\x2c\x11\x63\x6a\x17\x47\x17\xa7\xef\x97\xed\x74\x80\x24\x5a\xaa\x9b\x39\x8e\x31\x44\x32\xdf\xc5\x4c\x8e\x06\xe0\x22\x89\xdb\x92\xf8\xb8\x29\xc8\x30\x70\xbf\x35\x48\x63\x6d\xc3\x25\x3d\x79\x5c\x58\xb5\x69\xfb\xcc\xee\x68\xc3\x5e\xb8\x2c\xee\xba\xeb\x59\xc1\x62\xde\xf1\x43\x0e\x2b\xa5\xa8\xeb\x88\x03\x5a\x8b\xf2\xb0\x75\xcc\x01\xad\x5e\xf6\xa9\x8e\x31\x0a\x4f\x4d\x4c\x51\x78\xac\x86\xa3\x3c\x93\xef\x77\xc8\xf2\x61\xc7\xc9\x9c\x08\xd2\xbf\x7a\x61\x94\x1d\x52\xcd\x15\x60\xfd\x4e\x83\x69\x9d\xbc\xf7\x4f\x8e\x22\xa4\x72\x42\x52\x2f\x67\x39\xc9\x4d\x0d\x5a\x99\x05\xa7\x56\x2e\x3e\xf3\x3c\xac\x8f\x89\x17\x18\xd9\x36\x30\xe3\xf5\xa9\xe2\x21\xb9\x32\xe8\xeb\xd5\xcd\xb3\x0a\x9b\xd7\xd4\xea\x00\xb2\x12\xc1\xef\xa5\x18\xa5\x02\xcc\x54\xdc\x8c\x61\x66\x4b\x21\xeb\xee\xc9\x11\x92\xf8\x34\x5e\x54\x3d\x32\x5e\x30\x83\x11\x73\x63\xf4\x0b\x72\xcf\x56\x43\x30\x7f\x3e\xba\x1d\xc6\xc5\x24\x87\xd1\x29\xbc\xcc\xc9\xef\x86\x33\x96\x32\xd8\x85\x93\x6c\xba\x60\x75\x7a\x8a\x0d\x05\x24\xc2\xbf\x30\xf1\x68\x1e\x11\x96\xe0\xdf\x40\x0f\x4a\xc9\xae\x8b\x38\x8e\xa8\xab\x0f\xf1\xdc\xbd\x24\x67\x96\x40\x3b\xf0\x29\x2d\x29\xd0\x44\x09\x92\xe2\x1a\xcc\x9d\x83\xc0\x54\xaa\xb8\x30\xb1\x19\x38\xf9\x78\x76\xbe\x52\xc5\xbe\xb6\x8e\x4d\x96\x14\x72\x65\xd0\x5a\x10\x75\x2e\x8f\xe6\x46\xd7\x67\xb7\x0f\x82\x25\x96\x91\x04\x71\xf1\x09\x51\x42\x0b\xea\x90\x1b\x7e\xeb\x42\xd7\x0a\x4c\x50\x12\xb9\x26\x05\x37\x3c\x42\xaf\x4c\x28\xad\xf0\x52\x9a\x19\x07\x02\x8b\xbe\x5f\x16\xd3\x66\xe5\x84\xa5\x39\x3e\xa8\x80\xf3\x50\x97\xc6\x77\x18\x1d\x93\x49\x58\xc9\x06\xc9\x14\x13\x34\xe8\xfd\x3f\x0a\x18\xe4\x03\xe5\x7c\x60\x99\x0d\x8f\x15\xdd\x34\x9a\x35\x94\x7e\xad\x03\x47\x12\xfc\x50\x48\x85\x96\x88\xb2\xa0\x3c\x76\x83\x79\xdb\x41\x02\x03\xf2\x82\x29\xa2\xa1\x8f\xb9\x8a\xa4\xa7\x88\x23\x47\x62\x2e\x80\x71\x58\x6d\xaf\xae\x29\xb3\xe5\x51\x2d\x1d\x44\x4d\xeb\x11\x96\x76\x5b\x53\x83\x0d\x53\x37\xdb\xaa\x00\xd3\xb4\x73\x10\xd5\x4b\x94\x23\x0c\x1e\xa3\x13\xac\xf4\x1b\x51\xd8\xe8\x5b\x1f\xef\xb4\x16\xc9\xa5\x38\x73\x28\x39\x4c\xab\x9a\xd4\xea\xa3\x13\x15\xda\x5f\x4e\x2f\x0b\x09\xf1\x3f\x2c\x84\xab\xce\xcc\x87\x1f\x15\xc1\xd9\x28\x3d\x99\x00\xce\x46\xba\x95\xca\x38\xcd\x35\xfe\xa1\x12\x2e\x4b\x79\x86\x1f\x29\xdf\xca\x22\x35\x8f\x57\xba\x06\xe5\x56\xd1\x7e\x4b\x07\xdf\x68\x0d\x24\x59\x51\x68\xd5\x8c\x94\xf9\x05\xdc\x2c\xa0\x34\x98\x50\x1e\x48\x91\x9c\x9c\x37\x37\x8a\xd0\x81\x4f\x91\xf3\x59\xcd\xe0\xb5\xaa\x07\xad\xc5\x8e\xb0\x66\x48\x6c\x5d\x50\x72\x1d\x62\x20\x2e\xa6\x92\x8c\x09\x4e\x6b\x6f\x9a\x4f\x2f\x04\xee\x12\x11\x78\x68\x3e\xac\x1f\x82\x8e\xad\xe1\x27\x17\x0c\xa8\xf0\x25\x02\x02\x41\xc8\x03\x26\x70\x03\xe7\x5e\xff\xb9\x77\xa1\x8f\x28\x8c\x39\xc1\xd4\xf5\xe6\x25\xd4\x65\x71\x58\xd3\x48\xc4\xcb\x66\x97\xe8\x46\x5c\x2e\xc6\x00\x53\x34\xf2\x70\x0d\x6b\x3f\x45\xa1\x61\x09\xcd\x44\xc4\xdd\x0d\xf9\x7a\xf1\x8e\xd0\x89\x1a\x18\x3f\x9e\x1d\xc4\x83\x4e\x09\x12\xd9\x91\xbf\x2c\x90\x8b\x00\xe7\x5d\x54\xb9\x12\x1f\xa4\xbf\x14\x6b\x50\x3c\x22\xea\x7f\x3b\x3f\x4c\xc3\x0d\xca\xab\x4f\x4e\xb5\x23\xee\x95\xa9\x74\x4e\xc7\x8e\x3b\xf0\x1b\xe1\x13\x42\x09\xfa\xd6\xba\x16\x21\xf1\xad\x74\xcc\x7c\x6c\x8c\x42\x4f\x0e\x60\x8c\x3c\x91\x06\xc4\x49\xa2\xfc\x30\xb3\x68\x56\x3d\xed\xd3\xa1\x68\x31\xc6\x8a\x7b\xeb\xef\x0b\x2b\xff\x3e\x2e\x0f\x63\x48\x2a\x41\x35\x3f\x34\x94\x0c\x0b\x25\x0c\x5d\x64\x36\xe6\x49\x15\x75\xba\xc9\x8b\x4c\x6a\x74\x9c\x5f\x12\xa7\x48\xbf\xd0\x6d\x4a\xf3\x6a\x2b\xc3\x84\x3b\x0c\xfd\x0b\x12\x77\x21\x5e\xba\xe9\xd1\x51\x70\xb6\xd3\x7d\xe7\x86\x27\x78\xd3\xeb\x4a\xb6\xfb\xe5\x6c\xd2\x7f\xfd\xfe\xeb\x38\x2c\x8e\xf5\x85\x51\xb5\x76\xb0\x2f\xa0\xb0\xc4\x90\x9f\x77\x1a\x15\xd2\x4a\x08\x69\xdc\xf4\x47\x86\x12\x29\x27\x0c\x0b\xd2\xdf\x4b\x6e\x77\x1b\x9d\x2a\xee\xc2\x16\x34\x04\xb9\x2e\x51\xe6\x85\xbc\x93\x0a\x46\x97\x72\x6a\x66\xb6\xbb\xee\x62\x52\x75\xf4\x1b\xb0\x86\xf6\xec\x27\x1a\xd2\xad\x13\xfe\x25\xf2\x83\xc5\xfb\xd4\xe9\x5e\x02\xa1\x72\x7b\x33\x4b\x5a\xb1\xbb\x29\x4a\x55\xd2\xdb\x65\xe1\xc8\xc3\x35\xce\x41\x03\xb4\x6d\x3a\x9f\x39\xfa\x00\x56\x5d\x9d\x9c\x0a\xdf\xcf\xae\x6d\x24\xfe\xee\x96\x6d\xf3\xa2\x65\x2b\xc3\x1b\x93\xda\x48\x18\x3d\xc5\x42\x0d\x93\x2b\x15\x64\xd8\x10\x1e\x99\x37\x78\xdc\x56\x57\x38\xfd\xb6\x24\xf7\x1c\xbb\xff\x9d\x5c\x5e\xa9\xce\x34\x98\x54\x46\x1f\x6d\x15\xc8\xb8\x07\x05\x47\x8b\xb3\x81\x1c\x7b\x9e\x58\xd3\x7e\xa5\x98\xe9\x95\x9a\xa3\xa9\x08\x4c\x2a\xae\x75\xc2\x70\x74\x60\xee\xa9\x72\x18\x4f\x6a\x3f\xe7\xd2\xdc\x4a\x90\x23\x74\x00\x01\x92\xd3\xbc\xe0\xd3\x6d\x86\xf8\x08\x47\x16\x8f\xf8\xa9\x05\xc6\x2e\x61\x5c\xc0\xce\xc3\x74\x22\xa7\x3a\x32\x27\x3e\x06\x42\xc1\x27\x34\x54\x93\x10\x15\x0e\x9b\x93\xa9\x92\x45\x95\xbd\x4c\xf1\xc2\x4c\x6a\x72\x69\xe5\xdb\x72\xfa\xf2\x16\x52\x6e\x1f\x49\x10\xbd\x95\x5a\x35\xa1\xc4\x0f\xfd\x01\xf4\xd2\x47\x66\x23\x61\x00\x9b\x1b\xfd\x6e\xf4\xb4\x98\xf3\x97\x67\x11\x24\xf6\x17\x41\x8f\xcf\xb4\xe4\x64\x19\x3d\x6d\xca\xc3\xb8\xbd\x4e\xfe\xc6\x0e\xa3\xae\x80\x11\x96\x37\xba\x58\x1e\x92\x08\x92\x83\x80\x0f\xcb\xb1\x8d\x6e\x23\x96\xf5\xba\xbb\xdd\x6a\x9e\xe5\x59\x62\xf1\x2c\x82\x1f\xe5\xd1\x67\x79\x16\x3d\x6c\xc2\xb2\xf8\xba\xc1\x78\x3a\x20\x19\x8c\xb1\x74\xa6\x1d\x78\xa3\xfe\x97\x49\xa7\xbf\x99\x62\x0a\xd8\x0f\xe4\xbc\x63\xfa\xa9\x59\x11\xc1\x42\x1f\xbd\x8c\xa7\x48\x1a\x65\x9a\x24\xb0\x6b\x8c\x44\xa7\x96\xb3\x59\x5f\x56\xf0\x64\xe5\x13\xdd\x98\xcf\x49\xbd\xf5\x34\x97\xd0\x30\xc1\xca\x71\xac\xe5\xc0\x89\xb9\x54\xd6\xc5\xb7\x05\x9d\xb0\xa7\x8e\x0d\xdc\x44\x51\x7e\xf9\x0c\xc7\x48\x76\xf1\x7a\xa5\x9d\xda\x68\x90\xb6\x32\x31\x6b\x91\x3e\x4e\x8b\x95\x2a\x76\x29\x65\xc7\xc8\x99\xda\x44\x7f\x43\x32\xf2\x29\x98\x09\x19\xdd\xae\x21\x24\x2a\x86\x54\xba\x60\xf3\x9f\x76\xd2\xf3\x2c\xba\xb5\x20\xba\x1b\x4f\x75\x52\xf3\x65\x87\x13\x89\x39\x41\x1d\x6d\xc0\x62\x4e\x25\xba\x4d\x56\x5b\x12\x5f\x0f\x44\x58\x08\xf9\xc4\x43\x3c\xde\xfa\xb2\xbb\x60\xb8\x8c\x01\x5f\x82\xe3\xe9\x32\xaf\x6c\x0c\x88\xc2\xd9\xaf\xef\xf5\x6e\x2d\x36\x97\x36\xc7\xb0\x74\x69\x7c\x73\x62\x2d\xaa\xf4\xaa\xfb\x9b\xa9\x3c\xa2\xf3\x18\xec\x98\x79\x1e\xbb\x51\x53\xe7\x4b\x27\xb3\xdf\x29\x2e\x61\x4c\xb0\xe7\x8a\xc1\x4a\x02\xf4\xe7\x4c\x2e\x54\xf1\x71\x94\xed\x64\xbf\xc8\x6c\x4d\x66\x5e\xe8\x45\x8f\xa1\x75\x3a\xe0\x67\x2b\xfb\xc9\x7a\x38\xe5\x78\x6c\xfd\xcc\x74\xc8\xac\x07\x58\xcf\x0b\x09\x89\x3f\xdb\xdb\xaa\xea\xa7\x5d\x1a\x32\x8b\x84\xce\x24\xb3\x7e\x9b\x25\x0f\xeb\x41\x6e\x6f\xfc\x67\x2b\xc3\xca\x7a\x18\x65\x3b\xa5\xcc\xb3\x12\xdd\xd6\xac\xf1\x4e\xb9\xa2\xd4\xcb\x24\x97\x40\xa7\xc2\x92\x53\x4c\xb8\x46\x7f\x0d\xe2\xea\xbe\xa9\xd4\x8c\x92\x58\x32\xba\xbc\xbc\x14\xd7\x69\x9e\xb3\xde\x6e\x44\xc2\xb1\xdf\xa7\x8d\xcf\xef\x82\x06\x0c\x11\x75\x87\xc9\xfe\x9b\xa2\xfd\x3e\x98\xad\x59\x62\xaf\xc6\xd4\x5c\x26\x9e\xb1\x1b\xba\x2a\xe3\x35\x51\x77\x0d\x18\x07\x62\xda\x68\x3b\xd6\xab\x6e\xca\xa9\xaf\xa9\x67\xa9\xf8\xcc\xba\x9c\x9a\x1c\x18\x07\x6f\x51\xa8\x10\xea\x24\xde\x22\xf0\x98\x8b\x33\x03\x68\xd1\x83\xe4\x1c\x04\x58\x4e\x24\xa6\xae\x55\xe1\xf7\x8c\x63\x8c\x00\xdc\xd7\xb7\x09\x39\xf7\xd4\x00\xc9\xb8\x39\xc4\x69\x6a\xb2\x95\xfb\xad\xd4\x6d\xe9\x46\xa9\x9b\xb2\xb4\xa2\xde\x5f\x2d\xf0\x53\x7a\x0f\x37\xeb\xa4\xd2\x6f\x66\x9c\x15\x44\xf5\xba\x23\x47\x93\x5c\x3b\x6a\x10\x53\xd2\xb9\xcc\xfa\x8f\xcb\x35\xb8\x54\x8c\x53\xff\xd7\x66\xaa\xfe\x61\xec\xf3\xd2\x6c\x58\x5f\x1a\xe3\xbc\x4c\x61\xab\xf9\x00\xd2\x97\x99\x19\x81\x5f\xfe\xd7\x7f\xab\x5e\x2f\x2f\xb5\xca\x5c\xbe\x3f\xfa\xd7\xe1\x65\xea\x36\xe3\x5e\x5f\x18\xa1\x51\xfb\xfd\xe3\x83\x4b\x03\xfb\xe3\xe9\x65\x07\xde\xb1\x1b\x3c\xc3\x7c\x0d\xe6\x2c\xd4\xae\x55\x51\x89\x92\xb4\x13\x36\x86\x5e\x37\xea\xae\x0f\xb9\x45\xd4\x68\xd9\x5b\x3c\x3e\x4c\x94\xa9\xcc\x18\x0b\xa6\x18\x95\x60\x88\xf3\x07\x2e\xfd\x79\x3b\xf2\xb9\x06\x37\x6b\xb9\x59\xef\x9a\x34\x35\xc8\xac\x35\xbe\x84\x14\xae\xd9\xff\xcf\xb0\x1f\x5e\x02\xba\x11\x76\xf7\x3f\x83\xf6\xbf\x97\x21\x00\x99\xef\xe8\x54\x0f\x9d\xab\x10\x1d\xb1\xbe\xf4\xe7\x77\x44\xd9\x23\x57\x18\xfc\xf9\x3f\xfa\x5b\x0f\xe2\x37\xb4\x5f\xb4\xf3\x11\x12\xdf\x68\x79\x14\x24\xd3\xca\xc2\x53\x24\x20\xc0\xdc\x27\x42\xdf\xbf\x2e\x19\x08\x6c\xaa\x78\xf0\xe8\x2c\xa4\xa5\x04\xc7\x4c\xe2\x4e\x8c\xa2\x19\xac\xd3\x53\x73\x4a\xa1\xa3\xb3\x4f\x7a\xe1\x3d\xee\x5d\xed\xa0\xa2\x60\x4b\x2b\x5c\x85\xdb\x29\x77\x31\x25\xb1\x51\xc6\x83\x40\xde\xb1\x35\x52\x94\xd6\xdd\x1d\x58\x69\xea\x6f\x3c\x73\x2a\x0e\xf9\x85\xe9\x52\x41\xfb\xf4\x56\x97\x9a\x05\xe8\x39\x44\x66\x14\x18\xcd\x2b\x78\xd5\x00\xef\xa6\xec\xc4\x33\xe4\x0d\x2b\xf3\x99\x63\xd6\xe2\x4c\xe9\x03\xd5\xd8\x45\xdc\x5d\xdc\x2f\x6e\xa9\xfa\xc6\xe7\x78\x75\x4a\x5a\x8c\x42\x74\x90\xd7\xa6\x0b\x0f\x60\xa4\x9f\x46\x0f\xcd\x8f\x37\xd1\xec\xef\x97\x4f\x71\x86\x9a\x21\x7a\x2a\x65\xb0\x92\x27\x2c\x77\x01\x4e\x0c\x3e\xb7\xfa\x14\x25\xff\x40\x2b\xc9\xed\x4f\x49\xcc\x25\xae\x41\xcb\xd2\x9a\x58\xde\xad\x28\x91\x0f\x05\x44\x26\x19\xbb\xb9\x5b\x72\x16\x7d\x1a\x87\xed\x1b\xfc\x8d\x3e\x5d\x9a\xf6\x5c\x81\x80\x5e\x1b\x46\x1b\x68\xcf\xd9\x1a\xed\xb5\xbb\xfd\xdd\x8d\xf6\xe6\x78\xbc\xdb\xde\x1b\xed\xe1\xb6\x8b\xfa\xfd\xee\x9e\x8b\x7a\x3b\xce\x46\xfa\xe5\x68\xe9\x39\xb2\xad\xf4\x79\x6d\x1a\xee\x7a\xa3\x6f\xc0\x0b\x08\x38\x9a\xf8\x68\xa0\xbc\x1a\xbb\xd1\x55\xb6\x04\x76\x38\x96\xb6\xd2\xc9\x50\x0c\xa0\xa5\xcf\xd5\x34\x65\x57\x92\x78\x67\x7b\xa3\x7a\xd1\x9b\xf3\x1a\xd0\x42\x01\x19\x46\x64\x0c\x23\x76\xd7\x88\xc1\x42\xc8\x3e\x94\x01\x2d\xa5\xa0\x62\xb0\x6e\x58\xd3\x6e\xc2\x8e\x4e\xa4\xcc\x1d\xdd\xa5\xe3\x30\xbf\x00\x3c\x3e\xbc\x91\x07\xef\x22\x89\xee\xff\x8d\xf4\xa4\x87\xbe\xb3\xa9\xdb\xee\x75\xdb\xdd\xad\xf3\x5e\x7f\xb0\xd5\x1b\xf4\x37\x3b\xdd\xad\x8d\xde\x66\xff\x8f\xb4\x87\x75\x90\xa3\xd0\x63\x7b\xb0\xb1\xdd\xd9\xd8\xee\xf7\xbb\xbb\x56\x8f\xf8\xc4\x05\xb4\xfa\x9d\xed\x4e\x37\x7d\x91\x75\x29\x89\xab\x29\x51\xf0\x06\xb7\x8b\xff\x75\x94\xde\x9c\x6b\x79\xd6\xfa\xa7\xad\xf5\xd9\xd3\x49\xd0\x42\xd1\x91\xd3\xb2\x0b\x2f\xd2\x9b\x45\xf2\xb2\x6f\x6e\x22\x25\x57\x1b\xe4\x4c\xa2\xc1\x29\x9e\x62\x26\x60\x31\x63\xb0\x24\x2f\xb0\xb0\xac\xd8\xbe\x8b\xfd\xd5\xd9\xe0\x77\xb4\x43\xa8\x19\x80\x16\x9b\x63\x8d\x49\xc2\x02\xb3\x04\xdb\x34\xbd\x8c\x31\x42\xbd\x41\xc2\x43\x1b\x25\x7c\x27\xc3\x84\x3b\x19\x27\xdc\xc9\x40\xa1\x6e\x68\x82\x45\xb6\x57\x92\xb4\xbc\xc0\xec\x8a\x19\xc3\x90\x8c\x53\x25\x51\x5f\xe6\x59\x26\xf5\x0c\x5a\xfb\x3e\xfa\xca\x28\x7c\xc2\xa3\xf8\x6c\x90\xd5\x36\x4e\x0e\x4b\x95\xaf\x98\x81\xdb\x00\x55\x3b\xfd\x35\x41\xb4\x44\x6b\x73\xa8\x5d\x9c\xc1\x21\x12\x72\x0d\xac\x8c\xb6\x3a\xdc\xa0\x2e\x6f\x0c\xfe\x4c\x67\x15\x6b\xd1\xcc\xc4\x6c\x49\x54\xe5\x19\x55\x10\x56\xdc\x2e\x1f\x6a\x84\x87\xc3\x01\xd8\x23\x20\xe6\xc3\x11\x67\x57\x98\x4b\x16\x10\x27\xda\x9b\x19\x8e\xe6\x12\x8b\x21\xa1\xc3\x6c\xf1\x86\xc4\x24\x86\xa6\x3a\x00\xe3\x43\xc2\x86\xd1\x92\x72\x02\xb7\x9d\xbf\x96\x42\x79\xd0\x80\x38\x03\x18\x0e\x1d\x46\x45\xe8\x63\x3e\x64\xe3\xb1\xc0\xd6\xe5\x3a\xc5\x64\x9c\xb6\xb5\x25\x0f\xbd\xed\x5e\x6f\x7b\xa7\xdb\xdf\xe8\x76\xbb\xdd\xac\x42\x9b\xc9\xd8\xee\x66\x6f\x6b\x73\x51\xef\xed\xca\xde\x5b\xbb\xbb\xbb\x8b\x7a\xef\x55\xf6\xde\xd9\xee\xf7\x6d\x21\x95\x24\x8d\xfc\x45\xc4\xb4\x50\x24\x05\x71\x54\x66\x2e\xe4\x38\x51\x99\xa1\xd0\xce\x6f\xfd\x43\x2b\xfb\xa0\x64\xb0\xb2\x36\xfe\xa3\xd6\xe9\x13\xd3\xbc\xfa\x32\xfc\xb2\xb0\xba\xd7\x2d\x46\xcd\xb9\xe2\x34\xa5\x63\xb5\xb9\xaf\x73\x3d\xd3\x5d\x17\xed\x80\x96\x3e\xfc\xd7\xfe\xf0\xf6\xc3\x79\x3b\xf3\x3a\x09\x9f\xce\xac\x1b\x85\xe3\xbb\x86\xcd\x9d\x84\x89\xf7\x30\x4b\x77\xfa\xee\xe1\x97\xfa\x5c\x55\xb2\xdc\x66\x85\xbe\x76\x1d\x0f\x68\xf5\xc8\xa7\x23\xe2\x5f\xbf\x75\xf8\x41\xf8\x7e\xbb\x87\x2e\x6e\x8f\xfe\xb8\x7e\x75\x7e\x7d\x7c\x8a\x12\xc6\x54\xdc\x6d\xfa\xcc\x98\x9a\x1b\xa2\xca\x98\xd3\xbf\x17\x6f\xfa\xb5\xac\xe9\x97\x71\xc6\x4c\x1a\x41\x32\x45\xaf\xc0\x99\x35\xf0\x01\x5c\xe8\xa1\x48\xbd\xd5\x41\x60\xfe\x9e\x33\x53\xca\xae\x38\x41\x1a\x40\xe6\xb3\x03\x58\xf4\x15\xab\x7a\x38\xf3\x42\x9f\x9a\x05\x63\x05\x3d\x5a\xdd\x84\x55\xe2\xae\x76\xe0\xac\xac\x9d\x5e\xfc\x1f\x44\x51\xe3\x5a\xb4\xfd\x96\x0d\x40\xe3\xa7\x26\x64\xed\xc0\xaf\x66\x01\xd7\xc8\x66\x00\xc4\x85\x97\xd0\xb3\xf9\x93\x97\xb4\xf7\xe9\xe0\x6d\x38\x1f\x1d\xf1\x43\x7a\xcb\xf7\xb1\xbf\xd3\xdf\x9c\x5c\x5f\x5d\x91\x83\x59\x22\xe9\x05\x95\xdd\x4a\xa5\xdd\xbb\x97\xb4\x7b\xb5\xd2\xee\x95\x48\xdb\x37\x38\xea\x5c\xa2\x54\xc1\x13\xff\x0e\x76\x05\xc5\xe5\x59\x90\xaf\x39\x56\x46\xf2\xce\x7d\x28\xde\xa9\x23\x78\xa7\x84\xde\x26\x97\x7f\x26\xd8\x67\x6e\xf2\x7f\x3c\x34\x24\x53\xd1\x08\x79\x53\xe8\xd6\x7d\xb9\xda\x23\xff\xda\x70\xc3\xdf\x3e\x1f\xcd\x66\x5b\x9f\x67\xef\xbd\xf9\xd7\x9e\xff\xf6\x74\xe3\x97\xf9\xf5\xf1\x6a\x5a\xad\xae\xc6\x7b\x7d\xfe\xb8\x33\xe9\x4f\xb6\xdf\x9d\xbb\x17\xff\xba\x40\xfd\x2b\xf1\x6e\xb7\x7f\xf5\xeb\xc1\xc6\x3c\x66\x49\xbe\xd0\x5e\xa9\x3b\xbf\x97\x0e\xf7\x6a\x75\xb8\x57\xa6\xc3\xa9\x4f\x9a\x61\x4e\xc6\x73\xf8\xe5\xd3\xb9\xa9\x63\x38\x80\xd3\x68\x91\x3f\xb9\xff\xcc\x2c\x18\x98\x2a\x87\x8d\x58\xb2\x71\x31\x3d\x9c\xde\xf8\xbf\xbf\x0a\x3e\x9d\x8c\x8f\xfa\xde\x31\xbe\x0a\xdc\xcd\x3f\x0e\x62\x96\xe4\x6f\x6f\x2b\x63\xc9\xe6\x7d\x38\xb2\x59\xc7\x90\xcd\x32\x7e\x08\xcc\x61\x75\xcc\x58\x7b\x84\xf8\x6a\xd3\x4b\x88\x3b\x35\x86\xfe\x79\xe3\x82\x1c\x4e\xbf\x52\x8b\x09\x5f\x02\x77\xf3\xf3\xeb\x84\x09\x4d\x2f\x85\x2e\xe3\xce\xd6\x7d\xb8\xb3\x55\xc7\x9d\xad\xc5\xdc\x99\x22\x11\x17\x15\xb0\x76\x82\xd3\x9b\x97\xb7\xcd\x92\x87\x3e\xab\x1c\xed\x1e\x2e\xe4\xd4\xd5\xad\xe2\xd4\x6f\x27\xf8\xa8\xcf\x8e\xf1\x17\x77\xe3\xf7\x57\x09\xa3\x16\x5c\x5a\x5d\x6a\x50\xfd\x7b\x19\x54\xbf\xd6\xa0\xfa\x25\x2c\x4a\x8c\x46\x2a\x64\x61\x8a\x66\x38\x3a\x49\x8e\x29\xc4\x75\xbd\x2a\x99\x70\xf5\xfb\xeb\xaf\x9f\x34\xed\x31\x13\xde\xcf\xde\xec\x7d\xf9\xf0\xeb\xe7\x98\x09\x75\x37\x15\x97\x31\x60\x63\xfb\x3e\x0c\xb0\x7b\x17\x19\x60\xbf\xcd\x78\x58\xe4\x99\x98\x83\x08\x40\x9e\x5e\xd0\xd2\x65\xc2\x2a\xc9\xde\xbe\xfa\xdc\x55\xb2\xff\x9a\xd2\xff\x19\x4f\xdd\x8d\xc3\xc8\x53\x14\x0b\x4b\x96\x91\xba\x77\x1f\x4a\xf7\xea\x08\xdd\x2b\xf5\x9c\x51\x69\xaf\xb8\x52\x67\x8d\x23\xc4\x87\xb1\x18\xb7\x3f\x4f\xa6\xe3\x0f\x7b\x93\xb7\xa7\xe2\xdd\xec\xf0\x53\x42\x5e\xe3\xe1\xf2\x7b\x12\x69\xcd\xeb\x5a\x66\x43\x3e\xae\x2c\x07\x2a\xb2\x17\x58\x0e\xe0\xe3\xeb\x0f\xed\xc3\xdf\xdb\x7b\x83\x68\x21\xd9\x94\x82\x53\xc4\xa4\x6d\xf0\xad\x6c\xa7\x8b\x88\xed\x1e\xb9\xed\x6e\x78\xd4\xf5\xfc\xeb\xee\xf5\xd8\xd9\x11\x44\xa2\x2d\xe1\x7d\x99\xed\xda\x73\x61\x15\xaf\xc6\xa5\x0f\x14\xe5\xbd\xc9\x96\xbb\xbb\x7b\xdd\xf5\xb8\xe3\xce\x36\x27\x3b\xc8\x1b\xed\x08\x6f\x3c\xa1\x5f\x36\xdc\xe9\x48\x7c\xf9\xc7\xff\xfb\xe7\xe1\xef\xe7\xa7\xfb\xf0\xb3\x21\xb6\xa3\xf9\xf2\x32\x3d\x7c\x69\xc1\x26\x02\x56\x37\xbb\x9b\xab\x6b\x9a\x0d\xfa\xe7\xeb\xf7\x17\x67\xe7\x87\xa7\xf1\xd0\xd0\xdd\x5c\xd5\xfb\xfb\x89\x28\xed\x53\x9c\xaa\x7d\x6f\xb2\xc5\xf8\x56\x77\x46\xc2\xee\x0e\xc3\x4a\x50\x53\x7e\xe5\xf4\xb7\xdd\xc9\x58\x7e\xe9\x21\x67\xd5\xe6\xde\xeb\x88\x8e\xd5\x45\x44\x58\xa1\xc6\x4f\x75\x03\xeb\xb9\xf8\xc4\xe7\xdb\x54\x5c\x8f\xfa\xe2\xd8\x7f\xf3\x65\x6b\xf4\x7b\x70\xb0\xf3\x1a\xb5\x56\xfe\x2f\x00\x00\xff\xff\xd4\x33\x65\x8f\x1e\xa3\x00\x00") +var _fleetManagerYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x7b\x53\x1b\xb9\xb2\xf8\xff\x7c\x8a\xfe\x39\xbf\x53\x9c\xdd\xc2\xc6\x36\x6f\xd7\xcd\xad\x22\x81\x24\xec\x49\x08\xcb\x63\xb3\xd9\xad\x53\x46\x9e\x91\x6d\x85\x19\x69\x90\x34\x06\xe7\x9e\xfb\xdd\x6f\x49\x9a\x87\xe6\xe9\x31\x84\x04\x76\xa1\x6a\x6b\xe3\x19\xa9\xa7\xdf\x6a\x49\xad\x16\x0b\x30\x45\x01\x19\xc0\x46\xa7\xdb\xe9\xc2\x0b\xa0\x18\xbb\x20\xa7\x44\x00\x12\x30\x26\x5c\x48\xf0\x08\xc5\x20\x19\x20\xcf\x63\x37\x20\x98\x8f\xe1\xe8\xe0\x50\xa8\x47\x57\x94\xdd\x98\xd6\xaa\x03\x85\x08\x1c\xb8\xcc\x09\x7d\x4c\x65\x67\xe5\x05\xec\x7b\x1e\x60\xea\x06\x8c\x50\x29\xc0\xc5\x63\x42\xb1\x0b\x53\xcc\x31\xdc\x10\xcf\x83\x11\x06\x97\x08\x87\xcd\x30\x47\x23\x0f\xc3\x68\xae\xbe\x04\xa1\xc0\x5c\x74\xe0\x68\x0c\x52\xb7\x55\x1f\x88\xb0\x63\x70\x85\x71\x60\x30\x49\x21\xb7\x02\x4e\x66\x48\xe2\xd6\x1a\x20\x57\xd1\x80\x7d\xd5\x54\x4e\x31\xb4\x7c\x44\xd1\x04\xbb\x6d\x81\xf9\x8c\x38\x58\xb4\x51\x40\xda\x51\xfb\xce\x1c\xf9\x5e\x0b\xc6\xc4\xc3\x2b\x84\x8e\xd9\x60\x05\x40\x12\xe9\xe1\x01\x9c\x62\x17\xde\x21\x09\xfb\xee\x0c\x51\x07\xbb\xf0\xda\x0b\x85\xc4\x1c\xce\xb0\x13\x72\x22\xe7\x70\x66\x00\xc2\x1b\x0f\x63\x09\x1f\xf4\x67\xf8\x0a\xc0\x0c\x73\x41\x18\x1d\x40\xaf\xd3\xef\x74\x57\x00\x5c\x2c\x1c\x4e\x02\xa9\x1f\x2e\x86\xfb\xcf\xd3\x77\xfb\xaf\xcf\x7e\x2a\x87\x6f\x78\x71\x8a\x85\x84\xfd\x93\x23\x45\xa4\xa1\x0f\x08\x15\x52\x01\x14\xc0\xc6\xb0\xff\xfa\x0c\x1c\xe6\x07\x8c\x62\x2a\x45\x67\x45\xd1\x8e\xb9\x50\xe4\xb5\x21\xe4\xde\x00\xa6\x52\x06\x62\xb0\xbe\x8e\x02\xd2\x51\x92\x13\x53\x32\x96\x1d\x87\xf9\x2b\x00\x39\x8c\x3f\x20\x42\xe1\x9f\x01\x67\x6e\xe8\xa8\x27\x3f\x81\x01\x57\x0e\x4c\x48\x34\xc1\x8b\x40\x9e\x49\x34\x21\x74\x52\x0a\x68\xb0\xbe\xee\x31\x07\x79\x53\x26\xe4\x60\xb7\xdb\xed\x16\xbb\x27\xef\xd3\x9e\xeb\xc5\x56\x4e\xc8\x39\xa6\x12\x5c\xe6\x23\x42\x57\x02\x24\xa7\x9a\x03\x0a\xcd\x75\x3e\x45\x8e\x58\x9f\xf5\x06\xba\xdf\x04\x4b\xf3\x0f\x50\x6a\xcc\x91\x02\x70\xe4\x0e\xd4\xf3\xdf\x8c\x34\x3f\x60\x89\x5c\x24\x51\xd4\x8a\x63\x11\x30\x2a\xb0\x88\xbb\x01\xb4\xfa\xdd\x6e\x2b\xfd\x09\xe0\x30\x2a\x31\x95\xf6\x23\x00\x14\x04\x1e\x71\xf4\x07\xd6\xbf\x08\x46\xb3\x6f\x01\x84\x33\xc5\x3e\xca\x3f\x05\xf8\xff\x1c\x8f\x07\xd0\x7a\xb1\x9e\x8a\x75\xdd\xb4\x15\xeb\x39\x14\x5b\x56\xe7\x0c\x43\xa2\x76\xe0\x67\x69\x11\xa1\xef\x23\x3e\x57\xaa\x29\x43\x4e\x85\x36\x9b\x59\xbe\x6d\x9e\x71\xeb\x98\x73\xc6\xc5\xfa\xff\x10\xf7\x7f\x17\x32\xf1\x50\xb5\x7d\x35\x3f\x72\x1f\x23\xfb\x34\x72\x95\x4c\x7b\x8b\x25\x68\x52\x95\x73\x4a\x08\x28\xe5\x59\xd2\x8c\xc4\xcd\x24\x9a\x58\x24\xb6\x4d\x0b\x11\x3d\x08\x10\x47\x3e\x96\x91\x5d\xc6\x4d\xca\x30\x4d\x5b\xae\x13\xb7\x55\x25\x8a\x66\x52\x10\x8f\x56\x04\xef\x89\x90\x95\x62\x50\x2f\x95\x67\x0b\x98\x10\x44\x0d\x15\x19\x56\x96\x8a\xc3\xcb\x77\x51\x0e\x33\xd3\xad\x42\x3c\x05\xfe\x0a\x89\x64\xb8\x98\xbf\x91\xc3\x3e\xd3\xad\x1f\x23\x9b\x33\x08\x56\xb2\xfa\xe3\x55\x8a\xea\x56\x0e\xd5\x4c\xc3\x0b\x8a\x6f\x03\xec\x48\xec\x46\xaa\xcf\x1c\xed\x73\xdd\x47\x61\xc5\xea\x0f\xdf\x22\x3f\xf0\x6c\xe6\xc7\x7f\x5b\xdd\xee\xa1\x79\x59\x7c\x57\xfe\xa1\x18\xd6\x7a\xda\xb5\x55\xa7\x7e\x46\x69\x94\x02\x72\x2c\x58\xc8\x1d\x2c\xd6\x40\x84\xce\x54\x45\x57\x37\x53\xac\x42\x1b\xf0\xd1\x2d\xf1\x43\x1f\xa2\xe0\x04\x1c\x14\x20\x47\x05\x01\x53\x24\x60\x84\x31\x05\x8e\x91\x33\x4d\x58\x2a\xa2\x20\xc1\xd6\xda\x57\x18\x71\xcc\x07\xf0\xe7\xbf\x0b\x8a\xeb\x60\x2a\x39\xf2\x1a\x7a\xe9\xd7\xa6\xb5\xe5\xa7\x33\xe2\x3e\x57\xb1\x5e\xd2\x47\x05\x22\x8c\x7a\x73\x40\xa1\x9c\x32\x4e\xbe\x9a\xe8\x4c\x87\x6e\x40\xa8\x61\x01\xf2\x31\x30\x3e\x41\x94\x08\xd3\x09\x19\xde\xb0\x1b\x8a\x79\xf6\x0d\x1b\x9b\x2e\x01\x76\xc8\x98\xa8\xb8\xc8\x60\xd3\x79\x8c\x86\x14\xe1\x76\x8a\xaf\x43\x9c\x75\x5a\xf5\x5a\x97\xed\xf7\x16\xcb\xd3\x88\xaa\xbb\xea\x62\x16\x60\x4e\x2d\x1b\x7c\xf7\x13\x91\xd3\x37\x88\x78\xd8\x7d\xcd\xb1\xe6\x91\x71\x0e\xdf\x06\x9f\x1a\xc8\x95\xde\x27\x82\x00\xdc\x80\x80\x31\x0b\xa9\xab\xc7\xde\x83\x54\xf0\x9b\xdd\xde\x23\x89\x15\xea\xe5\xbd\xd9\xed\xdd\x95\x93\x69\xd7\x4a\x56\xed\x87\x72\x0a\x92\x5d\x61\x6d\x8c\x84\xce\x90\x47\x5c\x9b\x49\x1b\x4f\x84\x49\x1b\x77\x67\xd2\xc6\x22\x26\x5d\x08\xcc\x81\x32\x99\xf3\x53\xc8\x71\xb0\x88\x1c\xb5\xf1\xbd\x36\xe3\x36\x9f\x08\xe3\x36\xef\xce\xb8\xcd\x45\x8c\x3b\x66\x05\x5b\xbc\x21\x72\x6a\x79\xe8\xa3\x03\xc0\xb7\x44\x48\x51\x1d\x2f\x3c\x56\xd6\x7d\xd3\xe1\xbf\xc0\xba\x45\x81\xd1\xc2\x51\x1c\xca\x82\x0a\x54\x90\x47\xea\x15\x5d\xec\x61\x89\x4b\x07\x76\xf3\x6a\xc1\xd8\xfe\x9f\x04\x93\x73\x35\x3c\xab\x71\xdd\x8c\xe4\x96\xd5\x8c\x19\x37\xeb\x3d\x69\x0c\x80\xb8\xc5\xbf\xde\x4f\xba\x33\x72\x7d\x42\x89\x90\x1c\x49\x45\xf9\xf8\xae\x03\x3e\x40\xdf\x00\x34\x7d\x15\x3a\x6b\x80\xa8\x6b\xb0\x23\x63\x20\x52\x2f\x86\x78\x82\xa9\xa9\x94\xbc\xc7\xa7\xca\x67\x62\x84\x0e\xe0\x3a\xc4\x7c\x6e\x89\x99\x22\x1f\x0f\x00\x89\x39\x75\xaa\x84\x7f\x82\xf9\x98\x71\x5f\x7f\x11\x39\x26\x54\xa2\x80\xa8\xe9\x35\xe5\x8c\xb2\x50\x80\x8f\x28\xd5\x2b\x1f\x75\x4a\x2f\xe7\x01\x1e\xc0\x88\x31\x0f\x23\x6a\xbd\x51\xf2\x27\x1c\xbb\x03\x90\x3c\xc4\xb5\x01\x52\xbf\x3a\x7c\x3f\xd0\x8a\x91\x19\x30\x9e\x86\xf1\x6e\x76\xbb\x1a\x77\xc2\xe8\xdd\xfd\x5f\x1e\x44\xf5\xaa\x89\x1a\x55\x8d\x1e\x99\xf9\x61\x71\x9a\xf3\x1c\x8f\x3c\xc7\x23\xcf\xf1\x88\x8a\x47\x8c\x4f\xb9\x47\x54\x92\x01\xf0\xb7\x8d\x4d\xee\xc7\xc6\x3c\x80\xbb\xc7\x29\x71\x08\x62\xc0\xd5\x87\x20\x8d\xc2\x9a\xe2\x48\xdb\x68\xc5\xb3\x6a\x5d\xc3\x00\x09\x98\x28\x5f\xd3\x70\xd4\xcc\x33\x0e\x7d\xca\xc2\x9e\x43\xe4\x4c\x21\x02\xa6\x97\x5c\x10\x08\x42\x27\x5e\x69\x14\xa1\x62\x8f\xdc\x7b\x15\x94\x74\x40\x4f\x70\xb1\xd9\xa3\xba\x49\x38\x24\xa7\x48\x07\x28\xaa\xa5\x9e\xc0\x2a\xdb\x56\x1d\x4c\x10\x93\x81\x1c\xca\x29\xa6\x52\xe9\x5d\x12\x67\xe1\x98\xc5\x7f\xb1\x20\x45\xd3\xf4\x8a\xb9\x96\x96\x94\xce\xff\xad\x0d\x8a\x52\x53\xad\x37\xd4\x72\x33\x6d\xbe\xa4\x73\x82\xe6\x1e\x43\x6e\xd6\x68\xab\x4c\xf6\xe2\xec\x14\x4f\x48\xd1\x57\x2c\x30\xd3\xb8\x5b\xc5\xaa\xcd\xe1\xc5\x9d\xa0\xc6\xdd\x0a\x50\xef\x1c\x34\x3e\xb1\x55\xb5\x13\x26\x1e\x7e\x59\x2d\x1b\xf7\x38\x0e\x0e\x9e\x6a\x24\x1d\xaf\xce\xdd\x23\x92\xce\x81\x78\x8e\xa4\x9f\x23\xe9\x98\x49\xdf\x38\x92\x4e\xc0\x7e\x40\xb7\xfb\x9e\xc7\x6e\xb0\x7b\x14\xe5\x3d\x9c\x9a\x7d\x92\x7b\x7c\x6f\x11\xcc\x52\x44\xce\x31\xf7\xc5\x31\x93\xb1\x0f\xb8\xc7\xf7\x2b\x40\xd5\xcf\x24\xc6\x8c\x8f\x88\xeb\x62\x0a\x98\xe8\x1d\xa5\x11\x76\x50\x28\x70\x1a\x6d\x10\xd1\x68\xba\x01\x2c\xdb\x37\xde\x99\xa2\xa1\x3f\xc2\x7a\x1d\x27\xcd\x30\xd1\xa1\x8d\x83\x28\x8c\x70\x14\x63\x45\x01\x0e\x11\xe6\x9b\xf9\xdd\xab\xce\x93\x9c\xcc\x3c\xe0\xe2\xea\x79\x1a\xdf\x61\x37\xd9\x20\x04\x97\x61\x41\x57\xa5\x99\xba\xd8\x3c\xdb\x7b\x22\x3c\xdb\x3b\x46\x3e\x7e\xcd\xe8\xd8\x23\x8e\xbc\x3b\xff\xca\xc0\x54\x3b\x4b\xc5\x0f\xdd\x32\xd5\x3b\x17\x4b\x33\xaf\x89\x76\x22\x9d\x68\x88\x32\x4b\x81\x44\x24\x2c\x7f\x92\xd3\xc3\x07\x5c\xba\xde\xa7\x10\x56\xcd\x0a\xe1\x66\x4a\xbc\x98\x97\x74\xa2\x19\x9b\x9b\x0f\x36\x9f\x09\x5a\xb3\xcb\x74\xfe\x54\x06\xcd\xda\xb0\x2e\x59\x12\x8f\x93\x3c\x72\x3d\x45\xd9\x6c\xef\x23\xf5\xe6\xc0\x93\x2d\x7a\x26\x70\x3c\xf7\x8b\x5c\x1a\xe2\x38\x3b\x5d\x2b\x5b\x45\x36\x53\xb8\x26\x33\xb6\x8a\xfd\x75\xb1\x0c\x93\x9a\x6c\x7b\xe7\xac\x61\x01\x4b\xe0\xc7\x85\xf4\xf9\x0c\x1f\x58\x22\xac\x57\x7d\xbf\x4d\x38\x6f\x41\x6a\x55\x87\xec\x19\xa6\xbe\x42\x6e\x4e\xc3\xbf\x2b\x17\x97\xf4\x10\x47\x26\x5e\xfc\x35\xc4\x7c\x7e\x8f\xb0\xbe\x04\x4c\xab\x3a\x50\x5f\x22\x7e\x7d\xbc\x9c\xfb\xc6\x51\xfd\xdf\x37\x50\xbf\xef\x92\xf7\x73\xde\x59\x93\xd1\xfb\x4e\x19\xa4\x01\x9a\x58\xa2\x5a\xd8\x5c\x90\xaf\xcb\x34\x67\xdc\xc5\xfc\xd5\x7c\x99\x0f\x60\xc4\x9d\x69\xc9\x1a\xaf\xc7\x42\x77\x18\x70\x36\x23\x2e\x2e\xc9\x6e\xad\xcd\xf9\x14\x61\x10\x30\xae\x34\x44\x83\x81\x04\x4c\xd5\xd0\xac\x5a\x9d\xe4\x1a\x3d\xcc\x00\x6d\xd0\xc5\x6e\x63\x5c\xe1\xbb\x0e\xd8\x36\x23\xb2\xe3\xf5\xb3\xcb\x6f\xe2\xf2\x9f\x3d\xd7\x63\xf3\x5c\xb5\x6e\x45\x67\xc6\xae\x73\xbd\x64\x7e\x67\x1f\x13\x75\x4f\xf2\x4c\x2a\x0c\xba\x89\xef\x31\x8b\xf7\x8f\xc4\x03\xc5\x84\xfd\x30\x47\x64\xb8\xf1\xec\x86\x9e\xdd\x50\xf2\xf7\xe3\xdd\x10\x71\x97\x70\x42\x0f\x1b\x6d\xc5\x4b\xb2\x43\x39\x0f\x2a\x7d\x1d\x72\x1c\x16\x52\xb9\xa4\x77\x33\x9e\x20\xee\x0b\x37\x53\xe2\x4c\x61\x84\x3d\x46\x27\x71\x9e\xff\xaa\x88\x16\x48\xbe\x6a\x8d\xa8\x73\x6f\xfb\x11\x9c\x26\x7e\x0d\xfe\x06\x8e\x2d\xe6\xc7\xb3\x6b\x7b\x76\x6d\xc9\xdf\x37\x73\x6d\x2f\xd4\x7f\x70\x3e\xc5\x02\xeb\x35\xcd\x78\xd9\xb2\x3d\x46\x0e\xa1\x13\xe0\xd8\xd3\xeb\x96\xc9\x01\xea\xa8\x4f\xcd\xe1\xa1\x75\x1f\x4b\x4e\x1c\xb1\xae\x93\x4d\x86\x1c\xd1\x09\x5e\xec\x50\xa2\x4e\x26\x39\x4b\x12\x1f\x0b\xcc\x09\x16\xa0\xbb\x9b\xbc\x15\x18\xcd\x93\x95\xca\x24\x93\x28\xef\x43\x3e\x18\x38\xaf\xe6\xa7\xaa\xe3\xaf\x56\xbe\xcb\x03\x47\x48\xbf\x9c\x7d\x3c\x06\xc4\x39\x9a\x2b\x77\x72\xc2\x99\x8f\xe5\x14\x87\x29\x65\x6c\xf4\x05\x3b\x52\xc0\x98\x33\x1f\xd8\x48\x60\x3e\x43\x92\x71\x12\xfa\x3f\x42\xe1\x22\x3e\xa5\x5c\x7a\xf6\x2f\xcf\xfe\x25\xf9\x7b\x62\xa1\x93\x1b\x1a\x1f\xb0\x54\x3c\x24\x95\x01\x7a\x4b\x74\x19\x13\x4f\xfd\xbf\x3a\xc7\xb0\xc4\xfd\x2d\xe9\xf8\x4c\x94\x26\xef\xe2\xef\x4c\x26\x82\x7c\xf6\x78\x0b\x3c\x9e\xcd\xa7\x67\x9f\xf7\xec\xf3\x92\xbf\x27\xe6\xf3\x96\xf4\x46\x63\xec\x2a\xc7\xd1\x20\x12\x43\x9e\x97\x58\x30\xa1\x20\x1c\x8e\x02\xac\x6b\xee\x8c\x19\xf7\x91\xd4\x7b\xd8\x08\x26\x64\x86\xe9\x02\xff\x14\x7f\x34\x32\xbd\xef\xe3\x96\x62\x94\x2c\x1a\x90\xed\x9d\x24\xbe\x95\x11\x29\x8b\xb4\x52\x35\x5d\x0f\x3c\x44\x1a\xeb\xa3\x49\x71\x16\x92\x13\x3a\xb1\x1d\xcb\x5f\x66\xc7\xf8\x03\x11\x82\xd0\xc9\x49\xac\x88\xf7\xd8\x35\xae\x00\xf5\xec\x90\x97\xdd\x39\xde\xac\x66\x52\x6a\x9f\x7a\x5b\x55\x1f\x91\x7f\x12\x3c\xfa\xa6\x29\x75\xcf\x83\xd6\xc3\x0e\x5a\x2b\xe9\x2b\xd5\x33\xa2\xc5\x00\xf9\xa8\x83\xc0\x53\x3c\xc6\x1c\x53\x27\x41\xd3\x38\x4a\x13\x21\xc6\x9f\xe7\x6a\xf0\x90\xc4\xa6\x93\xb8\x36\x5d\xa5\xde\xf5\x8a\xd0\xc5\x8d\xa6\x8a\x88\xba\x46\x2a\x14\x1c\x24\x63\x4f\x74\xfe\xc1\xe2\x82\xfa\x8a\xf5\x33\x40\x13\x6c\xfd\x14\xe4\xab\xfd\x53\x32\x99\x9c\x22\xd2\x47\x6f\x24\xf6\xc5\x72\x84\x37\xa2\x4a\x61\x51\x6c\xa4\xa6\x36\x13\xeb\x30\x8e\x42\x6e\x71\x2b\x8d\x73\x7d\x33\xad\x9b\x71\x13\xe4\x79\x1f\xc7\x8b\xf4\x24\xd6\xea\x9c\x12\xd8\x61\x4e\x09\x3f\xaa\x78\x02\xda\x10\xdd\x82\xaa\x97\xf2\x06\xb4\x20\x51\x89\x59\x56\x36\x4f\x62\x97\x61\x56\xed\x4a\x3b\x25\x85\xa8\xee\xc4\x90\xec\xcc\x63\x69\x2e\x68\x85\x2a\x47\x51\x4f\xc8\x72\x6f\x4a\x9b\x37\xf6\x43\xd9\x9c\xb8\xef\x24\xff\xa2\x0d\x9a\xe6\x7e\xe8\x49\x32\x44\x5f\x1b\x30\xc9\xae\xc5\x95\xfe\x65\x5c\x7f\xeb\x37\xe4\x85\x58\x0c\xe0\x4f\x14\xa5\xd2\xaf\x41\xc0\x71\x80\x94\x98\xd7\xcc\x26\x80\x20\x8c\xea\x5f\x1c\x23\x77\xbe\x06\x63\x5d\xaa\x66\x0d\x5c\x9c\xbc\x5e\x33\xa5\x13\x08\x9d\xfc\x1b\xf2\xbe\xbb\x52\xdb\xb2\xdb\xcb\xf5\x68\x1e\xeb\xf2\x48\x63\xd0\x3b\x02\x10\x0a\x93\x16\xe5\xe2\xc0\x63\xf3\x0e\xbc\x61\x3c\x1e\x22\x60\xff\xd3\xd9\x92\x18\x44\x1b\x37\x25\x3a\x9f\xc5\xc1\x7c\x3b\xda\x8e\x80\xa3\x83\xc6\x9f\x89\x45\x56\xae\xaf\xc5\xe3\x80\x10\xed\xb9\x34\x91\x5c\x52\x1c\xd4\xda\x58\x8f\x8e\xf6\x3a\xb9\x9d\x9c\x0c\x9f\x06\x10\x8a\x36\x46\x42\xb6\x7b\x7a\x3e\xb3\x0c\xdb\xf4\x81\xce\xc6\x4e\x45\x1f\xb1\x6c\x2c\x11\x63\x6a\x17\x47\x17\xa7\xef\x97\xed\x74\x80\x24\x5a\xaa\x9b\x39\x8e\x31\x44\x32\xdf\xc5\x4c\x8e\x06\xe0\x22\x89\xdb\x92\xf8\xb8\x29\xc8\x30\x70\xbf\x35\x48\x63\x6d\xc3\x25\x3d\x79\x5c\x58\xb5\x69\xfb\xcc\xee\x68\xe3\x5e\x92\x23\x22\xef\xef\x86\x2b\xe0\xe3\xb2\xb8\xee\xae\x67\x11\x8b\x79\xcd\x0f\x39\x6c\x95\xa2\xae\x23\x1a\x68\x2d\xca\xf3\xd6\x31\x0d\xb4\x7a\xd9\xa7\x3a\x86\x29\x3c\x35\x31\x4b\xe1\xb1\xe2\x73\x9e\xcd\xf7\x3b\xc4\xf9\xb0\xe3\x70\x4e\x04\xe9\x5f\xbd\x30\xca\x0e\xc1\xe6\x0a\xbc\x7e\xa7\xc1\xba\x4e\xde\xfb\x27\x47\x11\x52\x39\x21\xa9\x97\xb3\x9c\xe4\xa6\x06\xad\xcc\x82\x56\x2b\x17\xff\x79\x1e\xd6\xc7\xd0\x0b\x8c\x6c\x1b\x98\xf1\xfa\x57\xf1\x10\x5e\x19\xf4\xf5\xea\xe6\x59\x85\xcd\x6b\x6a\x75\x80\x5a\x89\xe0\xf7\x52\x8c\x52\x01\x66\x2a\x7a\xc6\x30\xb3\xa5\x96\x75\xf7\xe4\x88\x4a\x7c\xda\x2f\xaa\x4e\x19\x2f\xc8\xc1\x88\xb9\x31\xfa\x05\xb9\x67\xab\x2d\x98\x3f\x1f\xdd\x0e\xe3\x62\x95\xc3\xe8\x94\x5f\xe6\x64\x79\xc3\x19\x51\x19\xec\xc2\x49\x39\x5d\x10\x3b\x3d\x25\x87\x02\x12\xe1\x5f\x98\xd8\x34\x8f\x38\x4b\xf0\x6f\xa0\x07\xa5\x64\xd7\x45\x34\x47\xd4\xd5\x87\x84\xee\x5e\xf2\x33\x4b\xa0\x1d\x58\x95\x96\x2c\x68\xa2\x04\x49\xf1\x0e\xe6\xce\x41\x60\x2a\x55\xdc\x99\xd8\x0c\x9c\x7c\x3c\x3b\x5f\xa9\x62\x5f\x5b\xc7\x3e\x4b\x0a\xb9\x32\x28\x2e\x88\x3a\x97\xa7\x73\xa3\xeb\xbf\xdb\x07\xcd\x12\xcb\x48\x82\xc4\xf8\x04\x2a\xa1\x05\x75\xc8\x0d\xbf\x75\xa1\x71\x05\x26\x28\x89\x8c\x93\x82\x1e\x1e\xa1\x57\x26\x54\x57\x78\x29\xcd\x8c\x03\x8d\x45\xdf\x2f\x8b\x99\xb3\x72\xc2\xd2\x1c\x4f\x54\xc0\x79\xa8\x4b\xef\x3b\x8c\x8e\xc9\x24\xac\x64\x83\x64\x8a\x09\x1a\xf4\xfe\x1f\x05\x0c\xf2\x81\x78\x3e\x70\xcd\x86\xdf\x8a\x6e\x1a\xcd\x4a\x4a\xbf\xd6\x81\x23\x09\x7e\x28\xa4\x42\x4b\x44\x59\x56\x1e\xbb\xc1\xbc\xed\x20\x81\x01\x79\xc1\x14\xd1\xd0\xc7\x5c\x45\xea\x53\xc4\x91\x23\x31\x17\xc0\x38\xac\xb6\x57\xd7\x94\xd9\xf2\xa8\x56\x0f\xa2\xa6\xf5\x08\x4b\xbb\xad\xa9\xf1\x86\xa9\x9b\x6d\x55\x80\x69\xda\x39\x88\xea\x25\xd0\x11\x06\x8f\xd1\x09\x56\xfa\x8d\x28\x6c\xf4\xad\x8f\x77\x5a\x8b\xe4\x52\x9c\x99\x94\x1c\xd6\x55\x4d\x6a\xf5\xd1\x89\x0a\xf9\x2f\xa7\x97\x85\x84\xfb\x1f\x16\xc2\x55\x67\xfe\xc3\x8f\x8a\xe0\x6c\x94\x9e\x4c\x00\x67\x23\xdd\x4a\x65\x9c\xe6\x32\xff\x50\x09\x97\xa5\x54\xc3\x8f\x94\x6f\x65\x11\x9c\xc7\x2b\x5d\x83\x72\xab\x68\xbf\xa5\x83\x6f\xb4\xc6\x92\xac\x58\xb4\x6a\x46\xca\xfc\x02\x71\x16\x50\x1a\x4c\x28\x0f\xa4\x48\x4e\xce\xb3\x1b\x45\xe8\xc0\xa7\xc8\xf9\xac\x66\xf0\x5a\xd5\x83\xd6\x62\x47\x58\x33\x24\xb6\x2e\x28\xb9\x0e\x31\x10\x17\x53\x49\xc6\x04\xa7\xb5\x3d\xcd\xa7\x17\x02\x77\x89\x08\x3c\x34\x1f\xd6\x0f\x41\xc7\xd6\xf0\x93\x0b\x06\x54\xf8\x12\x01\x81\x20\xe4\x01\x13\xb8\x81\x73\xaf\xff\xdc\xbb\xd0\x47\x14\xc6\x9c\x60\xea\x7a\xf3\x12\xea\xb2\x38\xac\x69\x24\xe2\x65\xb9\x4b\x74\x23\x2e\x17\x63\x80\x29\x1a\x79\xb8\x86\xb5\x9f\xa2\xd0\xb0\x84\x66\x22\xe2\xee\x86\x7c\xbd\x38\x48\xe8\x44\x0d\x8c\x1f\xcf\x0e\xe2\x41\xa7\x04\x89\xec\xc8\x5f\x16\xc8\x45\x80\xf3\x2e\xaa\x5c\x89\x0f\xd2\x5f\x8a\x35\x28\x1e\x11\xf5\xbf\x9d\x1f\xa6\xe1\x06\xe5\xd5\x27\xa7\xda\x11\xf7\xca\x54\x3a\xa7\x63\xc7\x1d\xf8\x8d\xf0\x09\xa1\x04\x7d\x6b\x5d\x8b\x90\xf8\x56\x3a\x66\x3e\x36\x46\xa1\x27\x07\x30\x46\x9e\x48\x03\xe2\x24\x11\x7f\x98\x59\x94\xab\x9e\xf6\xe9\x50\xb4\x18\x63\xc5\xbd\xf5\xf7\x85\x95\xdf\x1f\x97\x9f\x31\x24\x95\xa0\x9a\x1f\x1a\x4a\x86\x85\x12\x86\x2e\x32\x1b\xf3\xa4\x8a\x3a\xdd\xe4\x45\x26\xf5\x3a\xce\x5f\x89\x53\xb0\x5f\xe8\x36\xa5\x79\xbb\x95\x61\xc2\x1d\x86\xfe\x05\x89\xc1\x10\x2f\xdd\xf4\xe8\x28\x38\xdb\xe9\xbe\x73\xc3\x13\xbc\xe9\x75\x25\xdb\xfd\x72\x36\xe9\xbf\x7e\xff\x75\x1c\x16\xc7\xfa\xc2\xa8\x5a\x3b\xd8\x17\x50\x58\x62\xc8\xcf\x3b\x8d\x0a\x69\x25\x84\x34\x6e\xfa\x23\x43\x89\x94\x13\x86\x05\xe9\xef\x25\xb7\xd3\x8d\x4e\x15\x77\x79\x0b\x1a\x82\x5c\x97\x28\xf3\x42\xde\x49\x05\xa3\x4b\x39\x35\x33\xdb\x69\x77\x31\xa9\x3a\xfa\x0d\x58\x43\x7b\xf6\x13\x0d\xe9\xd6\x07\x0a\x24\xf2\x83\xc5\xfb\xe0\xe9\x5e\x05\xa1\x72\x7b\x33\x4b\x5a\xb1\xbb\x29\x7a\x55\xd2\xdb\x65\xe1\xc8\xc3\x35\xce\x41\x03\xb4\x6d\x3a\x9f\x99\xfa\x00\x56\x5d\x9d\xfc\x0a\xdf\xcf\xae\x6d\x24\xfe\xee\x96\x6d\xf3\xa2\x65\x2b\xc3\x1b\x93\x3a\x49\x18\x3d\xc5\x42\x0d\x93\x2b\x15\x64\xd8\x10\x1e\x99\x37\x78\xdc\x56\x57\x38\x5d\xb7\x24\xf7\x1c\xbb\xff\x9d\x5c\x5e\xa9\xce\x34\x98\x54\x46\x1f\x6d\x15\xc8\xb8\x07\x05\x47\x8b\xb3\x8d\x1c\x7b\x9e\x58\xd3\x7e\xa5\x98\x49\x96\x9a\xa3\xa9\x38\x4c\x2a\xae\x8d\xc2\x70\x74\x60\xee\xc1\x72\x18\x4f\x6a\x4b\xe7\xd2\xe8\x4a\x90\x23\x74\x00\x01\x92\xd3\xbc\xe0\xd3\x6d\x86\xf8\x88\x48\x16\x8f\xf8\xa9\x05\xc6\x2e\x91\x5c\xc0\xce\xc3\x74\x22\xa7\x3a\x32\x27\x3e\x06\x42\xc1\x27\x34\x54\x93\x10\x15\x0e\x9b\x93\xaf\x92\x45\x95\xc3\x4c\x71\xc4\x4c\xea\x73\x69\x65\xdd\x72\xfa\xf2\x16\x52\x6e\x1f\x49\x10\xbd\x95\x5a\x35\xa1\xc4\x0f\xfd\x01\xf4\xd2\x47\x66\x23\x61\x00\x9b\x1b\xfd\x6e\xf4\xb4\x98\x53\x98\x67\x11\x24\xf6\x17\x41\x8f\xcf\xcc\xe4\x64\x19\x3d\x6d\xca\xc3\xb8\xbd\x4e\x2e\xc7\x0e\xa3\xae\x80\x11\x96\x37\xba\x18\x1f\x92\x08\x92\x83\x86\x0f\xcb\xb1\x8d\x6e\x23\x96\xf5\xba\xbb\xdd\x6a\x9e\xe5\x59\x62\xf1\x2c\x82\x1f\xe5\xe9\x67\x79\x16\x3d\x6c\xc2\xb2\xf8\x3a\xc3\x78\x3a\x20\x19\x8c\xb1\x74\xa6\x1d\x78\xa3\xfe\x97\x49\xd7\xbf\x99\x62\x0a\xd8\x0f\xe4\xbc\x63\xfa\xa9\x59\x11\xc1\x42\x1f\xed\x8c\xa7\x48\x1a\x65\x9a\x24\xc8\x6b\x8c\x44\xa7\x96\xb3\x59\x5f\x56\xf0\x64\xe5\x13\xdd\x98\xcf\x49\x3d\xf7\x34\x57\xd1\x30\xc1\xca\xa1\xac\xe5\xc0\x89\xb9\xb4\xd6\xc5\xb7\x05\x9d\xb0\xa7\x8e\x0d\xdc\x44\x51\x7e\xf9\x0c\xca\x48\x76\xf1\x7a\xa5\x9d\x3a\x69\x90\xb6\x32\x3d\x6b\x91\x3e\x4e\x8b\xa1\x2a\x76\x29\x65\xc7\xc8\x99\xda\x44\x7f\x43\x32\xf2\x29\x9e\x09\x19\xdd\xae\x21\x24\x2a\xb6\x54\xba\x60\xf3\x9f\x76\xd2\xf3\x2c\xba\x15\x21\xba\x7b\x4f\x75\x52\xf3\x65\x87\x13\x89\x39\x41\x1d\x6d\xc0\x62\x4e\x25\xba\x4d\x56\x5b\x12\x5f\x0f\x44\x58\x08\xf9\xc4\x43\x3c\xde\xfa\xb2\xbb\x60\xb8\x8c\x01\x5f\x82\xe3\xe9\x32\xb2\x6c\x0c\x88\xc2\xd9\xaf\xef\xf5\x6e\x2d\x36\x97\x42\xc7\xb0\x74\xe9\x7d\x73\x22\x2e\xaa\x24\xab\xfb\x9b\xa9\x3c\xa2\xf3\x18\xec\x98\x79\x1e\xbb\x51\x53\xe7\x4b\x27\xb3\xdf\x29\x2e\x61\x4c\xb0\xe7\x8a\xc1\x4a\x02\xf4\xe7\x4c\xae\x55\xf1\x71\x94\x4d\x65\xbf\xc8\x6c\x4d\x66\x5e\xe8\x45\x8f\xa1\x75\xfa\xe0\x67\x2b\xbb\xca\x7a\x38\xe5\x78\x6c\xfd\xcc\x74\xc8\xac\x07\x58\xcf\x0b\x09\x8f\x3f\xdb\xdb\xaa\xea\xa7\x5d\x7a\x32\x8b\x84\xce\x54\xb3\x7e\x9b\x25\x0f\xeb\x41\x6e\x6f\xfc\x67\x2b\x83\xcb\x7a\x18\x65\x53\xa5\xcc\xb3\x12\xe9\xd6\xac\xf1\x4e\xb9\xa2\xd4\xcb\x24\x97\x4c\xa7\xc2\x92\x53\x4c\xb8\x46\x7f\x0d\xe2\xea\xc1\xa9\xd4\x8c\x92\x58\x32\xba\xbc\xbc\x14\xd7\x69\x1e\xb5\xde\x6e\x44\xc2\xb1\xdf\xa7\x8d\xcf\xef\x82\x06\x0c\x11\x75\x87\xc9\xfe\x9b\xa2\xfd\x3e\x98\xad\x59\x62\xaf\xc6\xd4\x5c\x56\x9e\xb1\x1b\xba\x2a\xe3\x35\x51\x77\x0d\x18\x07\x62\xda\x68\x3b\xd6\xab\x6e\xca\xa9\xaf\xa9\x67\xa9\xf8\xcc\xba\x9c\x9a\x1c\x18\x07\x6f\x51\xa8\x10\xea\x24\xde\x22\xf0\x98\x8b\x33\x03\x68\xd1\x83\xe4\x1c\x04\x58\x4e\x24\xa6\xae\x55\xe1\xf7\x8c\x63\x8c\x00\xdc\xd7\xb7\x09\x39\xf7\xd4\x00\xc9\xb8\x39\x24\x6a\x6a\xbe\x95\xfb\xad\xd4\x6d\xe9\x46\xa9\x9b\xb2\xb4\xa2\xde\x5f\x2d\xf0\x53\x7a\x0f\x37\xeb\xa4\xd2\x6f\x66\x9c\x15\x44\xf5\xc0\x23\x47\x93\x5c\x6b\x6a\x10\x53\xd2\xb9\xcc\xfa\x8f\xcb\x35\xb8\x54\x8c\x53\xff\xd7\x66\xaa\xfe\x61\xec\xf3\xd2\x6c\x58\x5f\x1a\xe3\xbc\x4c\x61\xab\xf9\x00\xd2\x97\xa5\x19\x81\x5f\xfe\xd7\x7f\xab\x5e\x2f\x2f\xb5\xca\x5c\xbe\x3f\xfa\xd7\xe1\x65\xea\x36\xe3\x5e\x5f\x18\xa1\x51\xfb\xfd\xe3\x83\x4b\x03\xfb\xe3\xe9\x65\x07\xde\xb1\x1b\x3c\xc3\x7c\x0d\xe6\x2c\xd4\xae\x55\x51\x89\x92\xb4\x13\x36\x86\x5e\x37\xea\xae\x0f\xd1\x45\xd4\x68\xd9\x5b\x3c\x3e\x4c\x94\xa9\xcc\x18\x0b\xa6\x18\x95\x78\x88\xf3\x07\x2e\xfd\x79\x3b\xf2\xb9\x06\x37\x6b\xb9\x59\xef\x9a\x34\x35\xc8\xac\x35\xbe\x84\x14\xae\xd9\xff\xcf\xb0\x1f\x5e\x02\xba\x11\x76\xf7\x3f\x83\xf6\xbf\x97\x21\x00\x99\xef\xe8\x54\x0f\x9d\xab\x10\x1d\xe1\xbe\xf4\xe7\x77\x44\xd9\x23\x57\x18\xfc\xf9\x3f\xfa\x5b\x0f\xe2\x37\xb4\x5f\xb4\xf3\x11\x12\xdf\x68\x79\x14\x24\xd3\xca\xc5\x53\x24\x20\xc0\xdc\x27\x42\xdf\xef\x2e\x19\x08\x6c\xaa\x84\xf0\xe8\xac\xa5\xa5\x04\xc7\x4c\xe2\x4e\x8c\xa2\x19\xac\xd3\x53\x79\x4a\xa1\xa3\xb3\x55\x7a\xe1\x3d\xee\x5d\xed\xa0\xa2\x60\x4b\x2b\x5c\x85\xdb\x29\x77\x31\x25\xb1\x51\xc6\x83\x40\xde\xb1\x35\x52\x94\xd6\xdd\x1d\x58\x69\x6a\x71\x3c\x73\x2a\x0e\xf9\x85\xe9\x52\x41\xfb\xf4\x56\x97\x9a\x05\xe8\x39\x44\x66\x14\x18\xcd\x2b\x78\xd5\x00\xef\xa6\xec\xc4\x33\xe4\x0d\x2b\xf3\xa5\x63\xd6\xe2\x4c\x69\x05\xd5\xd8\x45\xdc\x5d\xdc\x2f\x6e\xa9\xfa\xc6\xe7\x84\x75\x4a\x5a\x8c\x42\x74\x50\xd8\xa6\x0b\x0f\x60\xa4\x9f\x46\x0f\xcd\x8f\x37\xd1\xec\xef\x97\x4f\x71\x86\x9a\x21\x7a\x2a\x65\xb0\x92\x27\x2c\x77\xc1\x4e\x0c\x3e\xb7\xfa\x14\x25\xff\x40\x2b\x39\x3b\x90\x92\x98\x4b\x5c\x83\x96\xa5\x35\xb1\xbc\x5b\x51\x22\x1f\x0a\x88\x4c\x32\x76\x73\xb7\xf0\x2c\xfa\x34\x0e\xdb\x37\xf8\x1b\x7d\xba\x34\xed\xb9\x02\x01\xbd\x36\x8c\x36\xd0\x9e\xb3\x35\xda\x6b\x77\xfb\xbb\x1b\xed\xcd\xf1\x78\xb7\xbd\x37\xda\xc3\x6d\x17\xf5\xfb\xdd\x3d\x17\xf5\x76\x9c\x8d\xf4\xcb\xd1\xd2\x73\x64\x5b\xe9\xf3\xda\x34\xdc\xf5\x46\xdf\x80\x17\x10\x70\x34\xf1\xd1\x40\x79\x35\x76\xa3\xab\x78\x09\xec\x70\x2c\x6d\xa5\x93\xa1\x18\x40\x4b\x9f\xdb\x69\xca\xae\x24\xf1\xce\xf6\x46\xf5\xa2\x37\xe7\x41\xa0\x85\x02\x32\x8c\xc8\x18\x46\xec\xae\x11\x83\x85\x90\x7d\xe8\x03\x5a\x4a\x41\xc5\x60\xdd\xb0\xa6\xdd\x84\x1d\x9d\x48\x99\x3b\xba\x4b\xc7\x61\x7e\x01\x78\x7c\x38\x24\x0f\xde\x45\x12\xdd\xff\x1b\xe9\x49\x12\x7d\x27\x54\xb7\xdd\xeb\xb6\xbb\x5b\xe7\xbd\xfe\x60\xab\x37\xe8\x6f\x76\xba\x5b\x1b\xbd\xcd\xfe\x1f\x69\x0f\xeb\xa0\x48\xa1\xc7\xf6\x60\x63\xbb\xb3\xb1\xdd\xef\x77\x77\xad\x1e\xf1\x89\x0e\x68\xf5\x3b\xdb\x9d\x6e\xfa\x22\xeb\x52\x12\x57\x53\xa2\xe0\x0d\x6e\x2f\xff\xeb\x28\xbd\x39\x37\xf3\xac\xf5\x4f\x5b\xeb\xb3\xa7\x9f\xa0\x85\xa2\x23\xad\x65\x17\x6a\xa4\x37\x97\xe4\x65\xdf\xdc\x44\x4a\xae\x4e\xc8\x99\x44\x83\x53\x3c\xc5\x4c\xc0\x62\xc6\x60\x49\x5e\x60\x61\x59\xb1\x7d\x17\xfb\xab\xb3\xc1\xef\x68\x87\x50\x33\x00\x2d\x36\xc7\x1a\x93\x84\x05\x66\x09\xb6\x69\x7a\x19\x63\x84\x7a\x83\x84\x87\x36\x4a\xf8\x4e\x86\x09\x77\x32\x4e\xb8\x93\x81\x42\xdd\xd0\x04\x8b\x6c\xaf\x24\x69\x79\x81\xd9\x15\x33\x86\x21\x19\xa7\x4a\xa2\xbe\xcc\xb3\x4c\xea\x19\xb4\xf6\x7d\xf4\x95\x51\xf8\x84\x47\xf1\xd9\x20\xab\x6d\x9c\x1c\x96\x2a\x5f\x31\x03\xb7\x01\xaa\x76\xfa\x6b\x82\x68\x89\xd6\xe6\x50\xbb\x38\x83\x43\x24\xe4\x1a\x58\x19\x6d\x75\xb8\x41\x5d\xde\x18\xfc\x99\xce\x2a\xd6\xa2\x99\x89\xd9\x92\xa8\xca\x33\xaa\x20\xac\xb8\x5d\x3e\xd4\x08\x0f\x87\x03\xb0\x47\x40\xcc\x87\x23\xce\xae\x30\x97\x2c\x20\x4e\xb4\x37\x33\x1c\xcd\x25\x16\x43\x42\x87\xd9\xe2\x10\x89\x49\x0c\x4d\xf5\x01\xc6\x87\x84\x0d\xa3\x25\xe5\x04\x6e\x3b\x7f\xed\x85\xf2\xa0\x01\x71\x06\x30\x1c\x3a\x8c\x8a\xd0\xc7\x7c\xc8\xc6\x63\x81\xad\xcb\x7b\x8a\xc9\x38\x6d\x6b\x4b\x1e\x7a\xdb\xbd\xde\xf6\x4e\xb7\xbf\xd1\xed\x76\xbb\x59\x85\x36\x93\xb1\xdd\xcd\xde\xd6\xe6\xa2\xde\xdb\x95\xbd\xb7\x76\x77\x77\x17\xf5\xde\xab\xec\xbd\xb3\xdd\xef\xdb\x42\x2a\x49\x1a\xf9\x8b\x88\x69\xa1\x48\x0a\xe2\xa8\xcc\x5c\xc8\x71\xa2\x32\x43\xa1\x9d\xdf\xfa\x87\x56\xf6\x41\xc9\x60\x65\x6d\xfc\x47\xad\xd3\x27\xa6\x79\xf5\x65\xfb\x65\x61\x75\xaf\x5b\x8c\x9a\x73\xc5\x6f\x4a\xc7\x6a\x73\x1f\xe8\x7a\xa6\xbb\x2e\x0a\x02\x2d\x7d\xf8\xaf\xfd\xe1\xed\x87\xf3\x76\xe6\x75\x12\x3e\x9d\x59\x37\x16\xc7\x77\x19\x9b\x3b\x0f\x13\xef\x61\x96\xee\xf4\xdd\xc6\x2f\xf5\xb9\xaa\x64\xb9\xcd\x0a\x7d\xed\x3a\x21\xd0\xea\x91\x4f\x47\xc4\xbf\x7e\xeb\xf0\x83\xf0\xfd\x76\x0f\x5d\xdc\x1e\xfd\x71\xfd\xea\xfc\xfa\xf8\x14\x25\x8c\xa9\xb8\x3b\xf5\x99\x31\x35\x37\x50\x95\x31\xa7\x7f\x2f\xde\xf4\x6b\x59\xd3\x2f\xe3\x8c\x99\x34\x82\x64\x8a\x5e\x81\x33\x6b\xe0\x03\xb8\xd0\x43\x91\x7a\xab\x83\xc0\xfc\x3d\x6a\xa6\x54\x5e\x71\x82\x34\x80\xcc\x67\x07\xb0\xe8\x2b\x56\x75\x72\xe6\x85\x3e\x35\x0b\xc6\x0a\x7a\xb4\xba\x09\xab\xc4\x5d\xed\xc0\x59\x59\x3b\xbd\xf8\x3f\x88\xa2\xc6\xb5\x68\xfb\x2d\x1b\x80\xc6\x4f\x4d\xc8\xda\x81\x5f\xcd\x02\xae\x91\xcd\x00\x88\x0b\x2f\xa1\x67\xf3\x27\x2f\x69\xef\xd3\xc1\xdb\x70\x3e\x3a\xe2\x87\xf4\x96\xef\x63\x7f\xa7\xbf\x39\xb9\xbe\xba\x22\x07\xb3\x44\xd2\x0b\x2a\xc7\x95\x4a\xbb\x77\x2f\x69\xf7\x6a\xa5\xdd\x2b\x91\xb6\x6f\x70\xd4\xb9\x44\xa9\x82\x27\xfe\x1d\xec\x0a\x8d\xcb\xb3\x20\x5f\xd3\xac\x8c\xe4\x9d\xfb\x50\xbc\x53\x47\xf0\x4e\x09\xbd\x4d\x2e\x17\x4d\xb0\x37\x57\xfb\x3f\x3a\x1a\x92\xa9\x68\x84\xbc\x29\xa4\xeb\xbe\x5c\xed\x91\x7f\x6d\xb8\xe1\x6f\x9f\x8f\x66\xb3\xad\xcf\xb3\xf7\xde\xfc\x6b\xcf\x7f\x7b\xba\xf1\xcb\xfc\xfa\x78\x35\xad\x86\x57\xe3\xbd\x3e\x7f\xdc\x99\xf4\x27\xdb\xef\xce\xdd\x8b\x7f\x5d\xa0\xfe\x95\x78\xb7\xdb\xbf\xfa\xf5\x60\x63\x1e\xb3\x24\x5f\xc8\xaf\xd4\x9d\xdf\x4b\x87\x7b\xb5\x3a\xdc\x2b\xd3\xe1\xd4\x27\xcd\x30\x27\xe3\x39\xfc\xf2\xe9\xdc\xd4\x49\x1c\xc0\x69\xb4\xc8\x9f\xdc\xaf\x66\x16\x0c\x4c\x15\xc5\x46\x2c\xd9\xb8\x98\x1e\x4e\x6f\xfc\xdf\x5f\x05\x9f\x4e\xc6\x47\x7d\xef\x18\x5f\x05\xee\xe6\x1f\x07\x31\x4b\xf2\xb7\xc3\x95\xb1\x64\xf3\x3e\x1c\xd9\xac\x63\xc8\x66\x19\x3f\x04\xe6\xb0\x3a\x66\xac\x3d\x42\x7c\xb5\xe9\x25\xc7\x9d\x1a\x43\xff\xbc\x71\x41\x0e\xa7\x5f\xa9\xc5\x84\x2f\x81\xbb\xf9\xf9\x75\xc2\x84\xa6\x97\x4e\x97\x71\x67\xeb\x3e\xdc\xd9\xaa\xe3\xce\xd6\x62\xee\x4c\x91\x88\x8b\x0a\x58\x3b\xc1\xe9\xcd\xce\xdb\x66\xc9\x43\x9f\x55\x8e\x76\x0f\x17\x72\xea\xea\x56\x71\xea\xb7\x13\x7c\xd4\x67\xc7\xf8\x8b\xbb\xf1\xfb\xab\x84\x51\x0b\x2e\xc5\x2e\x35\xa8\xfe\xbd\x0c\xaa\x5f\x6b\x50\xfd\x12\x16\x25\x46\x23\x15\xb2\x30\x45\x33\x1c\x9d\x24\xc7\x14\xe2\xba\x61\x95\x4c\xb8\xfa\xfd\xf5\xd7\x4f\x9a\xf6\x98\x09\xef\x67\x6f\xf6\xbe\x7c\xf8\xf5\x73\xcc\x84\xba\x9b\x90\xcb\x18\xb0\xb1\x7d\x1f\x06\xd8\xbd\x8b\x0c\xb0\xdf\x66\x3c\x2c\xf2\x4c\xcc\x41\x04\x20\x4f\x2f\x68\xe9\x32\x64\x95\x64\x6f\x5f\x7d\xee\x2a\xd9\x7f\x4d\xe9\xff\x8c\xa7\xee\xc6\x61\xe4\x29\x8a\x85\x2b\xcb\x48\xdd\xbb\x0f\xa5\x7b\x75\x84\xee\x95\x7a\xce\xa8\x74\x58\x5c\x09\xb4\xc6\x11\xe2\xc3\x58\x8c\xdb\x9f\x27\xd3\xf1\x87\xbd\xc9\xdb\x53\xf1\x6e\x76\xf8\x29\x21\xaf\xf1\x70\xf9\x3d\x89\xb4\xe6\x75\x2d\xb3\x21\x1f\x57\xae\x03\x15\xd9\x0b\x2c\x07\xf0\xf1\xf5\x87\xf6\xe1\xef\xed\xbd\x41\xb4\x90\x6c\x4a\xcd\x29\x62\xd2\x36\xf8\x56\xb6\xd3\x45\xc4\x76\x8f\xdc\x76\x37\x3c\xea\x7a\xfe\x75\xf7\x7a\xec\xec\x08\x22\xd1\x96\xf0\xbe\xcc\x76\xed\xb9\xb0\x8a\x57\xe3\xd2\x07\x8a\xf2\xde\x64\xcb\xdd\xdd\xbd\xee\x7a\xdc\x71\x67\x9b\x93\x1d\xe4\x8d\x76\x84\x37\x9e\xd0\x2f\x1b\xee\x74\x24\xbe\xfc\xe3\xff\xfd\xf3\xf0\xf7\xf3\xd3\x7d\xf8\xd9\x10\xdb\xd1\x7c\x79\x99\x1e\xbe\xb4\x60\x13\x01\xab\x9b\xdd\xcd\xd5\x35\xcd\x06\xfd\xf3\xf5\xfb\x8b\xb3\xf3\xc3\xd3\x78\x68\xe8\x6e\xae\xea\xfd\xfd\x44\x94\xf6\x29\x4e\xd5\xbe\x37\xd9\x62\x7c\xab\x3b\x23\x61\x77\x87\x61\x25\xa8\x29\xbf\x72\xfa\xdb\xee\x64\x2c\xbf\xf4\x90\xb3\x6a\x73\xef\x75\x44\xc7\xea\x22\x22\xac\x50\xe3\xa7\xba\x81\xf5\x5c\x7c\xe2\xf3\x6d\x2a\xae\x47\x7d\x71\xec\xbf\xf9\xb2\x35\xfa\x3d\x38\xd8\x79\x8d\x5a\x2b\xff\x17\x00\x00\xff\xff\x84\x79\x99\x0f\x7e\xa3\x00\x00") func fleetManagerYamlBytes() ([]byte, error) { return bindataRead( @@ -94,7 +94,7 @@ func fleetManagerYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "fleet-manager.yaml", size: 41758, mode: os.FileMode(420), modTime: time.Unix(1, 0)} + info := bindataFileInfo{name: "fleet-manager.yaml", size: 41854, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/internal/dinosaur/pkg/handlers/admin_dinosaur.go b/internal/dinosaur/pkg/handlers/admin_dinosaur.go index f0c073ca63..6c29a01d11 100644 --- a/internal/dinosaur/pkg/handlers/admin_dinosaur.go +++ b/internal/dinosaur/pkg/handlers/admin_dinosaur.go @@ -3,12 +3,15 @@ package handlers import ( "encoding/json" + "fmt" "io" "net/http" + "regexp" "time" "github.com/golang/glog" "github.com/gorilla/mux" + "github.com/lib/pq" "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/admin/private" "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/dbapi" "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/public" @@ -20,6 +23,12 @@ import ( "github.com/stackrox/acs-fleet-manager/pkg/handlers" coreServices "github.com/stackrox/acs-fleet-manager/pkg/services" "github.com/stackrox/acs-fleet-manager/pkg/services/account" + "github.com/stackrox/acs-fleet-manager/pkg/shared/utils/arrays" + "gorm.io/gorm" +) + +var ( + validTraitRegexp = regexp.MustCompile(`^[[:alnum:]_-]{1,50}$`) ) // AdminCentralHandler is the interface for the admin central handler @@ -44,6 +53,14 @@ type AdminCentralHandler interface { // a tenant. In particular, avoid two Central CRs appearing in the same // tenant namespace. This may cause conflicts due to mixed resource ownership. PatchName(w http.ResponseWriter, r *http.Request) + // ListTraits returns all central traits + ListTraits(w http.ResponseWriter, r *http.Request) + // GetTrait tells wheter a central has the trait + GetTrait(w http.ResponseWriter, r *http.Request) + // AddTrait adds a trait to the set of central traits + AddTrait(w http.ResponseWriter, r *http.Request) + // DeleteTrait deletes a trait from a central + DeleteTrait(w http.ResponseWriter, r *http.Request) } type adminCentralHandler struct { @@ -298,3 +315,75 @@ func (h adminCentralHandler) PatchName(w http.ResponseWriter, r *http.Request) { } handlers.Handle(w, r, cfg, http.StatusOK) } + +func (h adminCentralHandler) ListTraits(w http.ResponseWriter, r *http.Request) { + cfg := &handlers.HandlerConfig{ + Action: func() (i interface{}, serviceError *errors.ServiceError) { + id := mux.Vars(r)["id"] + cr, svcErr := h.service.GetByID(id) + if svcErr != nil { + return nil, svcErr + } + if len(cr.Traits) == 0 { + return pq.StringArray{}, nil + } + return cr.Traits, nil + }, + } + handlers.HandleGet(w, r, cfg) +} + +func (h adminCentralHandler) GetTrait(w http.ResponseWriter, r *http.Request) { + cfg := &handlers.HandlerConfig{ + Validate: []handlers.Validate{handlers.ValidateRegex(r, "trait", validTraitRegexp)}, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + id := mux.Vars(r)["id"] + trait := mux.Vars(r)["trait"] + cr, svcErr := h.service.GetByID(id) + if svcErr != nil { + return nil, svcErr + } + if !arrays.Contains(cr.Traits, trait) { + return nil, errors.NotFound(fmt.Sprintf("Trait %q not found", trait)) + } + return nil, nil + }, + } + handlers.HandleGet(w, r, cfg) +} + +func (h adminCentralHandler) AddTrait(w http.ResponseWriter, r *http.Request) { + cfg := &handlers.HandlerConfig{ + Validate: []handlers.Validate{handlers.ValidateRegex(r, "trait", validTraitRegexp)}, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + id := mux.Vars(r)["id"] + trait := mux.Vars(r)["trait"] + central := &dbapi.CentralRequest{Meta: api.Meta{ID: id}} + if svcErr := h.service.Updates(central, map[string]interface{}{ + "traits": gorm.Expr(`(SELECT array_agg(DISTINCT v) FROM unnest(array_append(traits, ?)) AS traits_tmp(v))`, trait), + }); svcErr != nil { + return nil, errors.NewWithCause(svcErr.Code, svcErr, "Could not update central traits") + } + return nil, nil + }, + } + handlers.Handle(w, r, cfg, http.StatusOK) +} + +func (h adminCentralHandler) DeleteTrait(w http.ResponseWriter, r *http.Request) { + cfg := &handlers.HandlerConfig{ + Validate: []handlers.Validate{handlers.ValidateRegex(r, "trait", validTraitRegexp)}, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + id := mux.Vars(r)["id"] + trait := mux.Vars(r)["trait"] + central := &dbapi.CentralRequest{Meta: api.Meta{ID: id}} + if svcErr := h.service.Updates(central, map[string]interface{}{ + "traits": gorm.Expr(`array_remove(traits, ?)`, trait), + }); svcErr != nil { + return nil, errors.NewWithCause(svcErr.Code, svcErr, "Could not update central traits") + } + return nil, nil + }, + } + handlers.HandleDelete(w, r, cfg, http.StatusOK) +} diff --git a/internal/dinosaur/pkg/migrations/20240112000000_add_central_traits.go b/internal/dinosaur/pkg/migrations/20240112000000_add_central_traits.go new file mode 100644 index 0000000000..13697bbb09 --- /dev/null +++ b/internal/dinosaur/pkg/migrations/20240112000000_add_central_traits.go @@ -0,0 +1,31 @@ +package migrations + +// Migrations should NEVER use types from other packages. Types can change +// and then migrations run on a _new_ database will fail or behave unexpectedly. +// Instead of importing types, always re-create the type in the migration, as +// is done here, even though the same type is defined in pkg/api + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "github.com/lib/pq" + "github.com/stackrox/acs-fleet-manager/pkg/db" + "gorm.io/gorm" +) + +func addTraitsFieldToCentralRequests() *gormigrate.Migration { + type CentralRequest struct { + db.Model + Traits pq.StringArray `json:"traits" gorm:"type:text[]"` + } + migrationID := "20240112000000" + + return &gormigrate.Migration{ + ID: migrationID, + Migrate: func(tx *gorm.DB) error { + return addColumnIfNotExists(tx, &CentralRequest{}, "traits") + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropColumn(&CentralRequest{}, "traits") + }, + } +} diff --git a/internal/dinosaur/pkg/migrations/migrations.go b/internal/dinosaur/pkg/migrations/migrations.go index a288bf1bb8..d96036202c 100644 --- a/internal/dinosaur/pkg/migrations/migrations.go +++ b/internal/dinosaur/pkg/migrations/migrations.go @@ -53,6 +53,7 @@ func getMigrations() []*gormigrate.Migration { addExpirationLeaseType(), addClusterAddons(), addAlternateUserIDFieldToCentralRequests(), + addTraitsFieldToCentralRequests(), } } diff --git a/internal/dinosaur/pkg/presenters/admin_dinosaur.go b/internal/dinosaur/pkg/presenters/admin_dinosaur.go index 088f0964b9..7a7194ef24 100644 --- a/internal/dinosaur/pkg/presenters/admin_dinosaur.go +++ b/internal/dinosaur/pkg/presenters/admin_dinosaur.go @@ -28,5 +28,6 @@ func PresentDinosaurRequestAdminEndpoint(request *dbapi.CentralRequest, _ accoun ExpiredAt: request.ExpiredAt, FailedReason: request.FailedReason, InstanceType: request.InstanceType, + Traits: request.Traits, }, nil } diff --git a/internal/dinosaur/pkg/routes/route_loader.go b/internal/dinosaur/pkg/routes/route_loader.go index 44ead3601c..272734ca7b 100644 --- a/internal/dinosaur/pkg/routes/route_loader.go +++ b/internal/dinosaur/pkg/routes/route_loader.go @@ -262,6 +262,19 @@ func (s *options) buildAPIBaseRouter(mainRouter *mux.Router, basePath string, op Name(logger.NewLogEvent("admin-name", "[admin] set `name` central property").ToString()). Methods(http.MethodPatch) + adminCentralsRouter.HandleFunc("/{id}/traits", adminCentralHandler.ListTraits). + Name(logger.NewLogEvent("admin-list-traits", "[admin] list central traits").ToString()). + Methods(http.MethodGet) + adminCentralsRouter.HandleFunc("/{id}/traits/{trait}", adminCentralHandler.GetTrait). + Name(logger.NewLogEvent("admin-get-trait", "[admin] check existence of a central trait").ToString()). + Methods(http.MethodGet) + adminCentralsRouter.HandleFunc("/{id}/traits/{trait}", adminCentralHandler.AddTrait). + Name(logger.NewLogEvent("admin-put-trait", "[admin] add a central trait").ToString()). + Methods(http.MethodPut) + adminCentralsRouter.HandleFunc("/{id}/traits/{trait}", adminCentralHandler.DeleteTrait). + Name(logger.NewLogEvent("admin-delete-trait", "[admin] delete central trait").ToString()). + Methods(http.MethodDelete) + adminCreateRouter := adminCentralsRouter.NewRoute().Subrouter() adminCreateRouter.HandleFunc("", adminCentralHandler.Create).Methods(http.MethodPost) diff --git a/openapi/fleet-manager-private-admin.yaml b/openapi/fleet-manager-private-admin.yaml index 0974c72b2f..151f9cf370 100644 --- a/openapi/fleet-manager-private-admin.yaml +++ b/openapi/fleet-manager-private-admin.yaml @@ -419,6 +419,146 @@ paths: schema: $ref: 'fleet-manager.yaml#/components/schemas/Error' + '/api/rhacs/v1/admin/centrals/{id}/traits': + get: + summary: Returns a list of central traits. + operationId: getCentralTraits + parameters: + - $ref: "fleet-manager.yaml#/components/parameters/id" + responses: + "200": + content: + application/json: + schema: + type: array + items: + type: string + description: Central traits + "401": + description: Auth token is invalid + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "403": + description: User is not authorised to access the service + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "404": + description: No Central found with the specified ID + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "500": + description: Unexpected error occurred + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + + '/api/rhacs/v1/admin/centrals/{id}/traits/{trait}': + get: + summary: Returns central trait status. + operationId: getCentralTrait + parameters: + - $ref: "fleet-manager.yaml#/components/parameters/id" + - $ref: "#/components/parameters/trait" + responses: + "200": + description: Trait exists. + "401": + description: Auth token is invalid + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "403": + description: User is not authorised to access the service + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "404": + description: No Central found with the specified ID or no such trait. + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "500": + description: Unexpected error occurred + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + put: + summary: Adds a trait to a central. + operationId: putCentralTrait + parameters: + - $ref: "fleet-manager.yaml#/components/parameters/id" + - $ref: "#/components/parameters/trait" + responses: + "200": + description: Trait has been added or already exists. + "401": + description: Auth token is invalid + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "403": + description: User is not authorised to access the service + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "404": + description: No Central found with the specified ID + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "500": + description: Unexpected error occurred + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + delete: + summary: Deletes the central trait. + operationId: deleteCentralTrait + parameters: + - $ref: "fleet-manager.yaml#/components/parameters/id" + - $ref: "#/components/parameters/trait" + responses: + "200": + description: Central trait deleted + "401": + description: Auth token is invalid + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "403": + description: User is not authorised to access the service + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "404": + description: No Central found with the specified ID + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "500": + description: Unexpected error occurred + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' components: schemas: Central: @@ -486,6 +626,10 @@ components: type: string namespace: type: string + traits: + type: array + items: + type: string CentralList: allOf: - $ref: "fleet-manager.yaml#/components/schemas/List" @@ -515,6 +659,14 @@ components: type: string reason: type: string + parameters: + trait: + name: trait + description: A central trait + schema: + type: string + in: path + required: true securitySchemes: Bearer: diff --git a/openapi/fleet-manager.yaml b/openapi/fleet-manager.yaml index 53fa3eb0df..746d6fb921 100644 --- a/openapi/fleet-manager.yaml +++ b/openapi/fleet-manager.yaml @@ -665,6 +665,10 @@ components: type: string instance_type: type: string + traits: + type: array + items: + type: string example: $ref: "#/components/examples/CentralRequestExample" CentralRequestList: diff --git a/pkg/auth/roles_authz.go b/pkg/auth/roles_authz.go index 07b07bf63a..c5af1ba499 100644 --- a/pkg/auth/roles_authz.go +++ b/pkg/auth/roles_authz.go @@ -81,7 +81,7 @@ func readRoleAuthZConfigFile(file string, val *RoleConfig) error { return nil } -var allowedHTTPMethods = []string{http.MethodGet, http.MethodPatch, http.MethodDelete, http.MethodPost} +var allowedHTTPMethods = []string{http.MethodGet, http.MethodPatch, http.MethodDelete, http.MethodPost, http.MethodPut} func validateRolesConfiguration(configs []RolesConfiguration) error { for _, config := range configs { diff --git a/pkg/handlers/validation.go b/pkg/handlers/validation.go index 15948a88ba..b818d855de 100644 --- a/pkg/handlers/validation.go +++ b/pkg/handlers/validation.go @@ -4,11 +4,12 @@ import ( "net/http" "regexp" - "github.com/xeipuuv/gojsonschema" - "net/url" "strconv" + "github.com/gorilla/mux" + "github.com/xeipuuv/gojsonschema" + "github.com/stackrox/acs-fleet-manager/pkg/errors" ) @@ -144,3 +145,13 @@ func ValidatQueryParam(queryParams url.Values, field string) Validate { } } + +func ValidateRegex(r *http.Request, field string, regex *regexp.Regexp) Validate { + return func() *errors.ServiceError { + value := mux.Vars(r)[field] + if !regex.MatchString(value) { + return errors.MalformedServiceAccountName("%s %q does not match %s", field, value, regex.String()) + } + return nil + } +} diff --git a/pkg/server/api_server.go b/pkg/server/api_server.go index c3424b4fe8..def648efc0 100644 --- a/pkg/server/api_server.go +++ b/pkg/server/api_server.go @@ -110,6 +110,7 @@ func NewAPIServer(options ServerOptions) *APIServer { http.MethodGet, http.MethodPatch, http.MethodPost, + http.MethodPut, }), gorillahandlers.AllowedHeaders([]string{ "Authorization", From 96c5a69d036c1a24129ce01281521b501d732d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl?= Date: Thu, 8 Feb 2024 14:39:31 +0100 Subject: [PATCH 5/7] ROX-22360: `expired_at` is not nullified (#1643) --- internal/dinosaur/pkg/services/dinosaur.go | 1 + .../dinosaurmgrs/expiration_date_mgr.go | 28 +++++++++++-------- .../dinosaurmgrs/expiration_date_mgr_test.go | 13 +++++---- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/internal/dinosaur/pkg/services/dinosaur.go b/internal/dinosaur/pkg/services/dinosaur.go index 932af5465d..8136818358 100644 --- a/internal/dinosaur/pkg/services/dinosaur.go +++ b/internal/dinosaur/pkg/services/dinosaur.go @@ -88,6 +88,7 @@ type DinosaurService interface { // same as the original status. The error will contain any error encountered when attempting to update or the reason // why no attempt has been done UpdateStatus(id string, status dinosaurConstants.CentralStatus) (bool, *errors.ServiceError) + // Update does NOT update nullable fields when they're nil in the request. Use Updates() instead. Update(dinosaurRequest *dbapi.CentralRequest) *errors.ServiceError // Updates() updates the given fields of a dinosaur. This takes in a map so that even zero-fields can be updated. // Use this only when you want to update the multiple columns that may contain zero-fields, otherwise use the `DinosaurService.Update()` method. diff --git a/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr.go b/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr.go index 48c5b077b3..fd619b092e 100644 --- a/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr.go +++ b/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr.go @@ -104,30 +104,36 @@ func (k *ExpirationDateManager) reconcileCentralExpiredAt(centrals dbapi.Central quotaCostCache[key] = active } - if err := k.updateExpiredAtBasedOnQuotaEntitlement(central, active); err != nil { - svcErrors = append(svcErrors, errors.Wrapf(err, "failed to update expired_at value based on quota entitlement for central instance %q", central.ID)) + if timestamp, needsChange := k.expiredAtNeedsUpdate(central, active); needsChange { + central.ExpiredAt = timestamp + if err := k.updateExpiredAtInDB(central); err != nil { + svcErrors = append(svcErrors, errors.Wrapf(err, + "failed to update expired_at value based on quota entitlement for central instance %q", central.ID)) + } } } return svcErrors } -// Updates expired_at field of the given Central instance based on the user/organisation's quota entitlement status -func (k *ExpirationDateManager) updateExpiredAtBasedOnQuotaEntitlement(central *dbapi.CentralRequest, isQuotaEntitlementActive bool) *serviceErr.ServiceError { +func (k *ExpirationDateManager) updateExpiredAtInDB(central *dbapi.CentralRequest) *serviceErr.ServiceError { + glog.Infof("updating expired_at of central %q to %q", central.ID, central.ExpiredAt) + return k.centralService.Updates(&dbapi.CentralRequest{Meta: api.Meta{ID: central.ID}}, + map[string]interface{}{"expired_at": central.ExpiredAt}) +} + +// Returns whether the expired_at field of the given Central instance needs to be updated. +func (k *ExpirationDateManager) expiredAtNeedsUpdate(central *dbapi.CentralRequest, isQuotaEntitlementActive bool) (*time.Time, bool) { // if quota entitlement is active, ensure expired_at is set to null. if isQuotaEntitlementActive && central.ExpiredAt != nil { - central.ExpiredAt = nil - glog.Infof("updating expiration date of central instance %q to NULL", central.ID) - return k.centralService.Update(central) + return nil, true } // if quota entitlement is not active and expired_at is not already set, set // its value to the current time. if !isQuotaEntitlementActive && central.ExpiredAt == nil { now := time.Now() - central.ExpiredAt = &now - glog.Infof("quota entitlement for central instance %q is no longer active, updating expired_at to %q", central.ID, now.Format(time.RFC1123Z)) - return k.centralService.Update(central) + return &now, true } - return nil + return nil, false } diff --git a/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr_test.go b/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr_test.go index b04dde6c01..b2cc7e300a 100644 --- a/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr_test.go +++ b/internal/dinosaur/pkg/workers/dinosaurmgrs/expiration_date_mgr_test.go @@ -34,7 +34,10 @@ func TestExpirationDateManager(t *testing.T) { ListByStatusFunc: func(status ...constants.CentralStatus) ([]*dbapi.CentralRequest, *errors.ServiceError) { return centrals, nil }, - UpdateFunc: func(centralRequest *dbapi.CentralRequest) *errors.ServiceError { + UpdatesFunc: func(centralRequest *dbapi.CentralRequest, fields map[string]any) *errors.ServiceError { + if _, ok := fields["expired_at"]; !ok { + return errors.GeneralError("bad fields") + } return nil }, } @@ -50,7 +53,7 @@ func TestExpirationDateManager(t *testing.T) { errs := mgr.Reconcile() require.Empty(t, errs) assert.Len(t, centralService.ListByStatusCalls(), 1) - assert.Empty(t, centralService.UpdateCalls()) + assert.Empty(t, centralService.UpdatesCalls()) assert.Empty(t, quotaSvc.HasQuotaAllowanceCalls()) assert.Len(t, quotaFactory.GetQuotaServiceCalls(), 1) }) @@ -66,7 +69,7 @@ func TestExpirationDateManager(t *testing.T) { assert.Nil(t, central.ExpiredAt) assert.Len(t, centralService.ListByStatusCalls(), 1) assert.Len(t, quotaSvc.HasQuotaAllowanceCalls(), 1) - assert.Len(t, centralService.UpdateCalls(), 1) + assert.Len(t, centralService.UpdatesCalls(), 1) assert.Len(t, quotaFactory.GetQuotaServiceCalls(), 1) }) @@ -82,7 +85,7 @@ func TestExpirationDateManager(t *testing.T) { assert.Less(t, now, *central.ExpiredAt) assert.Len(t, centralService.ListByStatusCalls(), 1) assert.Len(t, quotaSvc.HasQuotaAllowanceCalls(), 1) - assert.Len(t, centralService.UpdateCalls(), 1) + assert.Len(t, centralService.UpdatesCalls(), 1) assert.Len(t, quotaFactory.GetQuotaServiceCalls(), 1) }) @@ -105,7 +108,7 @@ func TestExpirationDateManager(t *testing.T) { assert.Nil(t, centralE.ExpiredAt) assert.Len(t, centralService.ListByStatusCalls(), 1) assert.Len(t, quotaSvc.HasQuotaAllowanceCalls(), 3) - assert.Len(t, centralService.UpdateCalls(), 5) + assert.Len(t, centralService.UpdatesCalls(), 5) assert.Len(t, quotaFactory.GetQuotaServiceCalls(), 1) }) } From 9b50e96dd4e6ad93f8ed87ea5532e495d2e69df6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:41:32 +0100 Subject: [PATCH 6/7] Bump k8s.io/client-go from 0.28.4 to 0.29.1 (#1614) Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.28.4 to 0.29.1. - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.28.4...v0.29.1) --- updated-dependencies: - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index ca52380f4c..aa4d84df99 100644 --- a/go.mod +++ b/go.mod @@ -64,9 +64,9 @@ require ( gorm.io/driver/postgres v1.5.4 gorm.io/gorm v1.25.5 helm.sh/helm/v3 v3.13.3 - k8s.io/api v0.28.4 - k8s.io/apimachinery v0.28.4 - k8s.io/client-go v0.28.4 + k8s.io/api v0.29.1 + k8s.io/apimachinery v0.29.1 + k8s.io/client-go v0.29.1 k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/controller-runtime v0.16.3 sigs.k8s.io/yaml v1.4.0 @@ -167,7 +167,7 @@ require ( golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.16.1 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 // indirect @@ -180,10 +180,10 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.28.4 // indirect k8s.io/component-base v0.28.4 // indirect - k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) replace ( diff --git a/go.sum b/go.sum index a1413954bc..19b9df96a2 100644 --- a/go.sum +++ b/go.sum @@ -171,7 +171,7 @@ github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkc github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= @@ -677,7 +677,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -935,8 +935,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1169,18 +1169,18 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU= k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= +k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo= k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= @@ -1192,7 +1192,7 @@ sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigw sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 7b4d0a70c668f3eeddbf1c1694b71f7cf0f1a375 Mon Sep 17 00:00:00 2001 From: Yury Kovalev <8366110+kovayur@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:03:35 +0100 Subject: [PATCH 7/7] Delete terraform_cluster.sh (#1645) --- .github/workflows/deploy-data-plane.yaml | 41 ----- .github/workflows/deploy-dev.yaml | 1 - .github/workflows/deploy-integration.yaml | 1 - .github/workflows/deploy-production.yaml | 1 - .github/workflows/deploy-stage.yaml | 1 - dp-terraform/helm/.dockerignore | 2 - dp-terraform/helm/rhacs-terraform/README.md | 8 - .../helm/rhacs-terraform/terraform_cluster.sh | 171 ------------------ dp-terraform/helm/rhacs-terraform/test.sh | 15 -- 9 files changed, 241 deletions(-) delete mode 100644 dp-terraform/helm/.dockerignore delete mode 100755 dp-terraform/helm/rhacs-terraform/terraform_cluster.sh delete mode 100755 dp-terraform/helm/rhacs-terraform/test.sh diff --git a/.github/workflows/deploy-data-plane.yaml b/.github/workflows/deploy-data-plane.yaml index 2e82418cb6..295d6794b9 100644 --- a/.github/workflows/deploy-data-plane.yaml +++ b/.github/workflows/deploy-data-plane.yaml @@ -18,10 +18,6 @@ on: description: 'Name of the environment defined in GitHub.' required: true type: string - deploy_clusters: - description: 'Names of clusters to deploy to, space separated.' - required: true - type: string probe_clusters: description: 'Name of clusters to deploy probe to, space separated.' required: true @@ -34,45 +30,8 @@ on: env: HELM_DRY_RUN: ${{ inputs.dry_run }} - # Credentials are populated by explicit `configure-aws-credentials` jobs in - # the workflow, so loading additional credentials in the terraform_cluster.sh - # script is not necessary. - AWS_AUTH_HELPER: none jobs: - terraform: - name: Re-terraform ${{ inputs.acs_environment }} clusters - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - environment: ${{ inputs.github_environment }} - steps: - - name: Set up Go 1.20 - uses: actions/setup-go@v3 - with: - go-version: "1.20" - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Critical for correct image detection in deploy script - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1-node16 - with: - aws-region: ${{ secrets.AWS_REGION }} - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github - - name: Run terraforming on ${{ inputs.deploy_clusters }} - working-directory: ./dp-terraform/helm/rhacs-terraform - run: | - set -euo pipefail - # shellcheck disable=SC2043 - for cluster in ${{ inputs.deploy_clusters }} - do - echo "Running script terraform_cluster.sh on ${cluster}" - ./terraform_cluster.sh ${{ inputs.acs_environment }} "${cluster}" - echo "Script terraform_cluster.sh on ${cluster} succeeded" - done - deploy-probe: name: Deploy blackbox monitoring probe service to ${{ inputs.acs_environment }} runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-dev.yaml b/.github/workflows/deploy-dev.yaml index 6fbb1daec9..0ad199bcc4 100644 --- a/.github/workflows/deploy-dev.yaml +++ b/.github/workflows/deploy-dev.yaml @@ -17,6 +17,5 @@ jobs: with: acs_environment: dev github_environment: development - deploy_clusters: "" probe_clusters: "acs-dev-dp-01" dry_run: true diff --git a/.github/workflows/deploy-integration.yaml b/.github/workflows/deploy-integration.yaml index 7e340ce1e8..63fdfc9820 100644 --- a/.github/workflows/deploy-integration.yaml +++ b/.github/workflows/deploy-integration.yaml @@ -12,5 +12,4 @@ jobs: with: acs_environment: integration github_environment: integration - deploy_clusters: "" probe_clusters: "acs-int-us-01" diff --git a/.github/workflows/deploy-production.yaml b/.github/workflows/deploy-production.yaml index d66800fb57..3a4234ee85 100644 --- a/.github/workflows/deploy-production.yaml +++ b/.github/workflows/deploy-production.yaml @@ -12,5 +12,4 @@ jobs: with: acs_environment: prod github_environment: production - deploy_clusters: "acs-prod-dp-01 acs-prod-eu-01" probe_clusters: "acs-prod-dp-01 acs-prod-eu-01" diff --git a/.github/workflows/deploy-stage.yaml b/.github/workflows/deploy-stage.yaml index d48112c5f3..02c8b0e643 100644 --- a/.github/workflows/deploy-stage.yaml +++ b/.github/workflows/deploy-stage.yaml @@ -12,5 +12,4 @@ jobs: with: acs_environment: stage github_environment: stage - deploy_clusters: "acs-stage-dp-02 acs-stage-eu-02" probe_clusters: "acs-stage-dp-02 acs-stage-eu-02" diff --git a/dp-terraform/helm/.dockerignore b/dp-terraform/helm/.dockerignore deleted file mode 100644 index fbef79bca2..0000000000 --- a/dp-terraform/helm/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -rhacs-terraform/terraform_cluster.sh -rhacs-terraform/test.sh diff --git a/dp-terraform/helm/rhacs-terraform/README.md b/dp-terraform/helm/rhacs-terraform/README.md index 31d587b17c..dc84516919 100644 --- a/dp-terraform/helm/rhacs-terraform/README.md +++ b/dp-terraform/helm/rhacs-terraform/README.md @@ -4,14 +4,6 @@ Chart to terraform data plane OSD clusters. ## Usage -**Preferred method for terraforming Stage and Prod data plane clusters** - -Run the script for your environment and cluster name: - -```bash -./terraform_cluster.sh stage acs-stage-dp-01 -``` - **Prepare environment variables** The env var `FM_ENDPOINT` should point to an endpoint for the fleet manager. An option to use a fleet manager instance running in your laptop is to [setup ngrok](https://ngrok.com/docs/getting-started), launch the fleet manager, and run `ngrok http 8000` to expose it to the internet. That commands outputs an endpoint that you can use for `FM_ENDPOINT`. diff --git a/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh b/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh deleted file mode 100755 index 413286f85a..0000000000 --- a/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh +++ /dev/null @@ -1,171 +0,0 @@ -#!/bin/bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$SCRIPT_DIR/../../.." - -# shellcheck source=scripts/lib/external_config.sh -source "$ROOT_DIR/scripts/lib/external_config.sh" -# shellcheck source=scripts/lib/helm.sh -source "$ROOT_DIR/scripts/lib/helm.sh" - -if [[ $# -ne 2 ]]; then - echo "Usage: $0 [environment] [cluster]" >&2 - echo "Known environments: stage prod" - echo "Cluster typically looks like: acs-{env}-dp-01" - exit 2 -fi - -ENVIRONMENT=$1 -CLUSTER_NAME=$2 - -export AWS_AUTH_HELPER="${AWS_AUTH_HELPER:-aws-saml}" - -init_chamber - -load_external_config secured-cluster SECURED_CLUSTER_ - -AWS_ACCOUNT_ID="${AWS_ACCOUNT_ID:-$(aws sts get-caller-identity --query "Account" --output text)}" - -PROMETHEUS_MEMORY_LIMIT=${PROMETHEUS_MEMORY_LIMIT:-"20Gi"} -PROMETHEUS_MEMORY_REQUEST=${PROMETHEUS_MEMORY_REQUEST:-"20Gi"} - -case $ENVIRONMENT in - stage) - FM_ENDPOINT="https://gbrh0yv9ebhqegl.api.stage.openshift.com" - OBSERVABILITY_GITHUB_TAG="stage" - OBSERVABILITY_OBSERVATORIUM_GATEWAY="https://observatorium-mst.api.stage.openshift.com" - OBSERVABILITY_OPERATOR_VERSION="v4.2.1" - OPERATOR_ENABLED="false" - OPERATOR_USE_UPSTREAM="false" - OPERATOR_CHANNEL="stable" - OPERATOR_VERSION="v4.1.0" - FLEETSHARD_SYNC_CPU_REQUEST="${FLEETSHARD_SYNC_CPU_REQUEST:-"200m"}" - FLEETSHARD_SYNC_MEMORY_REQUEST="${FLEETSHARD_SYNC_MEMORY_REQUEST:-"1024Mi"}" - FLEETSHARD_SYNC_CPU_LIMIT="${FLEETSHARD_SYNC_CPU_LIMIT:-"1000m"}" - FLEETSHARD_SYNC_MEMORY_LIMIT="${FLEETSHARD_SYNC_MEMORY_LIMIT:-"1024Mi"}" - SECURED_CLUSTER_ENABLED="true" - RHACS_GITOPS_ENABLED="true" - RHACS_TARGETED_OPERATOR_UPGRADES="true" - ;; - - prod) - FM_ENDPOINT="https://ixi6srehbv5uxsa.api.openshift.com" - OBSERVABILITY_GITHUB_TAG="production" - OBSERVABILITY_OBSERVATORIUM_GATEWAY="https://observatorium-mst.api.openshift.com" - OBSERVABILITY_OPERATOR_VERSION="v4.2.1" - OPERATOR_ENABLED="false" - OPERATOR_USE_UPSTREAM="false" - OPERATOR_CHANNEL="stable" - OPERATOR_VERSION="v4.1.0" - FLEETSHARD_SYNC_CPU_REQUEST="${FLEETSHARD_SYNC_CPU_REQUEST:-"200m"}" - FLEETSHARD_SYNC_MEMORY_REQUEST="${FLEETSHARD_SYNC_MEMORY_REQUEST:-"1024Mi"}" - FLEETSHARD_SYNC_CPU_LIMIT="${FLEETSHARD_SYNC_CPU_LIMIT:-"1000m"}" - FLEETSHARD_SYNC_MEMORY_LIMIT="${FLEETSHARD_SYNC_MEMORY_LIMIT:-"1024Mi"}" - PROMETHEUS_MEMORY_LIMIT="30Gi" - PROMETHEUS_MEMORY_REQUEST="30Gi" - SECURED_CLUSTER_ENABLED="true" - RHACS_GITOPS_ENABLED="true" - RHACS_TARGETED_OPERATOR_UPGRADES="true" - ;; - - *) - echo "Unknown environment ${ENVIRONMENT}" - exit 2 - ;; -esac - -CLUSTER_ENVIRONMENT="$(echo "${CLUSTER_NAME}" | cut -d- -f 2 | sed 's,^int$,integration,')" -if [[ $CLUSTER_ENVIRONMENT != "$ENVIRONMENT" ]]; then - echo "Cluster ${CLUSTER_NAME} is expected to be in environment ${CLUSTER_ENVIRONMENT}, not ${ENVIRONMENT}" >&2 - exit 2 -fi - -FLEETSHARD_SYNC_ORG="app-sre" -FLEETSHARD_SYNC_IMAGE="acs-fleet-manager" -FLEETSHARD_SYNC_TAG="$(make --quiet --no-print-directory -C "${ROOT_DIR}" tag)" - -if [[ "${HELM_DRY_RUN:-}" == "true" ]]; then - "${ROOT_DIR}/scripts/check_image_exists.sh" "${FLEETSHARD_SYNC_ORG}" "${FLEETSHARD_SYNC_IMAGE}" "${FLEETSHARD_SYNC_TAG}" 0 || echo >&2 "Ignoring failed image check in dry-run mode." -else - "${ROOT_DIR}/scripts/check_image_exists.sh" "${FLEETSHARD_SYNC_ORG}" "${FLEETSHARD_SYNC_IMAGE}" "${FLEETSHARD_SYNC_TAG}" -fi - -echo "Loading external config: audit-logs/${CLUSTER_NAME}" -load_external_config "audit-logs/${CLUSTER_NAME}" AUDIT_LOGS_ - -echo "Loading external config: cluster-${CLUSTER_NAME}" -load_external_config "cluster-${CLUSTER_NAME}" CLUSTER_ -if [[ "${ENVIRONMENT}" != "dev" ]]; then - oc login --token="${CLUSTER_ROBOT_OC_TOKEN}" --server="$CLUSTER_URL" -fi - -OPERATOR_SOURCE="redhat-operators" -OPERATOR_USE_UPSTREAM="${OPERATOR_USE_UPSTREAM:-false}" -if [[ "${OPERATOR_USE_UPSTREAM}" == "true" ]]; then - load_external_config quay/rhacs-eng QUAY_ - quay_basic_auth="${QUAY_READ_ONLY_USERNAME}:${QUAY_READ_ONLY_PASSWORD}" - pull_secret_json="$(mktemp)" - trap 'rm -f "${pull_secret_json}"' EXIT - oc get secret/pull-secret -n openshift-config --template='{{index .data ".dockerconfigjson" | base64decode}}' > "${pull_secret_json}" - oc registry login --registry="quay.io/rhacs-eng" --auth-basic="${quay_basic_auth}" --to="${pull_secret_json}" --skip-check - oc set data secret/pull-secret -n openshift-config --from-file=.dockerconfigjson="${pull_secret_json}" - - OPERATOR_SOURCE="rhacs-operators" -fi - -# TODO(ROX-16771): Move this to env-specific values.yaml files -# TODO(ROX-16645): set acsOperator.enabled to false -invoke_helm "${SCRIPT_DIR}" rhacs-terraform \ - --namespace rhacs \ - --set acsOperator.enabled="${OPERATOR_ENABLED}" \ - --set acsOperator.source="${OPERATOR_SOURCE}" \ - --set acsOperator.sourceNamespace=openshift-marketplace \ - --set acsOperator.channel="${OPERATOR_CHANNEL}" \ - --set acsOperator.version="${OPERATOR_VERSION}" \ - --set acsOperator.upstream="${OPERATOR_USE_UPSTREAM}" \ - --set fleetshardSync.image.repo="quay.io/${FLEETSHARD_SYNC_ORG}/${FLEETSHARD_SYNC_IMAGE}" \ - --set fleetshardSync.image.tag="${FLEETSHARD_SYNC_TAG}" \ - --set fleetshardSync.authType="RHSSO" \ - --set fleetshardSync.clusterId="${CLUSTER_ID}" \ - --set fleetshardSync.clusterName="${CLUSTER_NAME}" \ - --set fleetshardSync.environment="${ENVIRONMENT}" \ - --set fleetshardSync.fleetManagerEndpoint="${FM_ENDPOINT}" \ - --set fleetshardSync.managedDB.enabled=true \ - --set fleetshardSync.managedDB.subnetGroup="${CLUSTER_MANAGED_DB_SUBNET_GROUP}" \ - --set fleetshardSync.managedDB.securityGroup="${CLUSTER_MANAGED_DB_SECURITY_GROUP}" \ - --set fleetshardSync.managedDB.performanceInsights=true \ - --set fleetshardSync.aws.region="${CLUSTER_REGION}" \ - --set fleetshardSync.gitops.enabled="${RHACS_GITOPS_ENABLED:-}" \ - --set fleetshardSync.targetedOperatorUpgrades.enabled="${RHACS_TARGETED_OPERATOR_UPGRADES:-}" \ - --set fleetshardSync.resources.requests.cpu="${FLEETSHARD_SYNC_CPU_REQUEST}" \ - --set fleetshardSync.resources.requests.memory="${FLEETSHARD_SYNC_MEMORY_REQUEST}" \ - --set fleetshardSync.resources.limits.cpu="${FLEETSHARD_SYNC_CPU_LIMIT}" \ - --set fleetshardSync.resources.limits.memory="${FLEETSHARD_SYNC_MEMORY_LIMIT}" \ - --set fleetshardSync.secretEncryption.type="kms" \ - --set fleetshardSync.secretEncryption.keyID="${CLUSTER_SECRET_ENCRYPTION_KEY_ID}" \ - --set fleetshardSync.addonAutoUpgradeEnabled=false \ - --set cloudwatch.clusterName="${CLUSTER_NAME}" \ - --set cloudwatch.environment="${ENVIRONMENT}" \ - --set logging.groupPrefix="${CLUSTER_NAME}" \ - --set observability.clusterName="${CLUSTER_NAME}" \ - --set observability.github.tag="${OBSERVABILITY_GITHUB_TAG}" \ - --set observability.observabilityOperatorVersion="${OBSERVABILITY_OPERATOR_VERSION}" \ - --set observability.observatorium.gateway="${OBSERVABILITY_OBSERVATORIUM_GATEWAY}" \ - --set observability.prometheus.resources.limits.memory="${PROMETHEUS_MEMORY_LIMIT}" \ - --set observability.prometheus.resources.requests.memory="${PROMETHEUS_MEMORY_REQUEST}" \ - --set audit-logs.enabled=true \ - --set audit-logs.annotations.rhacs\\.redhat\\.com/cluster-name="${CLUSTER_NAME}" \ - --set audit-logs.annotations.rhacs\\.redhat\\.com/environment="${ENVIRONMENT}" \ - --set audit-logs.customConfig.sinks.aws_cloudwatch_logs.group_name="${AUDIT_LOGS_LOG_GROUP_NAME}" \ - --set audit-logs.secrets.aws_role_arn="${AUDIT_LOGS_ROLE_ARN:-}" \ - --set secured-cluster.enabled="${SECURED_CLUSTER_ENABLED}" \ - --set secured-cluster.clusterName="${CLUSTER_NAME}" \ - --set secured-cluster.centralEndpoint="${SECURED_CLUSTER_CENTRAL_ENDPOINT}" \ - --set external-secrets.serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn="arn:aws:iam::${AWS_ACCOUNT_ID}:role/ExternalSecretsServiceRole" -# To uninstall an existing release: -# helm uninstall rhacs-terraform --namespace rhacs -# -# To delete all resources specified by the template: -# helm template ... > /var/tmp/resources.yaml -# kubectl delete -f /var/tmp/resources.yaml diff --git a/dp-terraform/helm/rhacs-terraform/test.sh b/dp-terraform/helm/rhacs-terraform/test.sh deleted file mode 100755 index f442ea21f5..0000000000 --- a/dp-terraform/helm/rhacs-terraform/test.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -CLUSTER_ID="test-clusterId" -FM_ENDPOINT="127.0.0.1:443" -OCM_TOKEN="example-token" - -# TODO(ROX-16645): set acsOperator.enabled to false -helm template rhacs-terraform \ - --debug \ - --namespace rhacs \ - --set fleetshardSync.ocmToken=${OCM_TOKEN} \ - --set fleetshardSync.fleetManagerEndpoint=${FM_ENDPOINT} \ - --set fleetshardSync.clusterId=${CLUSTER_ID} \ - --set acsOperator.enabled=true .