Terraform表达式

表达式用来在配置文件中进行一些计算,最简单的表达式就是字面量,比如”hello”,或者5。Terraform也支持一些更加复杂的表达式,比如引用其他resource的输出值、数学计算、布尔条件计算,以及一些内建的函数。

在Terraform配置中很多地方都可以使用表达式,但某些特定的场景下限制了可以使用的表达式的类型,例如只准使用特定数据类型的字面量,或是禁止使用resource的输出值等。

下标和属性

listtuple可以通过下标访问成员,例如local.list[3]、var.tuple[2]。
mapobject可以通过属性访问成员,例如local.object.attrname、local.map.keyname。由于map的key是用户定义的,可能无法成为合法的Terraform标识符,所以访问map成员时推荐使用方括号:local.map[“keyname”]。

引用命名值

Terraform中定义了多种命名值,表达式中的每一个命名值都关联到一个具体的值,可以用单一命名值作为一个表达式,或是组合多个命名值来计算出一个新值。

命名值有如下种类:

  • <RESOURCE TYPE>.<NAME>:表示一个资源对象,如果资源声明了count元参数,那么该表达式表示的是一个对象实例的list;如果资源声明了for_each元参数,那么该表达式表示的是一个对象实例的map
  • var.<NAME>:表示一个输入变量
  • local.<NAME>:表示一个局部值
  • module.<MODULE_NAME>.<OUTPUT_NAME>:表示一个模块的一个输出值
  • data.<DATA_TYPE>.<NAME>:表示一个数据源实例,如果数据源声明了count元参数,那么该表达式表示的是一个数据源实例list;如果数据源声明了for_each元参数,那么该表达式表示的是一个数据源实例map
  • path.module:表示当前模块在文件系统中的路径
  • path.root:表示根模块(调用Terraform命令行执行的代码文件所在的模块)在文件系统中的路径
  • path.cwd:表示当前工作目录的路径,一般来说该路径等同于path.root,但在调用Terraform命令行时如果指定了代码路径,那么二者将会不同
  • terraform.workspace:当前使用的Workspace

虽然这些命名表达式可以使用.<NAME>方式来访问对象的各种属性,但实际上他们实际类型并不是数据类型object。两者的区别在于,object同时支持使用.<NAME>或者["<NAME>"]两种方式访问对象成员属性,而上述命名表达式仅支持.<NAME>

局部命名值

在某些特定表达式或上下文当中,有一些特殊的命名值可以被使用,他们是局部命名值。
几种比较常见的局部命名值有:

  • count.index:表达当前count下标序号
  • each.key:表达当前for_each迭代器实例
  • self:在预置器中指代声明预置器的资源

命名值的依赖关系

构建资源或是模块时经常会使用含有命名值的表达式赋值,Terraform会分析这些表达式并自动计算出对象之间的依赖关系。

引用资源输出属性

最常见的引用类型就是引用一个resourcedata块定义的对象的输出属性,由于这些资源与数据源对象结构可能非常复杂,所以对它们的输出属性的引用表达式也可能非常复杂。
如下示例:

resource "aws_instance" "example" {ami           = "ami-abc123"instance_type = "t2.micro"ebs_block_device {device_name = "sda2"volume_size = 16}ebs_block_device {device_name = "sda3"volume_size = 20}
}

aws_instance文档列出了该类型所支持的所有输入参数和内嵌块,以及对外输出的属性列表。所有这些不同的资源类型Schema都可以在引用中使用,如下所示:

  • ami参数可以在可以在其他地方用aws_instance.example.ami表达式来引用

  • id属性可以用aws_instance.example.id的表达式来引用

  • 内嵌的ebs_block_device参数可以通过展开表达式来访问,比如获取所有的ebs_block_devicedevice_name列表:aws_instance.example.ebs_block_device[*].device_name

  • aws_instance类型里的内嵌块并没有任何输出属性,但如果ebs_block_device添加了一个名为”id”的输出属性,那么可以用aws_instance.example.ebs_block_device[*].id表达式来访问含有所有id的列表

  • 有时多个内嵌块会各自包含一个逻辑键来区分彼此,类似用资源名访问资源,也可以用内嵌块的名字来访问特定内嵌块。假如aws_instance类型有一个假想的内嵌块类型device并规定device可以赋予这样的一个逻辑键,那么代码看起来就会是这样的:

    device "foo" {size = 2
    }
    device "bar" {size = 4
    }

    可以使用键来访问特定块的数据,例如:aws_instance.example.device["foo"].size。要获取一个device名称到device大小的映射,可以使用for表达式:

    {for k, device in aws_instance.example.device : k => device.size}
  • 当一个资源声明了count参数,那么资源本身就成了一个资源对象列表而非单个资源。这种情况下要访问资源输出属性,要么使用展开表达式,要么使用下标索引:

    • aws_instance.example[*].id:返回所有instance的id列表
    • aws_instance.example[0].id:返回第一个instance的id
  • 当一个资源声明了for_each参数,那么资源本身就成了一个资源对象字典而非单个资源。这种情况下要访问资源的输出属性,要么使用特定键,要么使用for表达式:

    • aws_instance.example["a"].id:返回”a”对应的实例的id
    • [for value in aws_instance.example: value.id]:返回所有instance的id
      注意:
      (1)使用for_each的资源集合不能直接使用展开表达式,展开表达式只能适用于列表
      (2)可以把字典转换成列表后再使用展开表达式:values(aws_instance.example)[*].id

尚不知晓的值

当Terraform在计算变更计划时,有些资源输出属性无法立即求值,因为它们的值取决于远程API的返回值。例如,有一个远程对象可以在创建时返回一个生成的唯一id,Terraform无法在创建它之前就预知这个值。

为了允许在计算变更阶段就能计算含有这种值的表达式,Terraform使用了一个特殊的”尚不知晓(unknown value)”占位符来代替这些结果。大部分时候不需要特意理会它们,因为Terraform语言会自动处理这些尚不知晓的值,比如说使两个尚不知晓的值相加得到的会是一个尚不知晓的值。

然而,有些情况下表达式中含有尚不知晓的值会有明显的影响:

  • count元参数不可以为尚不知晓,因为变更计划必须明确地知晓到底要维护多少个目标实例
  • 如果尚不知晓的值被用于数据源,那么数据源在计算变更计划阶段就无法读取,它会被推迟到执行阶段读取,这种情况下在计划阶段该数据源的一切输出均为尚不知晓
  • 如果声明module块时传递给模块输入变量的表达式使用了尚不知晓值,那么在模块代码中任何使用了该输入变量值的表达式的值都将是尚不知晓
  • 如果模块输出值表达式中含有尚不知晓值,任何使用该模块输出值的表达式都将是尚不知晓
  • Terraform会尝试验证尚不知晓值的数据类型是否合法,但仍然有可能无法正确检查数据类型,导致执行阶段发生错误

尚不知晓值在执行terraform plan时会被输出为”(not yet known)”。

算数和逻辑操作符

Terraform语言支持一组算数和逻辑操作符,它们的功能类似于JavaScript或Ruby里的操作符功能。
当一个表达式中含有多个操作符时,它们的优先级顺序时:

  • !- (负号)
  • */%
  • +- (减号)
  • >>=<<=
  • ==!=
  • &&
  • ||

可以使用小括号覆盖默认优先级。
不同的操作符可以按它们之间相似的行为被归纳为几组,每一组操作符都期待被给予特定类型的值。
Terraform会在类型不符时尝试进行隐式类型转换,如果失败则会抛错。

算数操作符

  • a + b:返回a与b的和
  • a - b:返回a与b的差
  • a * b:返回a与b的积
  • a / b:返回a与b的商
  • a % b:返回a与b的模(该操作符一般仅在a与b是整数时有效)
  • -a:返回a与-1的商

相等性操作符

  • a == b:如果a与b类型与值都相等返回true,否则返回false
  • a != b:与==相反

比较操作符

  • a < b:如果a比b小则为true,否则为false
  • a > b:如果a比b大则为true,否则为false
  • a <= b:如果a比b小或者相等则为true,否则为false
  • a >= b:如果a比b大或者相等则为true,否则为false

逻辑操作符

  • a || b:a或b中有至少一个为true则为true,否则为false
  • a && b:a与比都为true则为true,否则为false
  • !a:如果a为true则为false,如果a为false则为true

条件表达式

条件表达式是判断一个布尔表达式的结果以便于在后续两个值当中选择一个:

condition ? true_val : false_val

如果condition表达式为true,那么结果是true_value,反之则为false_value。

一个常见的条件表达式用法是使用默认值替代非法值:

var.a != "" ? var.a : "default-a"

如果输入变量a的值是空字符串,那么结果会是default-a,否则返回输入变量a的值。

条件表达式的判断条件可以使用上述的任意操作符,供选择的两个值也可以是任意类型,但它们的类型必须相同,这样Terraform才能判断条件表达式的输出类型。

函数调用

Terraform支持在计算表达式时使用一些内建函数,函数调用表达式类似操作符,通用语法是:

<FUNCTION NAME>(<ARGUMENT 1>, <ARGUMENT 2>)

函数名标明了要调用的函数,每一个函数都定义了数量不等、类型不一的入参以及不同类型的返回值。

有些函数定义了不定长的入参表,例如,min函数可以接收任意多个数值类型入参,返回其中最小的数值:

min(55, 3453, 2)

展开函数入参

如果想要把列表或元组的元素作为参数传递给函数,可以使用展开符:

min([55, 2453, 2]...)

展开符使用的是三个独立的.号组成的...,展开符是一种只能用在函数调用场景下的特殊语法。

for表达式

for表达式是将一种复杂类型映射成另一种复杂类型的表达式,输入类型值中的每一个元素都会被映射为一个或零个结果。
举例来说,如果var.list是一个字符串列表,那么下面的表达式将会把列表元素全部转为大写:

[for s in var.list : upper(s)]

在这里for表达式迭代了var.list中每一个元素(就是s),然后计算了upper(s),最后构建了一个包含了所有upper(s)结果的新元组,元组内元素顺序与源列表相同。

for表达式周围的括号类型决定了输出值的类型。
上面的例子里使用了方括号,所以输出类型是元组;如果使用的是花括号,那么输出类型是对象,for表达式内部冒号后面应该使用以=>符号分隔的表达式:

{for s in var.list : s => upper(s)}

该表达式返回一个对象,对象的成员属性名称就是源列表中的元素,值就是对应的大写值。

一个for表达式还可以包含一个可选的if子句用以过滤结果,这可能会减少返回的元素数量:

[for s in var.list : upper(s) if s != ""]

被for迭代的也可以是对象或者字典,这样的话迭代器就会被表示为两个临时变量:

[for k, v in var.map : length(k) + length(v)]

最后,如果返回类型是对象(使用花括号)那么表达式中可以使用...符号实现group by:

{for s in var.list : substr(s, 0, 1) => s... if s != ""}

展开表达式

展开表达式提供了一种类似for表达式的简洁表达方式。
例如var.list包含一组对象,每个对象有一个属性id,那么读取所有id的for表达式会是这样:

[for o in var.list : o.id]

与之等价的展开表达式是这样的:

var.list[*].id

这个特殊的[*]符号迭代了列表中每一个元素,然后返回了它们在.号右边的属性值。

展开表达式只能被用于列表(所以使用for_each参数的资源不能使用展开表达式,因为它的类型是字典)。然而,如果一个展开表达式被用于一个既不是列表又不是元组的值,那么这个值会被自动包装成一个单元素的列表然后被处理。

即:var.single_object[*].id等价于[var.single_object][*].id,大部分场景下这种行为没有什么意义,但在访问一个不确定是否会定义count参数的资源时,这种行为很有帮助,例如:

aws_instance.example[*].id

上面的表达式不论aws_instance.example定义了count与否都会返回实例的id列表,这样如果以后为aws_instance.example添加了count参数我们也不需要修改这个表达式。

dynamic块

在顶级块(例如resource)当中,一般只能以类似name = expression的形式进行一对一的赋值。大部分情况下这已经够用了,但某些资源类型包含了可重复的内嵌块,无法使用表达式循环赋值:

resource "aws_elastic_beanstalk_environment" "tfenvtest" {name = "tf-test-name"# 在aws_elastic_beanstalk_environment中通常会包含多个setting块setting {namespace = "aws:ec2:vpc"name      = "VPCId"value     = "vpc-xxxxxxxx"}setting {namespace = "aws:ec2:vpc"name      = "Subnets"value     = "subnet-xxxxxxxx"}
}

可以用dynamic块来动态构建重复的setting这样的内嵌块:

resource "aws_elastic_beanstalk_environment" "tfenvtest" {name                = "tf-test-name"application         = "${aws_elastic_beanstalk_application.tftest.name}"solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"dynamic "setting" {for_each = var.settingscontent {namespace = setting.value["namespace"]name = setting.value["name"]value = setting.value["value"]}}
}

dynamic可以在resourcedataproviderprovisioner块内使用。
一个dynamic块类似于for表达式,只不过它产生的是内嵌块,它可以迭代一个复杂类型数据然后为每一个元素生成相应的内嵌块。
在上面的例子里:

  • dynamic的标签(也就是”setting”)确定了要生成的内嵌块种类
  • for_each参数提供了需要迭代的复杂类型值
  • iterator参数(可选)设置了用以表示当前迭代元素的临时变量名,如果没有设置iterator,那么临时变量名默认就是dynamic块的标签(也就是setting)
  • labels参数(可选)是一个表示块标签的有序列表,用以按次序生成一组内嵌块,有labels参数的表达式里可以使用临时的iterator变量
  • 内嵌的content块定义了要生成的内嵌块的块体,可以在content块内部使用临时的iterator变量

由于for_each参数可以是集合或者结构化类型,所以可以使用for表达式或是展开表达式来转换一个现有集合的类型。

iterator变量(上面的例子里就是setting)有两个属性:

  • key:迭代容器如果是map,那么就是当前元素的键;迭代容器如果是list,那么就是当前元素在list中的下标序号;如果是由for_each表达式产出的set,那么key和value是一样的,这时不应该使用key
  • value:当前元素的值

一个dynamic块只能生成属于当前块定义过的内嵌块参数,无法生成诸如lifecycleprovisioner这样的元参数,因为Terraform必须在确保对这些元参数求值的计算是成功的。

for_each的值必须是不为空的map或者set,如果你需要根据内嵌数据结构或者多个数据结构的元素组合来声明资源实例集合,可以使用Terraform表达式和函数来生成合适的值。

注意: 过度使用dynamic块会导致代码难以阅读以及维护,建议只在需要构造可重用的模块代码时使用dynamic块,尽可能手写内嵌块。

字符串字面量

Terraform有两种不同的字符串字面量,最通用的就是用一对双引号包裹的字符,比如”hello”。在双引号之间,反斜杠\被用来进行转义。
Terraform支持的转义符有:

符号说明
\n换行
\r回车
\t制表符
\"双引号 (不会截断字符串)
\\反斜杠
\uNNNN普通字符映射平面的Unicode字符(NNNN代表四位16进制数)
\UNNNNNNNN补充字符映射平面的Unicode字符(NNNNNNNN代表八位16进制数)

另一种字符串表达式被称为”heredoc”风格,是受Unix Shell语言启发。它可以使用自定义的分隔符更加清晰地表达多行字符串:

<<EOT
hello
world
EOT

<<标记后面直到行尾组成的标识符(上述例子为EOT)开启了字符串,然后Terraform会把剩下的行都添加进字符串,直到遇到与标识符完全相等的字符串为止。
在上面的例子里,EOT就是标识符。任何字符都可以用作标识符,但传统上标识符一般以EO起头。上面例子里的EOT代表”文本的结束(end of text)”。

上面例子里的heredoc风格字符串要求内容必须对齐行头,这在块内声明时看起来会比较奇怪:

block {value = <<EOT
hello
world
EOT
}

为了改进可读性,Terraform也支持缩进的heredoc,只要把<<改成<<-

block {value = <<-EOThelloworldEOT
}

上面的例子里,Terraform会以最靠近行头的行作为基准来调整行头缩进,得到的字符串是这样的:

helloworld

heredoc中的反斜杠不会被解释成转义,而只会是简单的反斜杠。

双引号和heredoc风格两种字符串都支持字符串模版,模版的形式是${...}以及%{...}。如果想要表达${或者%{的字面量,那么可以重复第一个字符:$${%%{

字符串模版

字符串模版允许在字符串中嵌入表达式,或是通过其他值动态构造字符串。

插值

一个${...}序列被称为插值,插值计算花括号之间的表达式的值,有必要的话将之转换为字符串,然后插入字符串模版,形成最终的字符串:

"Hello, ${var.name}!"

输入变量var.name的值被访问后插入了字符串模版,产生了最终的结果,比如:”Hello, Juan!” 。

命令

一个%{...}序列被称为命令,命令可以是一个布尔表达式或者是对集合的迭代,类似条件表达式以及for表达式。

有两种命令:

  • if <BOOL> / else / endif命令根据布尔表达式的结果在两个模版中选择一个:

    "Hello, %{ if var.name != "" }${var.name}%{ else }unnamed%{ endif }!"

    else部分可以省略,这样如果布尔表达结果为false那么就会插入空字符串。

  • for <NAME> in <COLLECTION> / endfor命令迭代一个结构化对象或者集合,用每一个元素渲染模版,然后把它们拼接起来:

    <<EOT
    %{ for ip in aws_instance.example.*.private_ip }
    server ${ip}
    %{ endfor }
    EOT

    for关键字后紧跟的名字被用作代表迭代器元素的临时变量,可以用来在内嵌模版中使用。

为了在不添加额外空格和换行的前提下提升可读性,所有的模版序列都可以在首尾添加~符号。如果有~符号,那么模版序列会去除字符串左右的空白(空格以及换行)。如果~出现在头部,那么会去除字符串左侧的空白;如果出现在尾部,那么会去除字符串右边的空白:

<<EOT
%{ for ip in aws_instance.example.*.private_ip ~}
server ${ip}
%{ endfor ~}
EOT

如上示例,命令符后面的换行符被忽略了,但是server ${ip}后面的换行符被保留了,这确保了每一个元素生成一行输出:

server 10.1.16.154
server 10.1.16.1
server 10.1.16.34

当使用模版命令时,推荐使用heredoc风格字符串,用多行模版提升可读性;双引号字符串内最好只使用插值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/7479.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

idea无法识别加载pom.xml文件

有时idea无法识别加载pom.xml文件&#xff0c;直接打开pom.xml文件&#xff0c;然后添加到maven就行

新品宣传如何做好网络媒体推广+

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 新品宣传在网络媒体推广中&#xff0c;可以按照以下步骤进行&#xff1a; 产品预热 目标&#xff1a;提高潜在顾客的期待感和好奇心。 方法&#xff1a; A.发布软文&#xff1a;在各大平…

平滑 3d 坐标

3d平滑 import torch import torch.nn.functional as F import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3Dclass SmoothOperator:def smooth(self, vertices):# 使用一维平均池化进行平滑vertices_smooth F.avg_pool1d(vertices.p…

如何开发自己的深度学习优化算法

深度学习优化算法 如何开发自己的深度学习优化算法理解优化算法的基础**核心组件**&#xff1a; 设计自定义优化算法的步骤**步骤 1: 定义问题和目标****步骤 2: 研究现有算法****步骤 3: 开发初步想法****步骤 4: 创建原型****步骤 5: 系统测试与优化** 关键建议 如何开发自己…

暖心又实用!母亲节教会妈妈这4招才是最贴心的礼物

母亲节就要到了&#xff0c;这个特殊的日子&#xff0c;我们总是想要为妈妈送上最真挚的祝福和关怀。在这个数字化时代&#xff0c;一部智能手机就能成为我们表达爱意的桥梁。今天&#xff0c;就让我们一起来看看华为手机的四个功能&#xff0c;让妈妈的手机使用体验更加便捷、…

Stylus详解与引入:简化CSS编写的利器

在前端开发中&#xff0c;CSS是不可或缺的一部分&#xff0c;但编写CSS往往显得繁琐冗长&#xff0c;特别是在处理复杂的样式表时。为了简化CSS编写的过程&#xff0c;提高开发效率&#xff0c;Stylus应运而生。本文将介绍Stylus的基本语法和如何在项目中引入使用。 什么是Sty…

有效的括号--力扣经典面试题

目录 引言 题目描述: 思路分析: 代码展示: 引言 这道题是关于栈的经典面试题,如果大家对栈这个数据结构不是很了解的话,可以先看这篇博客--数据结构之栈的超详细讲解-CSDN博客 题目描述: 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c…

版本前瞻 | FASS2.2即将上线,6大亮点公布

自FASS上一版本发布已经过去了整整四个月。在这期间&#xff0c;FASS经历了很多重要项目的考验&#xff0c;也收到了用户很多宝贵的建议。经过几个月的开发和打磨完善&#xff0c;最新版本的FASS2.2终于要和大家见面了&#xff0c;针对存储系统配置使用复杂、运维监控粗放等痛点…

Unity初级---初识生命周期

1. Awake() &#xff1a;唤醒函数&#xff0c;最先执行的函数&#xff0c;只执行一次&#xff0c;当脚本文件挂载的对象被激活时调用 2. OnEnable() &#xff0c;OnDisable()&#xff1a;当脚本启用和禁用时触发&#xff0c;可执行多次&#xff0c;触发的前提是脚本挂载的对象…

多线程系列(七) -ThreadLocal 用法及内存泄露分析

一、简介 在 Java web 项目中&#xff0c;想必很多的同学对ThreadLocal这个类并不陌生&#xff0c;它最常用的应用场景就是用来做对象的跨层传递&#xff0c;避免多次传递&#xff0c;打破层次之间的约束。 比如下面这个HttpServletRequest参数传递的简单例子&#xff01; p…

PyQt 入门

Qt hello - 专注于Qt的技术分享平台 Python体系下GUI框架也多了去了&#xff0c;PyQt算是比较受欢迎的一个。如果对Qt框架熟悉&#xff0c;那掌握这套框架是很简单的。 一&#xff0c;安装 1.PyQt5 pip3 install PyQt5 2.Designer UI工具 pip3 install PyQt5-tools 3.UI…

【微磁学3D绘图工具探索】Excalibur

文章目录 概要调查报告技术名词解释主要特点 技术和算法实现他能够画出怎样酷炫的图 小结 概要 微磁学中的磁学结构同时包括二维和三维&#xff0c;想要绘制得好看&#xff0c;结果清晰&#xff0c;那么就需要一些自己写的绘图代码之外的额外渲染功能&#xff0c;尤其是对于三…

C语言写的LLM训练

特斯拉前 AI 总监、OpenAI 创始团队成员 Andrej Karpathy 用 C 代码完成了 GPT-2 大模型训练过程&#xff1a;karpathy/llm.c: LLM training in simple, raw C/CUDA (github.com) 下载源码 git clone --recursive https://github.com/karpathy/llm.c.git下载模型 从HF-Mirro…

springboot+vue+elementui实现校园互助平台大作业、毕业设计

目录 一、项目介绍 二、项目截图 管理后台 1.登录&#xff08;默认管理员账号密码均为&#xff1a;admin&#xff09; 2. 用户管理 ​编辑 3.任务管理 互助单&#xff08;学生发布&#xff09; 行政单&#xff08;教师发布&#xff09; ​编辑 审核&#xff08;退回需…

springboot 引入第三方bean

如何进行第三方bean的定义 参数进行自动装配

如何通过编程学习走科技特长生的路线?

编程是一门非常重要的技能&#xff0c;在当今数字化时代&#xff0c;掌握编程技能可以为个人的发展和就业提供更多机会。如果想要走科技特长生的路线&#xff0c;通过编程学习是一个非常好的选择。以下是一些步骤和建议&#xff0c;帮助你通过编程学习走科技特长生的路线&#…

rancher/elemental 构建不可变IOS(一)

一、什么是elemental Elemental 是 Rancher 的一个变种&#xff0c;专注于提供一个更轻量级的 Kubernetes 发行版。它旨在提供简化的部署和管理体验&#xff0c;同时保持 Kubernetes 的灵活性和强大功能。Elemental 通常针对较小的部署场景或资源受限的环境&#xff0c;例如测…

自建GitLab仓库

摘要 GitLab 是一个功能强大的开源代码托管平台&#xff0c;它不仅提供了代码存储和版本控制的核心功能&#xff0c;还集成了项目管理、CI/CD 流水线、代码审查等企业级特性。本文将指导你如何在自己的服务器上搭建 GitLab 社区版&#xff0c;创建一个完全属于自己的开源仓库&…

数据结构链表

数据结构链表 链表 1&#xff09;链表的概念及结构: 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的。 2&#xff09;实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 单向、双向…

# SSH 是什么?

SSH 是什么&#xff1f; 1、SSH 简介&#xff1a; SSH &#xff1a;全称 Secure Shell &#xff0c;它是安全外壳协议&#xff08;Secure Shell&#xff0c;简称SSH&#xff09;&#xff0c;是一种在不安全网络上用于安全远程登录和其他安全网络服务的协议。 SSH 由 IETF 的网…