feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Errors for the compute VM instances Ops Agents commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.core import exceptions
class Error(exceptions.Error):
"""Base exception for Ops Agents exceptions."""
class PolicyError(Error):
"""Base exception for Ops Agents policy exceptions."""
class PolicyMalformedError(PolicyError):
"""Raised when the specified policy is not a Cloud Ops Agents Policy."""
def __init__(self, policy_id):
message = (
'Encountered a malformed Cloud Ops Agents Policy.\n The Cloud Ops'
' Agents policy [{policy_id}] may have been modified directly by the OS'
' Config API / gcloud commands. If so, please delete and re-create with'
' the Ops Agents policy gcloud commands. If not, this may be an'
' internal error.'.format(policy_id=policy_id)
)
super(PolicyMalformedError, self).__init__(message)
class PolicyNotFoundError(PolicyError):
"""Raised when the specified Ops Agents policy is not found."""
def __init__(self, policy_id):
message = (
'Ops Agents policy [{policy_id}] not found'.format(policy_id=policy_id)
)
super(PolicyNotFoundError, self).__init__(message)
class PolicyValidationError(PolicyError):
"""Raised when Ops Agents policy validation fails."""
class PolicyValidationMultiError(PolicyValidationError):
"""Raised when multiple Ops Agents policy validations fail."""
def __init__(self, errors):
super(PolicyValidationMultiError, self).__init__(
' | '.join(sorted(str(error) for error in errors))
)
self.errors = set(errors)

View File

@@ -0,0 +1,258 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This class will store in-memory instance of ops agents policy."""
import dataclasses
import enum
import json
import sys
from typing import Any, Mapping, Optional
from apitools.base.py import encoding
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_exceptions as exceptions
from googlecloudsdk.core.resource import resource_property
from googlecloudsdk.generated_clients.apis.osconfig.v1 import osconfig_v1_messages
_StrEnum = (
(enum.StrEnum,) if sys.version_info[:2] >= (3, 11) else (str, enum.Enum)
)
@dataclasses.dataclass(repr=False)
class OpsAgentsPolicy(object):
"""An Ops Agents policy encapsulates the underlying VMM Policy.
Attr:
policy_id: the unique identifier for the policy. This will be the value of
the OSPolicyAssignment's name.
agents_rule: the agents rule to be applied to VMs.
instance_filter:
[InstanceFilter](https://cloud.google.com/compute/docs/osconfig/rest/v1/projects.locations.osPolicyAssignments#InstanceFilter)
Filters to select target VMs for an assignment. Only Ops Agent supported
[osShortName](https://cloud.google.com/compute/docs/osconfig/rest/v1/projects.locations.osPolicyAssignments#inventory)
values are allowed.
rollout_state:
The state of the policy's rollout, as defined by the OSPolicyAssignment's
[rolloutState](https://cloud.google.com/compute/docs/osconfig/rest/v1/projects.locations.osPolicyAssignments#rolloutstate)
update_time:
The time of the last update to the policy object, in RFC3339 format.
This will be the value of the OSPolicyAssignment's
[revisionCreateTime](https://cloud.google.com/compute/docs/osconfig/rest/v1/projects.locations.osPolicyAssignments#resource:-ospolicyassignment)
"""
@dataclasses.dataclass(repr=False)
class AgentsRule(object):
"""An Ops agents rule contains package state, and version.
Attr:
version: agent version, e.g. 'latest', '2.52.1'.
package_state: desired state for the package.
"""
class PackageState(*_StrEnum):
INSTALLED = 'installed'
REMOVED = 'removed'
version: Optional[str]
package_state: PackageState = PackageState.INSTALLED
def __repr__(self) -> str:
"""JSON single line format string."""
return self.ToJson()
def ToJson(self) -> str:
"""JSON single line format string."""
key_camel_cased_dict = {
resource_property.ConvertToCamelCase(key): value
for key, value in self.__dict__.items()
}
return json.dumps(
key_camel_cased_dict,
separators=(',', ':'),
default=str,
sort_keys=True,
)
policy_id: str
agents_rule: AgentsRule
instance_filter: osconfig_v1_messages.OSPolicyAssignmentInstanceFilter
update_time: Optional[str] = None
rollout_state: Optional[str] = None
def __repr__(self) -> str:
"""JSON single line format string representation for testing."""
policy_map = {
'policyId': self.policy_id,
'agentsRule': self.agents_rule,
'instanceFilter': encoding.MessageToPyValue(self.instance_filter),
'updateTime': self.update_time,
'rolloutState': self.rollout_state,
}
return json.dumps(
policy_map,
default=lambda o: o.__dict__,
separators=(',', ':'),
sort_keys=True,
)
def ToPyValue(self):
policy_map = {
'policyId': self.policy_id,
# Converting enum to string.
'agentsRule': json.loads(self.agents_rule.ToJson()),
'instanceFilter': encoding.MessageToPyValue(self.instance_filter),
'updateTime': self.update_time,
'rolloutState': self.rollout_state,
}
return policy_map
_OPS_AGENTS_POLICY_KEYS = frozenset(['agentsRule', 'instanceFilter'])
def CreateAgentsRule(
agents_rule: Mapping[str, str],
) -> OpsAgentsPolicy.AgentsRule:
"""Create agents rule in ops agents policy.
Args:
agents_rule: fields (version, packageState) describing agents rule from the
command line.
Returns:
An OpsAgentPolicy.AgentsRule object.
"""
if not agents_rule or 'packageState' not in agents_rule:
raise exceptions.PolicyValidationError(
'agentsRule must contain packageState'
)
if (
agents_rule['packageState'] == 'installed'
and 'version' not in agents_rule
):
raise exceptions.PolicyValidationError(
'version is required when installing agents'
)
unknown_keys = set(agents_rule) - {
resource_property.ConvertToCamelCase(f.name)
for f in dataclasses.fields(OpsAgentsPolicy.AgentsRule)
}
if unknown_keys:
raise exceptions.PolicyValidationError(
f'unknown OpsAgentsPolicy fields: {unknown_keys} in agentsRule'
)
return OpsAgentsPolicy.AgentsRule(
version=agents_rule.get('version'),
package_state=OpsAgentsPolicy.AgentsRule.PackageState(
agents_rule['packageState']
),
)
def CreateOpsAgentsPolicy(
policy_id: str,
ops_agents_policy: Mapping[str, Any],
) -> OpsAgentsPolicy:
"""Create Ops Agent Policy.
Args:
policy_id: unique id for Cloud Ops Agents Policy.
ops_agents_policy: fields (agentsRule, instanceFilter) describing ops agents
policy from the command line.
Returns:
Ops agents policy.
"""
if (
not ops_agents_policy
or ops_agents_policy.keys() != _OPS_AGENTS_POLICY_KEYS
):
raise exceptions.PolicyValidationError(
'ops_agents_policy must contain '
+ ' and '.join(sorted(_OPS_AGENTS_POLICY_KEYS))
)
return OpsAgentsPolicy(
policy_id=policy_id,
agents_rule=CreateAgentsRule(ops_agents_policy['agentsRule']),
instance_filter=encoding.PyValueToMessage(
osconfig_v1_messages.OSPolicyAssignmentInstanceFilter,
ops_agents_policy['instanceFilter'],
),
)
def UpdateOpsAgentsPolicy(
update_ops_agents_policy: Mapping[str, Any],
ops_agents_policy: OpsAgentsPolicy,
) -> OpsAgentsPolicy:
"""Merge existing ops agents policy with user updates.
Unless explicitly mentioned, a None value means "leave unchanged".
Args:
update_ops_agents_policy: fields describing a subset of an ops agents policy
that will overwrite the existing policy.
ops_agents_policy: fields describing ops agents policy from the command
line.
Returns:
Updated ops agents policy.
"""
if update_ops_agents_policy is None:
raise exceptions.PolicyError('update_ops_agents_policy cannot be None')
unknown_keys = set(update_ops_agents_policy) - _OPS_AGENTS_POLICY_KEYS
if unknown_keys:
raise exceptions.PolicyValidationError(
f'unknown OpsAgentsPolicy fields: {unknown_keys} in'
' update_ops_agents_policy'
)
agents_rule = update_ops_agents_policy.get('agentsRule')
instance_filter = update_ops_agents_policy.get('instanceFilter')
if not (agents_rule or instance_filter):
raise exceptions.PolicyError(
'update_ops_agents_policy must update at least one field'
)
if agents_rule is not None:
updated_agents_rule = CreateAgentsRule(agents_rule)
else:
updated_agents_rule = ops_agents_policy.agents_rule
if instance_filter is not None:
updated_instance_filter = encoding.PyValueToMessage(
osconfig_v1_messages.OSPolicyAssignmentInstanceFilter,
instance_filter,
)
else:
updated_instance_filter = ops_agents_policy.instance_filter
return OpsAgentsPolicy(
policy_id=ops_agents_policy.policy_id,
instance_filter=updated_instance_filter,
agents_rule=updated_agents_rule,
)

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Util for cloud ops agents policy commands."""
import json
from typing import Optional
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_exceptions as exceptions
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_policy
from googlecloudsdk.api_lib.compute.instances.ops_agents.converters import os_policy_assignment_to_cloud_ops_agents_policy_converter as to_ops_agents_policy
from googlecloudsdk.api_lib.compute.instances.ops_agents.validators import cloud_ops_agents_policy_validator
from googlecloudsdk.api_lib.compute.os_config import utils as osconfig_api_utils
from googlecloudsdk.command_lib.compute.os_config import utils as osconfig_command_utils
def GetAgentsRuleFromDescription(
description: str,
) -> Optional[cloud_ops_agents_policy.OpsAgentsPolicy.AgentsRule]:
"""Returns an agents rule from a OSPolicy description."""
if description is None:
return None
description_parts = description.split(' | ', maxsplit=1)
if len(description_parts) < 2:
return None
try:
agents_rule_json = json.loads(description_parts[1])
except json.JSONDecodeError:
return None
try:
return cloud_ops_agents_policy.CreateAgentsRule(agents_rule_json)
except exceptions.PolicyValidationError:
return None
def GetOpsAgentsPolicyFromApi(
release_track: str, policy_id: str, project: str, zone: str
) -> cloud_ops_agents_policy.OpsAgentsPolicy:
"""Retrieves an Ops Agents policy from the OS Config API.
Args:
release_track: API release track.
policy_id: User's POLICY_ID from command prompt.
project: User's project.
zone: User's zone.
Returns:
A validated OpsAgentsPolicy.
Raises:
PolicyNotFoundError: The policy_id does not exist.
PolicyMalformedError: The policy is not an Ops Agents policy.
PolicyValidationMultiError: The policy is not a valid Ops Agents policy.
"""
messages = osconfig_api_utils.GetClientMessages(release_track)
client = osconfig_api_utils.GetClientInstance(release_track)
service = client.projects_locations_osPolicyAssignments
parent_path = osconfig_command_utils.GetProjectLocationUriPath(project, zone)
assignment_id = osconfig_command_utils.GetOsPolicyAssignmentRelativePath(
parent_path, policy_id
)
get_request = messages.OsconfigProjectsLocationsOsPolicyAssignmentsGetRequest(
name=assignment_id
)
try:
get_response = service.Get(get_request)
except apitools_exceptions.HttpNotFoundError:
raise exceptions.PolicyNotFoundError(policy_id=policy_id)
if not cloud_ops_agents_policy_validator.IsCloudOpsAgentsPolicy(get_response):
raise exceptions.PolicyMalformedError(policy_id=policy_id)
ops_agents_policy = (
to_ops_agents_policy.ConvertOsPolicyAssignmentToCloudOpsAgentsPolicy(
get_response
)
)
return ops_agents_policy

View File

@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Converter related function for Ops Agents Policy."""
import os
import pathlib
import string
from typing import Optional
from apitools.base.py import encoding
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_policy as agent_policy
from googlecloudsdk.core import yaml
from googlecloudsdk.core.util import files
from googlecloudsdk.generated_clients.apis.osconfig.v1 import osconfig_v1_messages as osconfig
def _CreateRollout() -> osconfig.OSPolicyAssignmentRollout:
return osconfig.OSPolicyAssignmentRollout(
disruptionBudget=osconfig.FixedOrPercent(percent=100),
minWaitDuration='0s',
)
def _GetRepoSuffix(version: Optional[str]) -> str:
if version and '.*.*' in version:
return version.replace('.*.*', '')
return 'all'
def _CreateOSPolicy(
agents_rule: agent_policy.OpsAgentsPolicy.AgentsRule,
) -> osconfig.OSPolicy:
"""Creates OS Policy from Ops Agents Rule.
Args:
agents_rule: User inputed agents rule.
Returns:
osconfig.OSPolicy
"""
template_path = pathlib.Path(os.path.abspath(__file__)).parent
# Check to see if specific version is used.
# TODO: b/329073427 - Update when the next major version comes out.
is_latest = agents_rule.version == '2.*.*' or agents_rule.version == 'latest'
installed = (
agents_rule.package_state
== agent_policy.OpsAgentsPolicy.AgentsRule.PackageState.INSTALLED
)
if installed:
if is_latest:
template_name = 'policy_major_version_install.yaml'
else:
template_name = 'policy_pin_to_version_install.yaml'
else:
template_name = 'policy_uninstall.yaml'
agent_version = (
agents_rule.version
if installed and not is_latest
else _GetRepoSuffix(agents_rule.version)
)
template = string.Template(
files.ReadFileContents(template_path.joinpath(template_name))
).safe_substitute(agent_version=agent_version)
os_policy = encoding.PyValueToMessage(osconfig.OSPolicy, yaml.load(template))
# Description of ops_agents_policy in a single line json format.
os_policy.description = (
'AUTO-GENERATED VALUE, DO NOT EDIT! | %s' % agents_rule.ToJson()
)
return os_policy
def ConvertOpsAgentsPolicyToOSPolicyAssignment(
name: str,
ops_agents_policy: agent_policy.OpsAgentsPolicy,
) -> osconfig.OSPolicyAssignment:
"""Converts Ops Agent policy to OS Config guest policy."""
os_policy = _CreateOSPolicy(agents_rule=ops_agents_policy.agents_rule)
os_rollout = _CreateRollout()
return osconfig.OSPolicyAssignment(
name=name,
osPolicies=[os_policy],
instanceFilter=ops_agents_policy.instance_filter,
rollout=os_rollout,
description='Cloud Ops Policy Assignment via gcloud',
)

View File

@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Converter related function for Ops Agents Policy."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from googlecloudsdk.api_lib.compute.instances.ops_agents import ops_agents_policy as agent_policy
from googlecloudsdk.calliope import exceptions
def _CreateGroupLabels(policy_group_labels):
group_labels = []
for policy_group_label in policy_group_labels or []:
pairs = {
label.key: label.value
for label in policy_group_label.labels.additionalProperties
}
group_labels.append(pairs)
return group_labels
def _ExtractDescriptionAndAgentRules(guest_policy_description):
"""Extract Ops Agents policy's description and agent rules.
Extract Ops Agents policy's description and agent rules from description of
OS Config guest policy.
Args:
guest_policy_description: OS Config guest policy's description.
Returns:
extracted description and agent rules for ops agents policy.
Raises:
BadArgumentException: If guest policy's description is illformed JSON
object, or if it does not have keys description or agentRules.
"""
try:
decode_description = json.loads(guest_policy_description)
except ValueError as e:
raise exceptions.BadArgumentException(
'description', 'description field is not a JSON object: {}'.format(e))
if not isinstance(decode_description, dict):
raise exceptions.BadArgumentException(
'description', 'description field is not a JSON object.')
try:
decoded_description = decode_description['description']
except KeyError as e:
raise exceptions.BadArgumentException(
'description.description', 'missing a required key description: %s' % e)
try:
decoded_agent_rules = decode_description['agentRules']
except KeyError as e:
raise exceptions.BadArgumentException(
'description.agentRules', 'missing a required key agentRules: %s' % e)
return (decoded_description, decoded_agent_rules)
def _CreateAgentRules(agent_rules):
"""Create agent rules in ops agent policy.
Args:
agent_rules: json objects.
Returns:
agent rules in ops agent policy.
"""
ops_agent_rules = []
for agent_rule in agent_rules or []:
try:
ops_agent_rules.append(
agent_policy.OpsAgentPolicy.AgentRule(
agent_rule['type'], agent_rule['enableAutoupgrade'],
agent_rule['version'], agent_rule['packageState']))
except KeyError as e:
raise exceptions.BadArgumentException(
'description.agentRules',
'agent rule specification %s missing a required key: %s' % (
agent_rule, e))
return ops_agent_rules
def _CreateAssignment(guest_policy_assignment):
"""Create assignment in ops agent policy from a guest policy assignment.
Args:
guest_policy_assignment: type of assignment in guest policy.
Returns:
assignment in ops agent policy.
"""
return agent_policy.OpsAgentPolicy.Assignment(
group_labels=_CreateGroupLabels(guest_policy_assignment.groupLabels),
zones=guest_policy_assignment.zones,
instances=guest_policy_assignment.instances,
os_types=[
agent_policy.OpsAgentPolicy.Assignment.OsType(
t.osShortName, t.osVersion)
for t in guest_policy_assignment.osTypes or []])
def ConvertGuestPolicyToOpsAgentPolicy(guest_policy):
"""Converts OS Config guest policy to Ops Agent policy."""
description, agent_rules = _ExtractDescriptionAndAgentRules(
guest_policy.description)
return agent_policy.OpsAgentPolicy(
assignment=_CreateAssignment(guest_policy.assignment),
agent_rules=_CreateAgentRules(agent_rules),
description=description,
etag=guest_policy.etag,
name=guest_policy.name,
update_time=guest_policy.updateTime,
create_time=guest_policy.createTime)

View File

@@ -0,0 +1,658 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Converter related function for Ops Agents Policy."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import collections
import textwrap
from googlecloudsdk.api_lib.compute.instances.ops_agents import ops_agents_policy as agent_policy
class _PackageTemplates(
collections.namedtuple(
'_PackageTemplates',
('repo', 'clear_prev_repo'))):
pass
class _AgentRuleTemplates(
collections.namedtuple(
'_AgentRuleTemplates',
('install_with_version', 'yum_package', 'apt_package',
'zypper_package', 'goo_package', 'run_agent', 'win_run_agent',
'repo_id', 'display_name', 'recipe_name', 'current_major_version'))):
pass
_EMPTY_SOFTWARE_RECIPE_SCRIPT = textwrap.dedent("""\
#!/bin/bash
echo 'Skipping as the package state is [removed].'""")
_AGENT_RULE_TEMPLATES = {
'logging':
_AgentRuleTemplates(
install_with_version=(
'curl -sSO https://dl.google.com/cloudagents/add-logging-agent-repo.sh && '
'sudo bash add-logging-agent-repo.sh --also-install --version=%s'
),
yum_package=_PackageTemplates(
repo='google-cloud-logging-el%s-x86_64-%s',
clear_prev_repo=(
'sudo rm /etc/yum.repos.d/google-cloud-logging.repo || '
"true; find /var/cache/{yum,dnf} -name '*google-cloud-logging*' "
'| xargs sudo rm -rf || true'),
),
zypper_package=_PackageTemplates(
repo='google-cloud-logging-sles%s-x86_64-%s',
clear_prev_repo=(
'sudo rm /etc/zypp/repos.d/google-cloud-logging.repo || '
"true; find /var/cache/zypp -name '*google-cloud-logging*' "
'| xargs sudo rm -rf || true'),
),
apt_package=_PackageTemplates(
repo='google-cloud-logging-%s-%s',
clear_prev_repo=(
'sudo rm /etc/apt/sources.list.d/google-cloud-logging.list '
'|| true; find /var/cache/apt -name '
"'*google-fluentd*' | xargs sudo rm -rf || true"),
),
goo_package=None,
repo_id='google-cloud-logging',
display_name='Google Cloud Logging Agent Repository',
run_agent=textwrap.dedent("""\
#!/bin/bash -e
%(clear_prev_repo)s
for i in {1..5}; do
if (%(install)s); then
sudo service google-fluentd start
break
fi
sleep 1m
done"""),
win_run_agent=None,
recipe_name='set-google-fluentd-version',
current_major_version='1.*.*',
),
'metrics':
_AgentRuleTemplates(
install_with_version=(
'curl -sSO https://dl.google.com/cloudagents/add-monitoring-agent-repo.sh && '
'sudo bash add-monitoring-agent-repo.sh --also-install --version=%s'
),
yum_package=_PackageTemplates(
repo='google-cloud-monitoring-el%s-x86_64-%s',
clear_prev_repo=(
'sudo rm /etc/yum.repos.d/google-cloud-monitoring.repo || '
'true; find /var/cache/{yum,dnf} -name '
"'*google-cloud-monitoring*' | xargs sudo rm -rf || true"),
),
zypper_package=_PackageTemplates(
repo='google-cloud-monitoring-sles%s-x86_64-%s',
clear_prev_repo=(
'sudo rm /etc/zypp/repos.d/google-cloud-monitoring.repo || '
'true; find /var/cache/zypp -name '
"'*google-cloud-monitoring*' | xargs sudo rm -rf || true"),
),
apt_package=_PackageTemplates(
repo='google-cloud-monitoring-%s-%s',
clear_prev_repo=(
'sudo rm '
'/etc/apt/sources.list.d/google-cloud-monitoring.list || '
'true; find /var/cache/apt -name '
"'*stackdriver-agent*' | xargs sudo rm -rf || true"),
),
goo_package=None,
repo_id='google-cloud-monitoring',
display_name='Google Cloud Monitoring Agent Repository',
run_agent=textwrap.dedent("""\
#!/bin/bash -e
%(clear_prev_repo)s
for i in {1..5}; do
if (%(install)s); then
sudo service stackdriver-agent start
break
fi
sleep 1m
done"""),
win_run_agent=None,
recipe_name='set-stackdriver-agent-version',
current_major_version='6.*.*',
),
'ops-agent':
_AgentRuleTemplates(
install_with_version=(
'curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh && '
'sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=%s'
),
yum_package=_PackageTemplates(
repo='google-cloud-ops-agent-el%s-x86_64-%s',
clear_prev_repo=(
'sudo rm /etc/yum.repos.d/google-cloud-ops-agent.repo || '
'true; find /var/cache/{yum,dnf} -name '
"'*google-cloud-ops-agent*' | xargs sudo rm -rf || true"),
),
zypper_package=_PackageTemplates(
repo='google-cloud-ops-agent-sles%s-x86_64-%s',
clear_prev_repo=(
'sudo rm /etc/zypp/repos.d/google-cloud-ops-agent.repo || '
'true; find /var/cache/zypp -name '
"'*google-cloud-ops-agent*' | xargs sudo rm -rf || true"),
),
apt_package=_PackageTemplates(
repo='google-cloud-ops-agent-%s-%s',
clear_prev_repo=(
'sudo rm '
'/etc/apt/sources.list.d/google-cloud-ops-agent.list || '
'true; find /var/cache/apt -name '
"'*google-cloud-ops-agent*' | xargs sudo rm -rf || true"),
),
goo_package=_PackageTemplates(
repo='google-cloud-ops-agent-%s-%s',
clear_prev_repo=None,
),
repo_id='google-cloud-ops-agent',
display_name='Google Cloud Ops Agent Repository',
run_agent=textwrap.dedent("""\
#!/bin/bash -e
%(clear_prev_repo)s
for i in {1..5}; do
if (%(install)s); then
sudo systemctl start google-cloud-ops-agent.target || sudo service google-cloud-ops-agent restart
break
fi
sleep 1m
done"""),
win_run_agent=textwrap.dedent("""\
$Stoploop = $false
[int]$Retrycount = "0"
do {
googet --noconfirm remove google-cloud-ops-agent
Start-Sleep -Seconds 10
googet --noconfirm install google-cloud-ops-agent%s
if ( $? ) {
$Stoploop = $true
}
else {
Write-Output "Installing ops-agent failes, retrying..."
if ($Retrycount -gt 3) {
Write-Output "Retried 3 times already, failing..."
$Stoploop = $true
}
else {
Start-Sleep -Seconds 3
$Retrycount = $Retrycount + 1
}
}
}
while ($Stoploop -eq $false)"""),
recipe_name='set-ops-agent-version',
current_major_version='2.*.*',
),
}
_APT_CODENAMES = {
'8': 'jessie',
'9': 'stretch',
'10': 'buster',
'11': 'bullseye',
'12': 'bookworm',
'16.04': 'xenial',
'18.04': 'bionic',
'19.10': 'eoan',
'20.04': 'focal',
'21.04': 'hirsute',
'21.10': 'impish',
'22.04': 'jammy',
'23.04': 'lunar',
'23.10': 'mantic',
'24.04': 'noble',
'24.10': 'oracular',
}
_SUSE_OS = ('sles-sap', 'sles')
_YUM_OS = ('centos', 'rhel', 'rocky')
_APT_OS = ('debian', 'ubuntu')
_WINDOWS_OS = ('windows')
def _CreatePackages(messages, agent_rules, os_type):
"""Create OS Agent guest policy packages from Ops Agent policy agent field."""
packages = []
for agent_rule in agent_rules or []:
if agent_rule.type is agent_policy.OpsAgentPolicy.AgentRule.Type.LOGGING:
packages.append(
_CreatePackage(messages, 'google-fluentd', agent_rule.package_state,
agent_rule.enable_autoupgrade))
packages.append(
_CreatePackage(messages, 'google-fluentd-catch-all-config',
agent_rule.package_state,
agent_rule.enable_autoupgrade))
# apt os will start the service automatically without the start-service.
if os_type.short_name not in _APT_OS:
packages.append(
_CreatePackage(messages, 'google-fluentd-start-service',
agent_rule.package_state,
agent_rule.enable_autoupgrade))
if agent_rule.type is agent_policy.OpsAgentPolicy.AgentRule.Type.METRICS:
packages.append(
_CreatePackage(messages, 'stackdriver-agent',
agent_rule.package_state,
agent_rule.enable_autoupgrade))
# apt os will start the service automatically without the start-service.
if os_type.short_name not in _APT_OS:
packages.append(
_CreatePackage(messages, 'stackdriver-agent-start-service',
agent_rule.package_state,
agent_rule.enable_autoupgrade))
if agent_rule.type is agent_policy.OpsAgentPolicy.AgentRule.Type.OPS_AGENT:
packages.append(
_CreatePackage(messages, 'google-cloud-ops-agent',
agent_rule.package_state,
agent_rule.enable_autoupgrade))
return packages
def _CreatePackage(messages, package_name, package_state, enable_autoupgrade):
"""Creates package in guest policy.
Args:
messages: os config guest policy API messages.
package_name: package name.
package_state: package states.
enable_autoupgrade: True or False.
Returns:
package in guest policy.
"""
states = messages.Package.DesiredStateValueValuesEnum
desired_state = None
if (package_state
is agent_policy.OpsAgentPolicy.AgentRule.PackageState.INSTALLED):
if enable_autoupgrade:
desired_state = states.UPDATED
else:
desired_state = states.INSTALLED
elif (package_state
is agent_policy.OpsAgentPolicy.AgentRule.PackageState.REMOVED):
desired_state = states.REMOVED
return messages.Package(name=package_name, desiredState=desired_state)
def _CreatePackageRepositories(messages, os_type, agent_rules):
"""Create package repositories in guest policy.
Args:
messages: os config guest policy api messages.
os_type: it contains os_version, os_shortname.
agent_rules: list of agent rules which contains version, package_state, type
of {logging,metrics}.
Returns:
package repos in guest policy.
"""
package_repos = None
if os_type.short_name in _APT_OS:
package_repos = _CreateAptPkgRepos(
messages, _APT_CODENAMES.get(os_type.version), agent_rules)
elif os_type.short_name in _YUM_OS:
version = os_type.version.split('.')[0]
version = version.split('*')[0]
package_repos = _CreateYumPkgRepos(messages, version, agent_rules)
elif os_type.short_name in _SUSE_OS:
version = os_type.version.split('.')[0]
version = version.split('*')[0]
package_repos = _CreateZypperPkgRepos(messages, version, agent_rules)
elif os_type.short_name in _WINDOWS_OS:
package_repos = _CreateGooPkgRepos(messages, 'windows', agent_rules)
return package_repos
def _GetRepoSuffix(version):
return version.replace('.*.*', '') if '.*.*' in version else 'all'
def _CreateGooPkgRepos(messages, repo_distro, agent_rules):
goo_pkg_repos = []
for agent_rule in agent_rules:
template = _AGENT_RULE_TEMPLATES[agent_rule.type]
repo_name = template.goo_package.repo % (repo_distro,
_GetRepoSuffix(agent_rule.version))
goo_pkg_repos.append(_CreateGooPkgRepo(messages, repo_name))
return goo_pkg_repos
def _CreateGooPkgRepo(messages, repo_id):
"""Create a goo repo in guest policy.
Args:
messages: os config guest policy api messages.
repo_id: 'google-cloud-ops-agent-windows-[all|1]'.
Returns:
zoo repos in guest policy.
"""
return messages.PackageRepository(
goo=messages.GooRepository(
name=repo_id,
url='https://packages.cloud.google.com/yuck/repos/%s' % repo_id))
def _CreateZypperPkgRepos(messages, repo_distro, agent_rules):
zypper_pkg_repos = []
for agent_rule in agent_rules:
template = _AGENT_RULE_TEMPLATES[agent_rule.type]
repo_name = template.zypper_package.repo % (
repo_distro, _GetRepoSuffix(agent_rule.version))
zypper_pkg_repos.append(
_CreateZypperPkgRepo(messages, template.repo_id, template.display_name,
repo_name))
return zypper_pkg_repos
def _CreateZypperPkgRepo(messages, repo_id, display_name, repo_name):
"""Create a zypper repo in guest policy.
Args:
messages: os config guest policy api messages.
repo_id: 'google-cloud-logging' or 'google-cloud-monitoring'.
display_name: 'Google Cloud Logging Agent Repository' or 'Google Cloud
Monitoring Agent Repository'.
repo_name: repository name.
Returns:
zypper repos in guest policy.
"""
return messages.PackageRepository(
zypper=messages.ZypperRepository(
id=repo_id,
displayName=display_name,
baseUrl='https://packages.cloud.google.com/yum/repos/%s' % repo_name,
gpgKeys=[
'https://packages.cloud.google.com/yum/doc/yum-key.gpg',
'https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg'
]))
def _CreateYumPkgRepos(messages, repo_distro, agent_rules):
yum_pkg_repos = []
for agent_rule in agent_rules:
template = _AGENT_RULE_TEMPLATES[agent_rule.type]
repo_name = template.yum_package.repo % (
repo_distro, _GetRepoSuffix(agent_rule.version))
yum_pkg_repos.append(
_CreateYumPkgRepo(messages, template.repo_id, template.display_name,
repo_name))
return yum_pkg_repos
def _CreateYumPkgRepo(messages, repo_id, display_name, repo_name):
"""Create a yum repo in guest policy.
Args:
messages: os config guest policy api messages.
repo_id: 'google-cloud-logging' or 'google-cloud-monitoring'.
display_name: 'Google Cloud Logging Agent Repository' or 'Google Cloud
Monitoring Agent Repository'.
repo_name: repository name.
Returns:
yum repos in guest policy.
"""
return messages.PackageRepository(
yum=messages.YumRepository(
id=repo_id,
displayName=display_name,
baseUrl='https://packages.cloud.google.com/yum/repos/%s' % repo_name,
gpgKeys=[
'https://packages.cloud.google.com/yum/doc/yum-key.gpg',
'https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg'
]))
def _CreateAptPkgRepos(messages, repo_distro, agent_rules):
apt_pkg_repos = []
for agent_rule in agent_rules or []:
template = _AGENT_RULE_TEMPLATES[agent_rule.type]
repo_name = template.apt_package.repo % (
repo_distro, _GetRepoSuffix(agent_rule.version))
apt_pkg_repos.append(_CreateAptPkgRepo(messages, repo_name))
return apt_pkg_repos
def _CreateAptPkgRepo(messages, repo_name):
"""Create an apt repo in guest policy.
Args:
messages: os config guest policy api messages.
repo_name: repository name.
Returns:
An apt repo in guest policy.
"""
return messages.PackageRepository(
apt=messages.AptRepository(
uri='http://packages.cloud.google.com/apt',
distribution=repo_name,
components=['main'],
gpgKey='https://packages.cloud.google.com/apt/doc/apt-key.gpg'))
def _CreateOstypes(messages, assignment_os_types):
os_types = []
for assignment_os_type in assignment_os_types or []:
os_type = messages.AssignmentOsType(
osShortName=assignment_os_type.short_name,
osVersion=assignment_os_type.version)
os_types.append(os_type)
return os_types
def _CreateGroupLabel(messages, assignment_group_labels):
"""Create guest policy group labels.
Args:
messages: os config guest policy api messages.
assignment_group_labels: List of dict of key: value pair.
Returns:
group_labels in guest policy.
"""
group_labels = []
for group_label in assignment_group_labels or []:
pairs = [
messages.AssignmentGroupLabel.LabelsValue.AdditionalProperty(
key=key, value=value) for key, value in group_label.items()
]
group_labels.append(
messages.AssignmentGroupLabel(
labels=messages.AssignmentGroupLabel.LabelsValue(
additionalProperties=pairs)))
return group_labels
def _CreateAssignment(messages, assignment_group_labels, assignment_os_types,
assignment_zones, assignment_instances):
"""Creates a Assignment message from its components."""
return messages.Assignment(
groupLabels=_CreateGroupLabel(messages, assignment_group_labels),
zones=assignment_zones or [],
instances=assignment_instances or [],
osTypes=_CreateOstypes(messages, assignment_os_types))
def _GetRecipeVersion(prev_recipes, recipe_name):
for recipe in prev_recipes or []:
if recipe.name.startswith(recipe_name):
return str(int(recipe.version)+1)
return '0'
def _CreateRecipes(messages, agent_rules, os_type, prev_recipes):
"""Create recipes in guest policy.
Args:
messages: os config guest policy api messages.
agent_rules: ops agent policy agent rules.
os_type: ops agent policy os_type.
prev_recipes: a list of original SoftwareRecipe.
Returns:
Recipes in guest policy
"""
recipes = []
for agent_rule in agent_rules or []:
recipes.append(_CreateRecipe(messages, agent_rule, os_type, prev_recipes))
return recipes
def _CreateRecipe(messages, agent_rule, os_type, prev_recipes):
"""Create a recipe for one agent rule in guest policy.
Args:
messages: os config guest policy api messages.
agent_rule: ops agent policy agent rule.
os_type: ops agent policy os type.
prev_recipes: a list of original SoftwareRecipe.
Returns:
One software recipe in guest policy. If the package state is "removed", this
software recipe has an empty run script. We still keep the software recipe
to maintain versioning of the software recipe as the policy gets updated.
"""
version = _GetRecipeVersion(
prev_recipes, _AGENT_RULE_TEMPLATES[agent_rule.type].recipe_name)
return messages.SoftwareRecipe(
desiredState=messages.SoftwareRecipe.DesiredStateValueValuesEnum.UPDATED,
installSteps=[_CreateStepInScript(messages, agent_rule, os_type)],
name='%s-%s' % (
_AGENT_RULE_TEMPLATES[agent_rule.type].recipe_name, version),
version=version)
def _CreateStepInScript(messages, agent_rule, os_type):
"""Create scriptRun step in guest policy recipe section.
Args:
messages: os config guest policy api messages.
agent_rule: logging or metrics agent rule.
os_type: it contains os_version, os_short_name.
Returns:
Step of script to be run in Recipe section. If the package state is
"removed", this run script is empty. We still keep the software recipe to
maintain versioning of the software recipe as the policy gets updated.
"""
step = messages.SoftwareRecipeStep()
step.scriptRun = messages.SoftwareRecipeStepRunScript()
agent_version = '' if agent_rule.version == 'latest' else agent_rule.version
if os_type.short_name in _YUM_OS:
clear_prev_repo = _AGENT_RULE_TEMPLATES[
agent_rule.type].yum_package.clear_prev_repo
install_with_version = _AGENT_RULE_TEMPLATES[
agent_rule.type].install_with_version % agent_version
if os_type.short_name in _APT_OS:
clear_prev_repo = _AGENT_RULE_TEMPLATES[
agent_rule.type].apt_package.clear_prev_repo
install_with_version = _AGENT_RULE_TEMPLATES[
agent_rule.type].install_with_version % agent_version
if os_type.short_name in _SUSE_OS:
clear_prev_repo = _AGENT_RULE_TEMPLATES[
agent_rule.type].zypper_package.clear_prev_repo
install_with_version = _AGENT_RULE_TEMPLATES[
agent_rule.type].install_with_version % agent_version
if os_type.short_name in _WINDOWS_OS:
if agent_rule.version == 'latest' or '*.*' in agent_rule.version:
agent_version = ''
else:
agent_version = '.x86_64.%s@1' % agent_rule.version
# PackageState is REMOVED.
if (agent_rule.package_state
== agent_policy.OpsAgentPolicy.AgentRule.PackageState.REMOVED):
step.scriptRun.script = _EMPTY_SOFTWARE_RECIPE_SCRIPT
# PackageState is INSTALLED or UPDATED for Windows.
elif os_type.short_name in _WINDOWS_OS:
step.scriptRun.interpreter = messages.SoftwareRecipeStepRunScript.InterpreterValueValuesEnum.POWERSHELL
step.scriptRun.script = _AGENT_RULE_TEMPLATES[
agent_rule.type].win_run_agent % agent_version
# PackageState is INSTALLED or UPDATED for Linux.
else:
step.scriptRun.script = _AGENT_RULE_TEMPLATES[agent_rule.type].run_agent % {
'install': install_with_version,
'clear_prev_repo': clear_prev_repo
}
return step
def _CreateDescription(agent_rules, description):
"""Create description in guest policy.
Args:
agent_rules: agent rules in ops agent policy.
description: description in ops agent policy.
Returns:
description in guest policy.
"""
description_template = ('{"type": "ops-agents", "description": "%s", '
'"agentRules": [%s]}')
agent_contents = [agent_rule.ToJson() for agent_rule in agent_rules or []]
return description_template % (description, ','.join(agent_contents))
def _SetAgentVersion(agent_rules):
for agent_rule in agent_rules or []:
if agent_rule.version in {'current-major', None, ''}:
agent_rule.version = _AGENT_RULE_TEMPLATES[
agent_rule.type].current_major_version
def ConvertOpsAgentPolicyToGuestPolicy(messages, ops_agents_policy,
prev_recipes=None):
"""Converts Ops Agent policy to OS Config guest policy."""
ops_agents_policy_assignment = ops_agents_policy.assignment
_SetAgentVersion(ops_agents_policy.agent_rules)
# TODO(b/159365920): once os config supports multi repos, remove indexing [0].
guest_policy = messages.GuestPolicy(
description=_CreateDescription(ops_agents_policy.agent_rules,
ops_agents_policy.description),
etag=ops_agents_policy.etag,
assignment=_CreateAssignment(messages,
ops_agents_policy_assignment.group_labels,
ops_agents_policy_assignment.os_types,
ops_agents_policy_assignment.zones,
ops_agents_policy_assignment.instances),
packages=_CreatePackages(messages, ops_agents_policy.agent_rules,
ops_agents_policy_assignment.os_types[0]),
packageRepositories=_CreatePackageRepositories(
messages, ops_agents_policy_assignment.os_types[0],
ops_agents_policy.agent_rules),
recipes=_CreateRecipes(messages, ops_agents_policy.agent_rules,
ops_agents_policy.assignment.os_types[0],
prev_recipes))
return guest_policy

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Converter related function for Ops Agents Policy."""
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_policy as agents_policy
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_util as util
from googlecloudsdk.generated_clients.apis.osconfig.v1 import osconfig_v1_messages as osconfig
def ConvertOsPolicyAssignmentToCloudOpsAgentsPolicy(
os_policy_assignment: osconfig.OSPolicyAssignment,
) -> agents_policy.OpsAgentsPolicy:
"""Converts OS Config guest policy to Ops Agents policy.
A policy must have passed IsCloudOpsAgentsPolicy before this conversion.
Args:
os_policy_assignment: OS Config guest policy.
Returns:
Ops Agents policy.
"""
assert len(os_policy_assignment.osPolicies) == 1
description = os_policy_assignment.osPolicies[0].description
agents_rule = util.GetAgentsRuleFromDescription(description)
assert agents_rule is not None
return agents_policy.OpsAgentsPolicy(
policy_id=os_policy_assignment.name,
agents_rule=agents_rule,
instance_filter=os_policy_assignment.instanceFilter,
update_time=os_policy_assignment.revisionCreateTime,
rollout_state=os_policy_assignment.rolloutState,
)

View File

@@ -0,0 +1,273 @@
# A template for a single VMM OS policy that installs the given agent major version.
id: goog-ops-agent-policy
mode: ENFORCEMENT
allowNoResourceGroupMatch: true
resourceGroups:
- inventoryFilters:
- osShortName: rocky
osVersion: '8.*'
- osShortName: rhel
osVersion: '8.*'
resources:
- id: add-repo
repository:
yum:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-el8-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
yum:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: rocky
osVersion: '9.*'
- osShortName: rhel
osVersion: '9.*'
resources:
- id: add-repo
repository:
yum:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-el9-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
yum:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: sles
osVersion: '12.*'
resources:
- id: add-repo
repository:
zypper:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-sles12-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: import-key
exec:
validate:
script: "rpm --import https://packages.cloud.google.com/yum/doc/yum-key.gpg; rpm --import https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: install-pkg
pkg:
desiredState: INSTALLED
zypper:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: sles
osVersion: '15.*'
- osShortName: opensuse-leap
osVersion: '15.*'
resources:
- id: add-repo
repository:
zypper:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-sles15-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
zypper:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: debian
osVersion: '11'
resources:
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-bullseye-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: debian
osVersion: '12'
resources:
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-bookworm-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '18.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-bionic-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '20.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-focal-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '22.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-jammy-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '23.10'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-mantic-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '24.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-noble-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: INSTALLED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: windows
osVersion: '10.*'
- osShortName: windows
osVersion: '6.*'
resources:
- id: add-repo
repository:
goo:
name: Google Cloud Ops Agent
url: https://packages.cloud.google.com/yuck/repos/google-cloud-ops-agent-windows-$agent_version
- id: install-pkg
pkg:
desiredState: INSTALLED
googet:
name: google-cloud-ops-agent

View File

@@ -0,0 +1,197 @@
# A template for a single VMM OS policy that installs the given agent version.
id: goog-ops-agent-policy
mode: ENFORCEMENT
allowNoResourceGroupMatch: true
resourceGroups:
- inventoryFilters:
- osShortName: rocky
osVersion: '8.*'
- osShortName: rhel
osVersion: '8.*'
resources:
- id: install-agent
exec:
validate:
script: "[ $(rpm --query --queryformat '%{VERSION}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: rocky
osVersion: '9.*'
- osShortName: rhel
osVersion: '9.*'
resources:
- id: install-agent
exec:
validate:
script: "[ $(rpm --query --queryformat '%{VERSION}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: sles
osVersion: '12.*'
resources:
- id: install-agent
exec:
validate:
script: "[ $(rpm --query --queryformat '%{VERSION}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: sles
osVersion: '15.*'
- osShortName: opensuse-leap
osVersion: '15.*'
resources:
- id: install-agent
exec:
validate:
script: "[ $(rpm --query --queryformat '%{VERSION}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: debian
osVersion: '11'
resources:
- id: install-agent
exec:
validate:
script: "[ $(dpkg-query --show --showformat '${Version}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: debian
osVersion: '12'
resources:
- id: install-agent
exec:
validate:
script: "[ $(dpkg-query --show --showformat '${Version}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: ubuntu
osVersion: '18.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: install-agent
exec:
validate:
script: "[ $(dpkg-query --show --showformat '${Version}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: ubuntu
osVersion: '20.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: install-agent
exec:
validate:
script: "[ $(dpkg-query --show --showformat '${Version}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: ubuntu
osVersion: '22.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: install-agent
exec:
validate:
script: "[ $(dpkg-query --show --showformat '${Version}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: ubuntu
osVersion: '23.10'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: install-agent
exec:
validate:
script: "[ $(dpkg-query --show --showformat '${Version}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: ubuntu
osVersion: '24.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: install-agent
exec:
validate:
script: "[ $(dpkg-query --show --showformat '${Version}' 'google-cloud-ops-agent' | cut -d~ -f 1) == '$agent_version' ] && exit 100 || exit 101;"
interpreter: SHELL
enforce:
script: curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh; sudo bash add-google-cloud-ops-agent-repo.sh --also-install --version=$agent_version;
interpreter: SHELL
- inventoryFilters:
- osShortName: windows
osVersion: '10.*'
- osShortName: windows
osVersion: '6.*'
resources:
- id: install-agent
exec:
validate:
script: if (((((googet installed google-cloud-ops-agent) -split ' ')[-1]) -split '@')[0] -eq '$agent_version'){ exit 100; } else { exit 101;}
interpreter: POWERSHELL
enforce:
script: '(New-Object Net.WebClient).DownloadFile("https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.ps1", "${env:UserProfile}\add-google-cloud-ops-agent-repo.ps1"); Invoke-Expression "${env:UserProfile}\add-google-cloud-ops-agent-repo.ps1 -AlsoInstall -Version $agent_version";'
interpreter: POWERSHELL

View File

@@ -0,0 +1,273 @@
# A template for a single VMM OS policy that uninstalls the given agent major version.
id: goog-ops-agent-policy
mode: ENFORCEMENT
allowNoResourceGroupMatch: true
resourceGroups:
- inventoryFilters:
- osShortName: rocky
osVersion: '8.*'
- osShortName: rhel
osVersion: '8.*'
resources:
- id: add-repo
repository:
yum:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-el8-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
yum:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: rocky
osVersion: '9.*'
- osShortName: rhel
osVersion: '9.*'
resources:
- id: add-repo
repository:
yum:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-el9-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
yum:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: sles
osVersion: '12.*'
resources:
- id: add-repo
repository:
zypper:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-sles12-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: import-key
exec:
validate:
script: "rpm --import https://packages.cloud.google.com/yum/doc/yum-key.gpg; rpm --import https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: install-pkg
pkg:
desiredState: REMOVED
zypper:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: sles
osVersion: '15.*'
- osShortName: opensuse-leap
osVersion: '15.*'
resources:
- id: add-repo
repository:
zypper:
id: google-cloud-ops-agent
displayName: Google Cloud Ops Agent Repository
baseUrl: https://packages.cloud.google.com/yum/repos/google-cloud-ops-agent-sles15-x86_64-$agent_version
gpgKeys:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
zypper:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: debian
osVersion: '11'
resources:
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-bullseye-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: debian
osVersion: '12'
resources:
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-bookworm-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '18.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-bionic-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '20.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-focal-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '22.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-jammy-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '23.10'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-mantic-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: ubuntu
osVersion: '24.04'
resources:
- id: wait-for-cloud-init
exec:
validate:
script: "cloud-init status --wait; exit 100;"
interpreter: SHELL
enforce:
script: "echo hello"
interpreter: SHELL
- id: add-repo
repository:
apt:
archiveType: DEB
uri: https://packages.cloud.google.com/apt
distribution: google-cloud-ops-agent-noble-$agent_version
components:
- main
gpgKey: https://packages.cloud.google.com/apt/doc/apt-key.gpg
- id: install-pkg
pkg:
desiredState: REMOVED
apt:
name: google-cloud-ops-agent
- inventoryFilters:
- osShortName: windows
osVersion: '10.*'
- osShortName: windows
osVersion: '6.*'
resources:
- id: add-repo
repository:
goo:
name: Google Cloud Ops Agent
url: https://packages.cloud.google.com/yuck/repos/google-cloud-ops-agent-windows-$agent_version
- id: install-pkg
pkg:
desiredState: REMOVED
googet:
name: google-cloud-ops-agent

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Errors for the compute VM instances Ops Agents commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.core import exceptions
import six
class Error(exceptions.Error):
"""Base exception for Ops Agents exceptions."""
class PolicyError(Error):
"""Base exception for Ops Agents policy exceptions."""
class PolicyMalformedError(PolicyError):
"""Raised when the specified Ops Agents policy is malformed."""
def __init__(self, policy_id):
message = (
'Encountered a malformed policy. The Ops Agents policy [{policy_id}] '
'may have been modified directly by the OS Config guest policy API / '
'gcloud commands. If so, please delete and re-create with the Ops '
'Agents policy gcloud commands. If not, this may be an internal error.'
.format(policy_id=policy_id))
super(PolicyMalformedError, self).__init__(message)
class PolicyNotFoundError(PolicyError):
"""Raised when the specified Ops Agents policy is not found."""
def __init__(self, policy_id):
message = (
'Ops Agents policy [{policy_id}] not found.'
.format(policy_id=policy_id))
super(PolicyNotFoundError, self).__init__(message)
class PolicyValidationError(PolicyError):
"""Raised when Ops Agents policy validation fails."""
class PolicyValidationMultiError(PolicyValidationError):
"""Raised when multiple Ops Agents policy validations fail."""
def __init__(self, errors):
super(PolicyValidationMultiError, self).__init__(
' | '.join(six.text_type(error) for error in errors))
self.errors = errors

View File

@@ -0,0 +1,279 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This class will store in-memory instance of ops agent policy."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import enum
import json
import sys
from googlecloudsdk.core.resource import resource_property
_StrEnum = (
(enum.StrEnum,) if sys.version_info[:2] >= (3, 11) else (str, enum.Enum)
)
class OpsAgentPolicy(object):
"""An Ops Agent policy encapsulates the underlying OS Config Guest Policy."""
class AgentRule(object):
"""An Ops agent rule contains agent type, version, enable_autoupgrade."""
class Type(*_StrEnum):
LOGGING = 'logging'
METRICS = 'metrics'
OPS_AGENT = 'ops-agent'
class PackageState(*_StrEnum):
INSTALLED = 'installed'
REMOVED = 'removed'
class Version(*_StrEnum):
LATEST_OF_ALL = 'latest'
CURRENT_MAJOR = 'current-major'
def __init__(self,
agent_type,
enable_autoupgrade,
version=Version.CURRENT_MAJOR,
package_state=PackageState.INSTALLED):
"""Initialize AgentRule instance.
Args:
agent_type: Type, agent type to be installed.
enable_autoupgrade: bool, enable autoupgrade for the package or
not.
version: str, agent version, e.g. 'latest', '5.5.2', '5.*.*'.
package_state: Optional PackageState, desiredState for the package.
"""
self.type = agent_type
self.enable_autoupgrade = enable_autoupgrade
self.version = version
self.package_state = package_state
def __eq__(self, other):
return self.__dict__ == other.__dict__
def ToJson(self):
"""Generate JSON with camel-cased key."""
key_camel_cased_dict = {
resource_property.ConvertToCamelCase(key): value
for key, value in self.__dict__.items()
}
return json.dumps(key_camel_cased_dict, default=str, sort_keys=True)
class Assignment(object):
"""The group or groups of VM instances that the policy applies to."""
class OsType(object):
"""The criteria for selecting VM Instances by OS type."""
class OsShortName(*_StrEnum):
CENTOS = 'centos'
DEBIAN = 'debian'
WINDOWS = 'windows'
RHEL = 'rhel'
ROCKY = 'rocky'
SLES = 'sles'
SLES_SAP = 'sles-sap'
UBUNTU = 'ubuntu'
def __init__(self, short_name, version):
"""Initialize OsType instance.
Args:
short_name: str, OS distro name, e.g. 'centos', 'debian'.
version: str, OS version, e.g. '19.10', '7', '7.8'.
"""
self.short_name = short_name
self.version = version
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __init__(self, group_labels, zones, instances, os_types):
"""Initialize Assignment Instance.
Args:
group_labels: list of dict, VM group label matchers, or None.
zones: list, VM zone matchers, or None.
instances: list, instance name matchers, or None.
os_types: OsType, VM OS type matchers, or None.
"""
self.group_labels = group_labels or []
self.zones = zones or []
self.instances = instances or []
self.os_types = os_types or []
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __init__(self,
assignment,
agent_rules,
description,
etag,
name,
update_time,
create_time):
"""Initialize an ops agent policy instance.
Args:
assignment: Assignment, selection criteria for applying policy to VMs.
agent_rules: list of AgentRule, the agent rules to be applied to VMs.
description: str, user specified description of the policy.
etag: str, unique tag for policy, generated by the API, or None.
name: str, user specified name of the policy, or None.
update_time: str, update time in RFC3339 format, or None.
create_time: str, create time in RFC3339 format, or None.
"""
self.assignment = assignment
self.agent_rules = agent_rules
self.description = description
self.etag = etag
self.id = name
self.update_time = update_time
self.create_time = create_time
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __repr__(self):
"""JSON format string representation for testing."""
return json.dumps(self, default=lambda o: o.__dict__,
indent=2, separators=(',', ': '), sort_keys=True)
def CreateOsTypes(os_types):
"""Create Os Types in Ops Agent Policy.
Args:
os_types: dict, VM OS type matchers, or None.
Returns:
A list of OpsAgentPolicy.Assignment.OsType objects.
"""
OsType = OpsAgentPolicy.Assignment.OsType # pylint: disable=invalid-name
return [
OsType(OsType.OsShortName(os_type['short-name']), os_type['version'])
for os_type in os_types or []
]
def CreateAgentRules(agent_rules):
"""Create agent rules in ops agent policy.
Args:
agent_rules: list of dict, fields describing agent rules from the command
line.
Returns:
An OpsAgentPolicy.AgentRules object.
"""
ops_agents = []
for agent_rule in agent_rules or []:
ops_agents.append(
OpsAgentPolicy.AgentRule(
OpsAgentPolicy.AgentRule.Type(agent_rule['type']),
agent_rule['enable-autoupgrade'],
agent_rule.get('version',
OpsAgentPolicy.AgentRule.Version.CURRENT_MAJOR),
OpsAgentPolicy.AgentRule.PackageState(agent_rule.get(
'package-state',
OpsAgentPolicy.AgentRule.PackageState.INSTALLED))))
return ops_agents
def CreateOpsAgentPolicy(description, agent_rules, group_labels, os_types,
zones, instances):
"""Create Ops Agent Policy.
Args:
description: str, ops agent policy description.
agent_rules: list of dict, fields describing agent rules from the command
line.
group_labels: list of dict, VM group label matchers.
os_types: dict, VM OS type matchers.
zones: list, VM zone matchers.
instances: list, instance name matchers.
Returns:
ops agent policy.
"""
return OpsAgentPolicy(
assignment=OpsAgentPolicy.Assignment(
group_labels=group_labels,
zones=zones,
instances=instances,
os_types=CreateOsTypes(os_types)),
agent_rules=CreateAgentRules(agent_rules),
description=description,
etag=None,
name=None,
update_time=None,
create_time=None)
def UpdateOpsAgentsPolicy(ops_agents_policy, description, etag,
agent_rules, os_types, group_labels,
zones, instances):
"""Merge existing ops agent policy with user updates.
Unless explicitly mentioned, a None value means "leave unchanged".
Args:
ops_agents_policy: OpsAgentPolicy, ops agent policy.
description: str, ops agent policy description, or None.
etag: str, unique tag for policy to prevent race conditions, or None.
agent_rules: list of dict, fields describing agent rules from the command
line, or None. An empty list means the same as None.
os_types: dict, VM OS type matchers, or None.
An empty dict means the same as None.
group_labels: list of dict, VM group label matchers, or None.
zones: list of zones, VM zone matchers, or None.
instances: list of instances, instance name matchers, or None.
Returns:
Updated ops agents policy.
"""
updated_description = (
ops_agents_policy.description if description is None else description)
# TODO(b/164141164): Decide what should happen when etag=''.
# Unless supplied, keep the etag from the last policy read from the server.
# If the etag is stale, the RPC will error out to prevent race conditions.
updated_etag = ops_agents_policy.etag if etag is None else etag
assignment = ops_agents_policy.assignment
updated_assignment = OpsAgentPolicy.Assignment(
group_labels=(
assignment.group_labels if group_labels is None else group_labels),
zones=assignment.zones if zones is None else zones,
instances=assignment.instances if instances is None else instances,
os_types=CreateOsTypes(os_types) or assignment.os_types)
updated_agent_rules = (
CreateAgentRules(agent_rules) or ops_agents_policy.agent_rules)
return OpsAgentPolicy(
assignment=updated_assignment,
agent_rules=updated_agent_rules,
description=updated_description,
etag=updated_etag,
name=ops_agents_policy.id,
update_time=None,
create_time=ops_agents_policy.create_time)

View File

@@ -0,0 +1,327 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common validators for cloud ops agents policy create and update commands."""
import enum
import re
import sys
from typing import Set
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_exceptions as exceptions
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_policy as agents_policy
from googlecloudsdk.api_lib.compute.instances.ops_agents import cloud_ops_agents_util as util
from googlecloudsdk.core import log
from googlecloudsdk.generated_clients.apis.osconfig.v1 import osconfig_v1_messages as osconfig
_VERSION_RE = re.compile(
'|'.join((
'latest',
r'2\.\*\.\*', # Pinned major version.
r'2\.\d+\.\d+', # Pinned version.
))
)
_SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS = {
'centos': {
('7',),
('8',),
},
'rhel': {
('7',),
('8',),
('9',),
},
'rocky': {
('8',),
('9',),
},
'sles': {
('12',),
('15',),
},
'debian': {
('10',),
('11',),
('12',),
},
'ubuntu': {
('18', '04'),
('20', '04'),
('22', '04'),
('23', '10'),
},
'windows': {
('6',),
('10',),
},
}
_SUPPORTED_PACKAGE_STATE = frozenset({
'installed',
'removed',
})
_StrEnum = (
(enum.StrEnum,) if sys.version_info[:2] >= (3, 11) else (str, enum.Enum)
)
class AgentsInstanceFilterConflictErrorMessage(*_StrEnum):
ALL_TRUE = (
'No other values can be declared under instanceFilter if all is set'
' to true'
)
EMPTY_INSTANCE_FILTER = (
'There should be at least a single value in either'
' instanceFilter.inclusionLabels, instanceFilter.exclusionLabels or'
' instanceFilter.inventories'
)
class AgentsVersionInvalidFormatError(exceptions.PolicyValidationError):
"""Raised when agents version format is invalid."""
def __init__(self, version):
super(AgentsVersionInvalidFormatError, self).__init__(
'The agents version [{}] is not allowed. Expected values: [latest], '
'or anything in the format of '
'[MAJOR_VERSION.MINOR_VERSION.PATCH_VERSION] or '
'[MAJOR_VERSION.*.*].'.format(version)
)
class AgentsPackageStateInvalidFormatError(exceptions.PolicyValidationError):
"""Raised when agents package_state format is invalid."""
def __init__(self, package_state):
super(AgentsPackageStateInvalidFormatError, self).__init__(
'The agents packageState [{}] is not allowed. Expected values:'
' [installed] or [removed] '.format(package_state)
)
class AgentsInstanceFilterEmptyError(exceptions.PolicyValidationError):
"""Raised when instance_filter is empty."""
def __init__(self):
super(AgentsInstanceFilterEmptyError, self).__init__(
'instanceFilter cannot be empty'
)
class AgentsInstanceFilterConflictError(exceptions.PolicyValidationError):
"""Raised when an invalid instance_filter is created."""
def __init__(self, error_message: AgentsInstanceFilterConflictErrorMessage):
super(AgentsInstanceFilterConflictError, self).__init__(
'Invalid instanceFilter: {}'.format(error_message)
)
class AgentsOsTypeNotSupportedError(exceptions.PolicyValidationError):
"""Raised when agents OS type is not supported."""
def __init__(self, short_name: str, version: str):
super(AgentsOsTypeNotSupportedError, self).__init__(
'The combination of short name [{}] and version [{}] is not supported. '
'The supported versions are: {}.'.format(
short_name,
version,
'; '.join(
'%s %s' % (k, ','.join(sorted('.'.join(e) for e in v)))
for k, v in sorted(
_SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS.items()
)
),
)
)
def ValidateOpsAgentsPolicy(policy: agents_policy.OpsAgentsPolicy):
"""Validates semantics of a Cloud Ops agents policy.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is an OpsAgentsPolicy object.
Args:
policy: ops_agents.OpsAgentPolicy. The policy that manages Ops agents.
Raises:
PolicyValidationMultiError that contains a list of validation
errors from the following list.
* AgentsVersionInvalidFormatError:
Agents version format is invalid.
* AgentsPackageStateInvalidFormatError:
Agents package_state format is invalid.
* AgentsInstanceFilterEmptyError:
Instance filter format is empty.
* AgentsInstanceFilterConflictError:
Instance filter must have all set to true with nothing else added or there
should be at least a single value in either
inclusionLabels, exclusionLabels or
inventories
* AgentsOsTypeNotSupportedError:
The combination of the OS short name and version is not supported.
"""
errors = _ValidateAgentRules(policy.agents_rule) + _ValidateInstanceFilter(
policy.instance_filter
)
if errors:
raise exceptions.PolicyValidationMultiError(errors)
log.debug(f'Cloud Ops Agents policy validation passed.\n{policy}')
def _ValidateAgentRules(agents_rule: agents_policy.OpsAgentsPolicy.AgentsRule):
return _ValidateAgentsRuleVersion(
agents_rule.version, agents_rule.package_state
) + _ValidateAgentsRulePackageState(agents_rule.package_state)
def _ValidateAgentsRuleVersion(
version: str,
package_state: str,
) -> Set[AgentsVersionInvalidFormatError]:
if not (
(package_state == 'removed' and not version)
or _VERSION_RE.fullmatch(version)
):
return [AgentsVersionInvalidFormatError(version)]
return []
def _ValidateAgentsRulePackageState(
package_state: str,
) -> Set[AgentsPackageStateInvalidFormatError]:
if package_state not in _SUPPORTED_PACKAGE_STATE:
return [AgentsPackageStateInvalidFormatError(package_state)]
return []
def _ValidateInstanceFilter(
instance_filter: osconfig.OSPolicyAssignmentInstanceFilter,
):
return (
_ValidateInstanceFilterIsNotEmpty(instance_filter)
+ _ValidateInstanceFilterAllTrue(instance_filter)
+ _ValidateInstanceFilterAllFalse(instance_filter)
+ _ValidateInventories(instance_filter)
)
def _ValidateInstanceFilterIsNotEmpty(
instance_filter: osconfig.OSPolicyAssignmentInstanceFilter,
) -> Set[AgentsInstanceFilterEmptyError]:
return [] if instance_filter else [AgentsInstanceFilterEmptyError()]
def _ValidateInstanceFilterAllTrue(
instance_filter: osconfig.OSPolicyAssignmentInstanceFilter,
) -> Set[AgentsInstanceFilterConflictError]:
"""Validates that if instance_filter.all is true no other values are present.
Args:
instance_filter: cloud ops agents instance filter.
Returns:
An empty list if the validation passes. A singleton list with the following
error if the validation fails.
* AgentsInstanceFilterConflictError:
Instance filter must have all set to true with nothing else added or there
should be at least a single value in either inclusionLabels,
exclusionLabels or inventories.
"""
if instance_filter.all and (
instance_filter.inclusionLabels
or instance_filter.exclusionLabels
or instance_filter.inventories
):
return [
AgentsInstanceFilterConflictError(
AgentsInstanceFilterConflictErrorMessage.ALL_TRUE
)
]
return []
def _ValidateInstanceFilterAllFalse(
instance_filter: osconfig.OSPolicyAssignmentInstanceFilter,
) -> Set[AgentsInstanceFilterConflictError]:
"""Validates that if instance_filter.all is false that there is a value in either inclusionLabels, exclusionLabels, or inventories.
Args:
instance_filter: cloud ops agents instance filter.
Returns:
An empty list if the validation passes. A singleton list with the following
error if the validation fails.
* AgentsInstanceFilterConflictError:
There should be at least a single value in either inclusionLabels,
exclusionLabels or inventories.
"""
if (
not instance_filter.all
and not instance_filter.inclusionLabels
and not instance_filter.exclusionLabels
and not instance_filter.inventories
):
return [
AgentsInstanceFilterConflictError(
AgentsInstanceFilterConflictErrorMessage.EMPTY_INSTANCE_FILTER
)
]
return []
def _ValidateInventories(
instance_filter: osconfig.OSPolicyAssignmentInstanceFilter,
) -> Set[AgentsOsTypeNotSupportedError]:
"""Validates that inventories only contain Ops Agents supported OS types and version.
Args:
instance_filter: cloud ops agents instance filter.
Returns:
An empty list if the validation passes. A list with the following
error if the validation fails.
* AgentsOsTypeNotSupportedError:
The combination of the OS short name and version is not supported.
"""
errors = []
for inventory in instance_filter.inventories:
if not (
inventory.osShortName in _SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS
and any(
v == tuple(inventory.osVersion.split('.')[:len(v)])
for v in _SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS[
inventory.osShortName
]
)
):
errors.append(
AgentsOsTypeNotSupportedError(
inventory.osShortName, inventory.osVersion
)
)
return errors
def IsCloudOpsAgentsPolicy(policy: osconfig.OSPolicyAssignment) -> bool:
"""Returns if the policy was created with the Ops Agent command.
"""
return (
len(policy.osPolicies) == 1
and util.GetAgentsRuleFromDescription(policy.osPolicies[0].description)
)

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""OS Config Policies validation functions for Ops Agents Policy."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
_GUEST_POLICY_TYPE_OPS_AGENT = 'ops-agents'
def IsOpsAgentPolicy(guest_policy):
"""Validate whether an OS Conifg guest policy is an Ops Agent Policy.
Args:
guest_policy: Client message of OS Config guest policy.
Returns:
True if it is an Ops Agent Policy type OS Config guest policy.
"""
if guest_policy.description is None:
return False
try:
guest_policy_description = json.loads(guest_policy.description)
except ValueError:
return False
return (isinstance(guest_policy_description, dict) and
'type' in guest_policy_description and
guest_policy_description['type'] == _GUEST_POLICY_TYPE_OPS_AGENT)

View File

@@ -0,0 +1,544 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common validators for ops agents policy create and update commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import collections
import json
import re
from googlecloudsdk.api_lib.compute.instances.ops_agents import exceptions
from googlecloudsdk.api_lib.compute.instances.ops_agents import ops_agents_policy as agent_policy
from googlecloudsdk.core import log
# TODO(b/163135147): Treat 0-prefixed versions as invalid.
_PINNED_MAJOR_VERSION_RE = re.compile(r'^\d+\.\*\.\*$')
_PINNED_LEGACY_VERSION_RE = re.compile(r'^5\.5\.2-\d+$')
_PINNED_VERSION_RE = re.compile(r'^\d+\.\d+\.\d+$')
_SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS = {
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.CENTOS: [
'7',
'8',
],
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.DEBIAN: [
'9',
'10',
'11',
'12',
],
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.RHEL: [
'7',
'8',
'9',
],
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.ROCKY: [
'8',
'9',
],
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES: [
'12',
'15',
],
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES_SAP: [
'12',
'15',
],
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.UBUNTU: [
'16.04',
'18.04',
'19.10',
'20.04',
'21.04',
'21.10',
'22.04',
'23.04',
'23.10',
'24.04',
'24.10',
],
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.WINDOWS: [
'10',
'6',
],
}
_SUPPORTED_OS_SHORT_NAMES_AND_AGENT_TYPES = {
agent_policy.OpsAgentPolicy.AgentRule.Type.LOGGING: [
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.CENTOS,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.DEBIAN,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.RHEL,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.ROCKY,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES_SAP,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.UBUNTU,
],
agent_policy.OpsAgentPolicy.AgentRule.Type.METRICS: [
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.CENTOS,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.DEBIAN,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.RHEL,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.ROCKY,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES_SAP,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.UBUNTU,
],
agent_policy.OpsAgentPolicy.AgentRule.Type.OPS_AGENT: [
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.CENTOS,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.DEBIAN,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.RHEL,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.ROCKY,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.SLES_SAP,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.UBUNTU,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.WINDOWS,
],
}
_OS_SHORT_NAMES_WITH_OS_AGENT_PREINSTALLED = (
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.CENTOS,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.DEBIAN,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.RHEL,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.ROCKY,
agent_policy.OpsAgentPolicy.Assignment.OsType.OsShortName.WINDOWS,
)
_SUPPORTED_AGENT_MAJOR_VERSIONS = {
'logging': ('1',),
'metrics': ('5', '6'),
'ops-agent': ('1', '2'),
}
class AgentTypesUniquenessError(exceptions.PolicyValidationError):
"""Raised when agent type is not unique."""
def __init__(self, agent_type):
super(AgentTypesUniquenessError, self).__init__(
'At most one agent with type [{}] is allowed.'.format(agent_type))
class AgentTypesConflictError(exceptions.PolicyValidationError):
"""Raised when agent type is ops-agent and another agent type is specified."""
def __init__(self):
super(AgentTypesConflictError, self).__init__(
'An agent with type [ops-agent] is detected. No other agent type is '
'allowed. The Ops Agent has both a logging module and a metrics module '
'already.')
class AgentVersionInvalidFormatError(exceptions.PolicyValidationError):
"""Raised when agent version format is invalid."""
def __init__(self, version):
super(AgentVersionInvalidFormatError, self).__init__(
'The agent version [{}] is not allowed. Expected values: [latest], '
'[current-major], or anything in the format of '
'[MAJOR_VERSION.MINOR_VERSION.PATCH_VERSION] or '
'[MAJOR_VERSION.*.*].'.format(version))
class AgentUnsupportedMajorVersionError(exceptions.PolicyValidationError):
"""Raised when agent's major version is not supported."""
def __init__(self, agent_type, version):
supported_versions = _SUPPORTED_AGENT_MAJOR_VERSIONS[agent_type]
super(AgentUnsupportedMajorVersionError, self).__init__(
'The agent major version [{}] is not supported for agent type [{}]. '
'Supported major versions are: {}'.format(
version, agent_type, ', '.join(supported_versions)))
class AgentVersionAndEnableAutoupgradeConflictError(
exceptions.PolicyValidationError):
"""Raised when agent version is pinned but autoupgrade is enabled."""
def __init__(self, version):
super(AgentVersionAndEnableAutoupgradeConflictError, self).__init__(
'An agent can not be pinned to the specific version [{}] when '
'[enable-autoupgrade] is set to true for that agent.'.format(version))
class OsTypesMoreThanOneError(exceptions.PolicyValidationError):
"""Raised when more than one OS types are specified."""
def __init__(self):
super(OsTypesMoreThanOneError, self).__init__(
'Only one OS type is allowed in the instance filters.')
class OsTypeNotSupportedError(exceptions.PolicyValidationError):
"""Raised when the OS short name and version combination is not supported."""
def __init__(self, short_name, version):
super(OsTypeNotSupportedError, self).__init__(
'The combination of short name [{}] and version [{}] is not supported. '
'The supported versions are: {}.'.format(
short_name, version, json.dumps(
_SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS)))
class OSTypeNotSupportedByAgentTypeError(exceptions.PolicyValidationError):
"""Raised when the OS short name and agent type combination is not supported."""
def __init__(self, short_name, agent_type):
super(OSTypeNotSupportedByAgentTypeError, self).__init__(
'The combination of short name [{}] and agent type [{}] is not supported'
'. The supported combinations are: {}.'.format(
short_name, agent_type,
json.dumps(_SUPPORTED_OS_SHORT_NAMES_AND_AGENT_TYPES)))
def ValidateOpsAgentsPolicy(policy):
"""Validates semantics of an Ops agents policy.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is an OpsAgentPolicy object.
Args:
policy: ops_agents.OpsAgentPolicy. The policy that manages Ops agents.
Raises:
PolicyValidationMultiError that contains a list of validation
errors from the following list.
* AgentTypesUniquenessError:
Multiple agents with the same type are specified.
* AgentTypesConflictError:
More than one agent type is specified when there is already a type
ops-agent.
* AgentVersionInvalidFormatError:
Agent version format is invalid.
* AgentVersionAndEnableAutoupgradeConflictError:
Agent version is pinned but autoupgrade is enabled.
* OsTypesMoreThanOneError:
More than one OS types are specified.
* OsTypeNotSupportedError:
The combination of the OS short name and version is not supported.
* OSTypeNotSupportedByAgentTypeError:
The combination of the OS short name and agent type is not supported.
"""
errors = (
_ValidateAgentRules(policy.agent_rules) +
_ValidateOsTypes(policy.assignment.os_types) +
_ValidateAgentRulesAndOsTypes(policy.agent_rules,
policy.assignment.os_types))
if errors:
raise exceptions.PolicyValidationMultiError(errors)
log.debug('Ops Agents policy validation passed.')
def _ValidateAgentRulesAndOsTypes(agent_rules, os_types):
"""Validates semantics of the ops-agents-policy.os-types field and the ops-agents-policy.agent-rules field.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is a list of OpsAgentPolicy.Assignment.OsType objects.
The other field is a list of OpsAgentPolicy.AgentRule object. Each
OpsAgentPolicy object's 'type' field already complies with the allowed values.
Args:
agent_rules: list of OpsAgentPolicy.AgentRule. The list of agent rules to be
managed by the Ops Agents policy.
os_types: list of OpsAgentPolicy.Assignment.OsType. The list of OS types as
part of the instance filters that the Ops Agent policy applies to the Ops
Agents policy.
Returns:
An empty list if the validation passes. A list of errors from the following
list if the validation fails.
* OSTypeNotSupportedByAgentTypeError:
The combination of the OS short name and agent type is not supported.
"""
errors = []
for os_type in os_types:
for agent_rule in agent_rules:
errors.extend(
_ValidateAgentTypeAndOsShortName(os_type.short_name, agent_rule.type))
return errors
def _ValidateAgentTypeAndOsShortName(os_short_name, agent_type):
"""Validates the combination of the OS short name and agent type is supported.
This validation happens after the arg parsing stage. At this point, we can
assume that the field OS short name has been already validated at the arg
parsing stage. Also the
other field is OpsAgentPolicy object's 'type' field already complies with the
allowed values.
Args:
os_short_name: str. The OS short name to filter instances by.
agent_type: str. The AgentRule type.
Returns:
An empty list if the validation passes. A singleton list with the following
error if the validation fails.
* OSTypeNotSupportedByAgentTypeError:
The combination of the OS short name and agent type is not supported.
"""
supported_os_list = _SUPPORTED_OS_SHORT_NAMES_AND_AGENT_TYPES.get(agent_type)
if os_short_name not in supported_os_list:
return [OSTypeNotSupportedByAgentTypeError(os_short_name, agent_type)]
return []
def _ValidateAgentRules(agent_rules):
"""Validates semantics of the ops-agents-policy.agent-rules field.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is a list of OpsAgentPolicy.AgentRule object.
Args:
agent_rules: list of OpsAgentPolicy.AgentRule. The list of agent rules to be
managed by the Ops Agents policy.
Returns:
An empty list if the validation passes. A list of errors from the following
list if the validation fails.
* AgentTypesUniquenessError:
Multiple agents with the same type are specified.
* AgentTypesConflictError:
More than one agent type is specified when there is already a type
ops-agent.
* AgentVersionInvalidFormatError:
Agent version format is invalid.
* AgentVersionAndEnableAutoupgradeConflictError:
Agent version is pinned but autoupgrade is enabled.
"""
errors = _ValidateAgentTypesUniqueness(agent_rules)
errors.extend(_ValidateAgentTypesConflict(agent_rules))
for agent_rule in agent_rules:
errors.extend(_ValidateAgentRule(agent_rule))
return errors
def _ValidateAgentTypesUniqueness(agent_rules):
"""Validates that each type of agent occurs at most once.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is a list of OpsAgentPolicy.AgentRule object. Each
OpsAgentPolicy object's 'type' field already complies with the allowed values.
Args:
agent_rules: list of OpsAgentPolicy.AgentRule. The list of agent rules to be
managed by the Ops Agents policy.
Returns:
An empty list if the validation passes. A list that contains one or more
errors below if the validation fails.
* AgentTypesUniquenessError:
Multiple agents with the same type are specified.
"""
agent_types = collections.Counter(
agent_rule.type for agent_rule in agent_rules)
duplicate_types = [k for k, v in agent_types.items() if v > 1]
return [AgentTypesUniquenessError(t) for t in sorted(duplicate_types)]
def _ValidateAgentTypesConflict(agent_rules):
"""Validates that when agent type is ops-agent, it is the only agent type.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is a list of OpsAgentPolicy.AgentRule object. Each
OpsAgentPolicy object's 'type' field already complies with the allowed values.
Args:
agent_rules: list of OpsAgentPolicy.AgentRule. The list of agent rules to be
managed by the Ops Agents policy.
Returns:
An empty list if the validation passes. A list that contains one or more
errors below if the validation fails.
* AgentTypesConflictError:
More than one agent type is specified when there is already a type
ops-agent.
"""
agent_types = {agent_rule.type for agent_rule in agent_rules}
if agent_policy.OpsAgentPolicy.AgentRule.Type.OPS_AGENT in agent_types and len(
agent_types) > 1:
return [AgentTypesConflictError()]
return []
def _ValidateAgentRule(agent_rule):
"""Validates semantics of an individual OpsAgentPolicy.AgentRule.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is an OpsAgentPolicy.AgentRule object.
Args:
agent_rule: OpsAgentPolicy.AgentRule. The agent rule to enforce by the Ops
Agents policy.
Returns:
An empty list if the validation passes. A list of errors from the following
list if the validation fails.
* AgentVersionInvalidFormatError:
Agent version format is invalid.
* AgentVersionAndEnableAutoupgradeConflictError:
Agent version is pinned but autoupgrade is enabled.
"""
return (_ValidateAgentVersion(agent_rule.type, agent_rule.version) +
_ValidateAgentVersionAndEnableAutoupgrade(
agent_rule.version, agent_rule.enable_autoupgrade))
def _ValidateAgentVersion(agent_type, version):
"""Validates agent version format.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is a valid string.
Args:
agent_type: str. The type of agent to be installed. Allowed values:
* "logging"
* "metrics"
version: str. The version of agent. Allowed values:
* "latest"
* "current-major"
* "[MAJOR_VERSION].*.*"
* "[MAJOR_VERSION].[MINOR_VERSION].[PATCH_VERSION]"
Returns:
An empty list if the validation passes. A singleton list with one of
the following errors if the validation fails.
* AgentVersionInvalidFormatError:
Agent version format is invalid.
* AgentMajorVersionNotSupportedError:
Agent's major version is not supported for the given agent type.
"""
version_enum = agent_policy.OpsAgentPolicy.AgentRule.Version
if version in {version_enum.LATEST_OF_ALL, version_enum.CURRENT_MAJOR}:
return []
valid_pin_res = {
_PINNED_MAJOR_VERSION_RE,
_PINNED_LEGACY_VERSION_RE,
_PINNED_VERSION_RE,
}
if not any(regex.search(version) for regex in valid_pin_res):
return [AgentVersionInvalidFormatError(version)]
major_version = version.split('.')[0]
if major_version not in _SUPPORTED_AGENT_MAJOR_VERSIONS[agent_type]:
return [AgentUnsupportedMajorVersionError(agent_type, version)]
return []
def _ValidateAgentVersionAndEnableAutoupgrade(version, enable_autoupgrade):
"""Validates that agent version is not pinned when autoupgrade is enabled.
This validation happens after the arg parsing stage. At this point, we can
assume that the fields are valid string and boolean.
Args:
version: str. The version of agent. Possible formats:
* "latest"
* "[MAJOR_VERSION].*.*"
* "[MAJOR_VERSION].[MINOR_VERSION].[PATCH_VERSION]"
enable_autoupgrade: bool. Whether autoupgrade is enabled.
Returns:
An empty list if the validation passes. A singleton list with the following
error if the validation fails.
* AgentVersionAndEnableAutoupgradeConflictError:
Agent version is pinned but autoupgrade is enabled.
"""
if _PINNED_VERSION_RE.search(version) and enable_autoupgrade:
return [AgentVersionAndEnableAutoupgradeConflictError(version)]
return []
def _ValidateOsTypes(os_types):
"""Validates semantics of the ops-agents-policy.os-types field.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is a list of OpsAgentPolicy.Assignment.OsType objects.
Args:
os_types: list of OpsAgentPolicy.Assignment.OsType.
The list of OS types as part of the instance filters that the Ops Agent
policy applies to the Ops Agents policy.
Returns:
An empty list if the validation passes. A list of errors from the following
list if the validation fails.
* OsTypesMoreThanOneError:
More than one OS types are specified.
* OsTypeNotSupportedError:
The combination of the OS short name and version is not supported.
"""
# TODO(b/164155747): Empty os-types should error out.
errors = _ValidateOnlyOneOsTypeAllowed(os_types)
for os_type in os_types:
errors.extend(_ValidateSupportedOsType(os_type.short_name, os_type.version))
return errors
def _ValidateOnlyOneOsTypeAllowed(os_types):
"""Validates that no more than one OS type is specified.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is a list of OpsAgentPolicy.Assignment.OsType objects.
Args:
os_types: list of OpsAgentPolicy.Assignment.OsType.
The list of OS types as part of the instance filters that the Ops Agent
policy applies to the Ops Agents policy.
Returns:
An empty list if the validation passes. A singleton list with the following
error if the validation fails.
* OsTypesMoreThanOneError:
More than one OS types are specified.
"""
if len(os_types) > 1:
return [OsTypesMoreThanOneError()]
return []
def _ValidateSupportedOsType(short_name, version):
"""Validates the combination of the OS short name and version is supported.
This validation happens after the arg parsing stage. At this point, we can
assume that the field is an OpsAgentPolicy.Assignment.OsType object. Also the
OS short name has been already validated at the arg parsing stage.
Args:
short_name: str. The OS short name to filter instances by.
version: str. The OS version to filter instances by.
Returns:
An empty list if the validation passes. A singleton list with the following
error if the validation fails.
* OsTypeNotSupportedError:
The combination of the OS short name and version is not supported.
"""
if (short_name in _SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS
and short_name not in _OS_SHORT_NAMES_WITH_OS_AGENT_PREINSTALLED):
log.warning(
'For the policies to take effect on [{}] OS distro, please follow '
'the instructions at '
'https://cloud.google.com/compute/docs/manage-os#agent-install '
'to install the OS Config Agent on your instances.'.format(
short_name))
supported_versions = _SUPPORTED_OS_SHORT_NAMES_AND_VERSIONS[short_name]
if any(version.startswith(v) for v in supported_versions):
# Validation passed.
return []
return [OsTypeNotSupportedError(short_name, version)]

View File

@@ -0,0 +1,257 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Convenience functions for dealing with instances."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import ipaddress
from googlecloudsdk.api_lib.compute import alias_ip_range_utils
from googlecloudsdk.api_lib.compute import constants
from googlecloudsdk.api_lib.compute import utils
from googlecloudsdk.command_lib.compute import scope as compute_scopes
from googlecloudsdk.command_lib.compute.instances import flags as instances_flags
import six
def CreateNetworkInterfaceMessage(
resources,
compute_client,
network,
subnet,
project,
location,
scope,
*,
nic_type=None,
no_address=None,
address=None,
private_network_ip=None,
alias_ip_ranges_string=None,
network_tier=None,
no_public_dns=None,
public_dns=None,
no_public_ptr=None,
public_ptr=None,
no_public_ptr_domain=None,
public_ptr_domain=None,
stack_type=None,
ipv6_network_tier=None,
ipv6_public_ptr_domain=None,
queue_count=None,
ipv6_address=None,
ipv6_prefix_length=None,
internal_ipv6_address=None,
internal_ipv6_prefix_length=None,
network_attachment=None,
external_ipv6_address=None,
external_ipv6_prefix_length=None,
parent_nic_name=None,
vlan=None,
igmp_query=None,
enable_vpc_scoped_dns=None,
service_class_id=None,
):
"""Returns a new NetworkInterface message."""
# TODO(b/30460572): instance reference should have zone name, not zone URI.
if scope == compute_scopes.ScopeEnum.ZONE:
region = utils.ZoneNameToRegionName(location.split('/')[-1])
elif scope == compute_scopes.ScopeEnum.REGION:
region = location
messages = compute_client.messages
network_interface = messages.NetworkInterface()
# By default interface is attached to default network. If network or subnet
# are specified they're used instead.
if subnet is not None:
subnet_ref = resources.Parse(
subnet,
collection='compute.subnetworks',
params={'project': project, 'region': region},
)
network_interface.subnetwork = subnet_ref.SelfLink()
if network is not None:
network_ref = resources.Parse(
network,
params={
'project': project,
},
collection='compute.networks',
)
network_interface.network = network_ref.SelfLink()
# We only populate the default network when network-attachment is not
# specified because for a network interface targeting a network attachment,
# both network and subnetwork should not be there.
elif subnet is None and network_attachment is None:
network_ref = resources.Parse(
constants.DEFAULT_NETWORK,
params={'project': project},
collection='compute.networks',
)
network_interface.network = network_ref.SelfLink()
if network_attachment is not None:
network_interface.networkAttachment = network_attachment
if enable_vpc_scoped_dns:
network_interface.enableVpcScopedDns = enable_vpc_scoped_dns
if service_class_id:
network_interface.serviceClassId = service_class_id
if private_network_ip is not None:
# Try interpreting the address as IP address.
try:
# ipaddress only allows unicode input
ipaddress.ip_address(six.text_type(private_network_ip))
network_interface.networkIP = private_network_ip
except ValueError:
# ipaddress could not resolve as an IP address.
network_interface.networkIP = instances_flags.GetAddressRef(
resources, private_network_ip, region
).SelfLink()
if nic_type is not None:
network_interface.nicType = (
messages.NetworkInterface.NicTypeValueValuesEnum(nic_type)
)
if queue_count is not None:
network_interface.queueCount = queue_count
if alias_ip_ranges_string:
network_interface.aliasIpRanges = (
alias_ip_range_utils.CreateAliasIpRangeMessagesFromString(
messages, True, alias_ip_ranges_string
)
)
if stack_type is not None:
network_interface.stackType = (
messages.NetworkInterface.StackTypeValueValuesEnum(stack_type)
)
# Can't use StackTypeValueValuesEnum to compare because in some api versions
# IPv6 Only instances may not be supported yet and StackTypeValueValuesEnum
# may not contain IPV6_ONLY at all
no_access_config = stack_type == 'IPV6_ONLY'
# For a ipv6-only network interface or a network interface targeting a
# network attachment, access config is not needed/wanted.
if not no_access_config and not no_address and network_attachment is None:
access_config = messages.AccessConfig(
name=constants.DEFAULT_ACCESS_CONFIG_NAME,
type=messages.AccessConfig.TypeValueValuesEnum.ONE_TO_ONE_NAT,
)
if network_tier is not None:
access_config.networkTier = (
messages.AccessConfig.NetworkTierValueValuesEnum(network_tier)
)
# If the user provided an external IP, populate the access
# config with it.
address_resource = instances_flags.ExpandAddressFlag(
resources, compute_client, address, region
)
if address_resource:
access_config.natIP = address_resource
if no_public_dns:
access_config.setPublicDns = False
elif public_dns:
access_config.setPublicDns = True
if no_public_ptr:
access_config.setPublicPtr = False
elif public_ptr:
access_config.setPublicPtr = True
if not no_public_ptr_domain and public_ptr_domain is not None:
access_config.publicPtrDomainName = public_ptr_domain
network_interface.accessConfigs = [access_config]
# New flag has higher priority than the old one.
if external_ipv6_address is None:
external_ipv6_address = ipv6_address
if external_ipv6_prefix_length is None:
external_ipv6_prefix_length = ipv6_prefix_length
if (
ipv6_network_tier is not None
or ipv6_public_ptr_domain is not None
or external_ipv6_address
):
ipv6_access_config = messages.AccessConfig(
name=constants.DEFAULT_IPV6_ACCESS_CONFIG_NAME,
type=messages.AccessConfig.TypeValueValuesEnum.DIRECT_IPV6,
)
network_interface.ipv6AccessConfigs = [ipv6_access_config]
if ipv6_network_tier is not None:
ipv6_access_config.networkTier = (
messages.AccessConfig.NetworkTierValueValuesEnum(ipv6_network_tier)
)
if ipv6_public_ptr_domain is not None:
ipv6_access_config.publicPtrDomainName = ipv6_public_ptr_domain
if external_ipv6_address:
# Try interpreting the address as IPv6.
try:
# ipaddress only allows unicode input
ipaddress.ip_address(six.text_type(external_ipv6_address))
ipv6_access_config.externalIpv6 = external_ipv6_address
except ValueError:
# ipaddress could not resolve as an IPv6 address.
ipv6_access_config.externalIpv6 = instances_flags.GetAddressRef(
resources, external_ipv6_address, region
).SelfLink()
if external_ipv6_prefix_length:
ipv6_access_config.externalIpv6PrefixLength = external_ipv6_prefix_length
else:
ipv6_access_config.externalIpv6PrefixLength = 96
if internal_ipv6_address is not None:
# Try interpreting the address as IPv6.
try:
# ipaddress only allows unicode input
if '/' in six.text_type(internal_ipv6_address):
ipaddress.ip_network(six.text_type(internal_ipv6_address))
else:
ipaddress.ip_address(six.text_type(internal_ipv6_address))
network_interface.ipv6Address = internal_ipv6_address
except ValueError:
# ipaddress could not resolve as an IPv6 address.
network_interface.ipv6Address = instances_flags.GetAddressRef(
resources, internal_ipv6_address, region
).SelfLink()
if internal_ipv6_prefix_length is not None:
network_interface.internalIpv6PrefixLength = internal_ipv6_prefix_length
if parent_nic_name is not None:
network_interface.parentNicName = parent_nic_name
if vlan is not None:
network_interface.vlan = vlan
if igmp_query is not None:
network_interface.igmpQuery = (
messages.NetworkInterface.IgmpQueryValueValuesEnum(igmp_query)
)
return network_interface