mirror of
https://github.com/LCTT/TranslateProject.git
synced 2026-08-23 04:03:29 +08:00
Merge branch 'master' of https://github.com/LCTT/TranslateProject into new
This commit is contained in:
@@ -1,182 +0,0 @@
|
||||
pinewall is translating
|
||||
|
||||
[Anatomy of a Linux DNS Lookup – Part IV][2]
|
||||
============================================
|
||||
|
||||
In [Anatomy of a Linux DNS Lookup – Part I][3], [Part II][4], and [Part III][5] I covered:
|
||||
|
||||
* `nsswitch`
|
||||
|
||||
* `/etc/hosts`
|
||||
|
||||
* `/etc/resolv.conf`
|
||||
|
||||
* `ping` vs `host` style lookups
|
||||
|
||||
* `systemd` and its `networking` service
|
||||
|
||||
* `ifup` and `ifdown`
|
||||
|
||||
* `dhclient`
|
||||
|
||||
* `resolvconf`
|
||||
|
||||
* `NetworkManager`
|
||||
|
||||
* `dnsmasq`
|
||||
|
||||
In Part IV I’ll cover how containers do DNS. Yes, that’s not simple either…
|
||||
|
||||
* * *
|
||||
|
||||
1) Docker and DNS
|
||||
============================================================
|
||||
|
||||
In [part III][6] we looked at DNSMasq, and learned that it works by directing DNS queries to the localhost address `127.0.0.1`, and a process listening on port 53 there will accept the request.
|
||||
|
||||
So when you run up a Docker container, on a host set up like this, what do you expect to see in its `/etc/resolv.conf`?
|
||||
|
||||
Have a think, and try and guess what it will be.
|
||||
|
||||
Here’s the default output if you run a default Docker setup:
|
||||
|
||||
```
|
||||
$ docker run ubuntu cat /etc/resolv.conf
|
||||
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
|
||||
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
|
||||
# 127.0.0.53 is the systemd-resolved stub resolver.
|
||||
# run "systemd-resolve --status" to see details about the actual nameservers.
|
||||
|
||||
search home
|
||||
nameserver 8.8.8.8
|
||||
nameserver 8.8.4.4
|
||||
```
|
||||
|
||||
Hmmm.
|
||||
|
||||
#### Where did the addresses `8.8.8.8` and `8.8.4.4` come from?
|
||||
|
||||
When I pondered this question, my first thought was that the container would inherit the `/etc/resolv.conf` settings from the host. But a little thought shows that that won’t always work.
|
||||
|
||||
If you have DNSmasq set up on the host, the `/etc/resolv.conf` file will be pointed at the `127.0.0.1` loopback address. If this were passed through to the container, the container would look up DNS addresses from within its own networking context, and there’s no DNS server available within the container context, so the DNS lookups would fail.
|
||||
|
||||
‘A-ha!’ you might think: we can always use the host’s DNS server by using the _host’s_ IP address, available from within the container as the default route:
|
||||
|
||||
```
|
||||
root@79a95170e679:/# ip route
|
||||
default via 172.17.0.1 dev eth0
|
||||
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
|
||||
```
|
||||
|
||||
#### Use the host?
|
||||
|
||||
From that we can work out that the ‘host’ is on the ip address: `172.17.0.1`, so we could try manually pointing DNS at that using dig (you could also update the `/etc/resolv.conf` and then run `ping`, this just seems like a good time to introduce `dig` and its `@` flag, which points the request at the ip address you specify):
|
||||
|
||||
```
|
||||
root@79a95170e679:/# dig @172.17.0.1 google.com | grep -A1 ANSWER.SECTION
|
||||
;; ANSWER SECTION:
|
||||
google.com. 112 IN A 172.217.23.14
|
||||
```
|
||||
|
||||
However: that might work if you use DNSMasq, but if you don’t it won’t, as there’s no DNS server on the host to look up.
|
||||
|
||||
So Docker’s solution to this quandary is to bypass all that complexity and point your DNS lookups to Google’s DNS servers at `8.8.8.8` and `8.8.4.4`, ignoring whatever the host context is.
|
||||
|
||||
_Anecdote: This was the source of my first problem with Docker back in 2013\. Our corporate network blocked access to those IP addresses, so my containers couldn’t resolve URLs._
|
||||
|
||||
So that’s Docker containers, but container _orchestrators_ such as Kubernetes can do different things again…
|
||||
|
||||
# 2) Kubernetes and DNS
|
||||
|
||||
The unit of container deployment in Kubernetes is a Pod. A pod is a set of co-located containers that (among other things) share the same IP address.
|
||||
|
||||
An extra challenge with Kubernetes is to forward requests for Kubernetes services to the right resolver (eg `myservice.kubernetes.io`) to the private network allocated to those service addresses. These addresses are said to be on the ‘cluster domain’. This cluster domain is configurable by the administrator, so it might be `cluster.local` or `myorg.badger` depending on the configuration you set up.
|
||||
|
||||
In Kubernetes you have four options for configuring how DNS lookup works within your pod.
|
||||
|
||||
* Default
|
||||
|
||||
This (misleadingly-named) option takes the same DNS resolution path as the host the pod runs on, as in the ‘naive’ DNS lookup described earlier. It’s misleadingly named because it’s not the default! ClusterFirst is.
|
||||
|
||||
If you want to override the `/etc/resolv.conf` entries, you can in your config for the kubelet.
|
||||
|
||||
* ClusterFirst
|
||||
|
||||
ClusterFirst does selective forwarding on the DNS request. This is achieved in one of two ways based on the configuration.
|
||||
|
||||
In the first, older and simpler setup, a rule was followed where if the cluster domain was not found in the request, then it was forwarded to the host.
|
||||
|
||||
In the second, newer approach, you can configure selective forwarding on an internal DNS
|
||||
|
||||
Here’s what the config looks like and a diagram lifted from the [Kubernetes docs][7] which shows the flow:
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: kube-dns
|
||||
namespace: kube-system
|
||||
data:
|
||||
stubDomains: |
|
||||
{"acme.local": ["1.2.3.4"]}
|
||||
upstreamNameservers: |
|
||||
["8.8.8.8", "8.8.4.4"]
|
||||
```
|
||||
|
||||
The `stubDomains` entry defines specific DNS servers to use for specific domains. The upstream servers are the servers we defer to when nothing else has picked up the DNS request.
|
||||
|
||||
This is achieved with our old friend DNSMasq running in a pod.
|
||||
|
||||

|
||||
|
||||
The other two options are more niche:
|
||||
|
||||
* ClusterFirstWithHostNet
|
||||
|
||||
This applies if you use host network for your pods, ie you bypass the Docker networking setup to use the same network as you would directly on the host the pod is running on.
|
||||
|
||||
* None
|
||||
|
||||
None does nothing to DNS but forces you to specify the DNS settings in the `dnsConfig` field in the pod specification.
|
||||
|
||||
### CoreDNS Coming
|
||||
|
||||
And if that wasn’t enough, this is set to change again as CoreDNS comes to Kubernetes, replacing kube-dns. CoreDNS will offer a few benefits over kube-dns, being more configurabe and more efficient.
|
||||
|
||||
Find out more [here][8].
|
||||
|
||||
If you’re interested in OpenShift networking, I wrote a post on that [here][9]. But that was for 3.6 so is likely out of date now.
|
||||
|
||||
### End of Part IV
|
||||
|
||||
That’s part IV done. In it we covered.
|
||||
|
||||
* Docker DNS lookups
|
||||
|
||||
* Kubernetes DNS lookups
|
||||
|
||||
* Selective forwarding (stub domains)
|
||||
|
||||
* kube-dns
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://zwischenzugs.com/2018/08/06/anatomy-of-a-linux-dns-lookup-part-iv/
|
||||
|
||||
作者:[zwischenzugs][a]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://zwischenzugs.com/
|
||||
[1]:https://zwischenzugs.com/2018/08/06/anatomy-of-a-linux-dns-lookup-part-iv/
|
||||
[2]:https://zwischenzugs.com/2018/08/06/anatomy-of-a-linux-dns-lookup-part-iv/
|
||||
[3]:https://zwischenzugs.com/2018/06/08/anatomy-of-a-linux-dns-lookup-part-i/
|
||||
[4]:https://zwischenzugs.com/2018/06/18/anatomy-of-a-linux-dns-lookup-part-ii/
|
||||
[5]:https://zwischenzugs.com/2018/07/06/anatomy-of-a-linux-dns-lookup-part-iii/
|
||||
[6]:https://zwischenzugs.com/2018/07/06/anatomy-of-a-linux-dns-lookup-part-iii/
|
||||
[7]:https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#impacts-on-pods
|
||||
[8]:https://coredns.io/
|
||||
[9]:https://zwischenzugs.com/2017/10/21/openshift-3-6-dns-in-pictures/
|
||||
@@ -1,323 +0,0 @@
|
||||
Zafiry translating...
|
||||
What is a Makefile and how does it work?
|
||||
======
|
||||
|
||||

|
||||
If you want to run or update a task when certain files are updated, the `make` utility can come in handy. The `make` utility requires a file, `Makefile` (or `makefile`), which defines set of tasks to be executed. You may have used `make` to compile a program from source code. Most open source projects use `make` to compile a final executable binary, which can then be installed using `make install`.
|
||||
|
||||
In this article, we'll explore `make` and `Makefile` using basic and advanced examples. Before you start, ensure that `make` is installed in your system.
|
||||
|
||||
### Basic examples
|
||||
|
||||
Let's start by printing the classic "Hello World" on the terminal. Create a empty directory `myproject` containing a file `Makefile` with this content:
|
||||
```
|
||||
say_hello:
|
||||
|
||||
echo "Hello World"
|
||||
|
||||
```
|
||||
|
||||
Now run the file by typing `make` inside the directory `myproject`. The output will be:
|
||||
```
|
||||
$ make
|
||||
|
||||
echo "Hello World"
|
||||
|
||||
Hello World
|
||||
|
||||
```
|
||||
|
||||
In the example above, `say_hello` behaves like a function name, as in any programming language. This is called the target. The prerequisites or dependencies follow the target. For the sake of simplicity, we have not defined any prerequisites in this example. The command `echo "Hello World"` is called the recipe. The recipe uses prerequisites to make a target. The target, prerequisites, and recipes together make a rule.
|
||||
|
||||
To summarize, below is the syntax of a typical rule:
|
||||
```
|
||||
target: prerequisites
|
||||
|
||||
<TAB> recipe
|
||||
|
||||
```
|
||||
|
||||
As an example, a target might be a binary file that depends on prerequisites (source files). On the other hand, a prerequisite can also be a target that depends on other dependencies:
|
||||
```
|
||||
final_target: sub_target final_target.c
|
||||
|
||||
Recipe_to_create_final_target
|
||||
|
||||
|
||||
|
||||
sub_target: sub_target.c
|
||||
|
||||
Recipe_to_create_sub_target
|
||||
|
||||
```
|
||||
|
||||
It is not necessary for the target to be a file; it could be just a name for the recipe, as in our example. We call these "phony targets."
|
||||
|
||||
Going back to the example above, when `make` was executed, the entire command `echo "Hello World"` was displayed, followed by actual command output. We often don't want that. To suppress echoing the actual command, we need to start `echo` with `@`:
|
||||
```
|
||||
say_hello:
|
||||
|
||||
@echo "Hello World"
|
||||
|
||||
```
|
||||
|
||||
Now try to run `make` again. The output should display only this:
|
||||
```
|
||||
$ make
|
||||
|
||||
Hello World
|
||||
|
||||
```
|
||||
|
||||
Let's add a few more phony targets: `generate` and `clean` to the `Makefile`:
|
||||
```
|
||||
say_hello:
|
||||
@echo "Hello World"
|
||||
|
||||
generate:
|
||||
@echo "Creating empty text files..."
|
||||
touch file-{1..10}.txt
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
If we try to run `make` after the changes, only the target `say_hello` will be executed. That's because only the first target in the makefile is the default target. Often called the default goal, this is the reason you will see `all` as the first target in most projects. It is the responsibility of `all` to call other targets. We can override this behavior using a special phony target called `.DEFAULT_GOAL`.
|
||||
|
||||
Let's include that at the beginning of our makefile:
|
||||
```
|
||||
.DEFAULT_GOAL := generate
|
||||
```
|
||||
|
||||
This will run the target `generate` as the default:
|
||||
```
|
||||
$ make
|
||||
Creating empty text files...
|
||||
touch file-{1..10}.txt
|
||||
```
|
||||
|
||||
As the name suggests, the phony target `.DEFAULT_GOAL` can run only one target at a time. This is why most makefiles include `all` as a target that can call as many targets as needed.
|
||||
|
||||
Let's include the phony target `all` and remove `.DEFAULT_GOAL`:
|
||||
```
|
||||
all: say_hello generate
|
||||
|
||||
say_hello:
|
||||
@echo "Hello World"
|
||||
|
||||
generate:
|
||||
@echo "Creating empty text files..."
|
||||
touch file-{1..10}.txt
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
Before running `make`, let's include another special phony target, `.PHONY`, where we define all the targets that are not files. `make` will run its recipe regardless of whether a file with that name exists or what its last modification time is. Here is the complete makefile:
|
||||
```
|
||||
.PHONY: all say_hello generate clean
|
||||
|
||||
all: say_hello generate
|
||||
|
||||
say_hello:
|
||||
@echo "Hello World"
|
||||
|
||||
generate:
|
||||
@echo "Creating empty text files..."
|
||||
touch file-{1..10}.txt
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
The `make` should call `say_hello` and `generate`:
|
||||
```
|
||||
$ make
|
||||
Hello World
|
||||
Creating empty text files...
|
||||
touch file-{1..10}.txt
|
||||
```
|
||||
|
||||
It is a good practice not to call `clean` in `all` or put it as the first target. `clean` should be called manually when cleaning is needed as a first argument to `make`:
|
||||
```
|
||||
$ make clean
|
||||
Cleaning up...
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
Now that you have an idea of how a basic makefile works and how to write a simple makefile, let's look at some more advanced examples.
|
||||
|
||||
### Advanced examples
|
||||
|
||||
#### Variables
|
||||
|
||||
In the above example, most target and prerequisite values are hard-coded, but in real projects, these are replaced with variables and patterns.
|
||||
|
||||
The simplest way to define a variable in a makefile is to use the `=` operator. For example, to assign the command `gcc` to a variable `CC`:
|
||||
```
|
||||
CC = gcc
|
||||
```
|
||||
|
||||
This is also called a recursive expanded variable, and it is used in a rule as shown below:
|
||||
```
|
||||
hello: hello.c
|
||||
${CC} hello.c -o hello
|
||||
```
|
||||
|
||||
As you may have guessed, the recipe expands as below when it is passed to the terminal:
|
||||
```
|
||||
gcc hello.c -o hello
|
||||
```
|
||||
|
||||
Both `${CC}` and `$(CC)` are valid references to call `gcc`. But if one tries to reassign a variable to itself, it will cause an infinite loop. Let's verify this:
|
||||
```
|
||||
CC = gcc
|
||||
CC = ${CC}
|
||||
|
||||
all:
|
||||
@echo ${CC}
|
||||
```
|
||||
|
||||
Running `make` will result in:
|
||||
```
|
||||
$ make
|
||||
Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.
|
||||
```
|
||||
|
||||
To avoid this scenario, we can use the `:=` operator (this is also called the simply expanded variable). We should have no problem running the makefile below:
|
||||
```
|
||||
CC := gcc
|
||||
CC := ${CC}
|
||||
|
||||
all:
|
||||
@echo ${CC}
|
||||
```
|
||||
|
||||
#### Patterns and functions
|
||||
|
||||
The following makefile can compile all C programs by using variables, patterns, and functions. Let's explore it line by line:
|
||||
```
|
||||
# Usage:
|
||||
# make # compile all binary
|
||||
# make clean # remove ALL binaries and objects
|
||||
|
||||
.PHONY = all clean
|
||||
|
||||
CC = gcc # compiler to use
|
||||
|
||||
LINKERFLAG = -lm
|
||||
|
||||
SRCS := $(wildcard *.c)
|
||||
BINS := $(SRCS:%.c=%)
|
||||
|
||||
all: ${BINS}
|
||||
|
||||
%: %.o
|
||||
@echo "Checking.."
|
||||
${CC} ${LINKERFLAG} $< -o $@
|
||||
|
||||
%.o: %.c
|
||||
@echo "Creating object.."
|
||||
${CC} -c $<
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm -rvf *.o ${BINS}
|
||||
```
|
||||
|
||||
* Lines starting with `#` are comments.
|
||||
|
||||
* Line `.PHONY = all clean` defines phony targets `all` and `clean`.
|
||||
|
||||
* Variable `LINKERFLAG` defines flags to be used with `gcc` in a recipe.
|
||||
|
||||
* `SRCS := $(wildcard *.c)`: `$(wildcard pattern)` is one of the functions for filenames. In this case, all files with the `.c` extension will be stored in a variable `SRCS`.
|
||||
|
||||
* `BINS := $(SRCS:%.c=%)`: This is called as substitution reference. In this case, if `SRCS` has values `'foo.c bar.c'`, `BINS` will have `'foo bar'`.
|
||||
|
||||
* Line `all: ${BINS}`: The phony target `all` calls values in`${BINS}` as individual targets.
|
||||
|
||||
* Rule:
|
||||
```
|
||||
%: %.o
|
||||
@echo "Checking.."
|
||||
${CC} ${LINKERFLAG} $< -o $@
|
||||
```
|
||||
|
||||
Let's look at an example to understand this rule. Suppose `foo` is one of the values in `${BINS}`. Then `%` will match `foo`(`%` can match any target name). Below is the rule in its expanded form:
|
||||
```
|
||||
foo: foo.o
|
||||
@echo "Checking.."
|
||||
gcc -lm foo.o -o foo
|
||||
|
||||
```
|
||||
|
||||
As shown, `%` is replaced by `foo`. `$<` is replaced by `foo.o`. `$<` is patterned to match prerequisites and `$@` matches the target. This rule will be called for every value in `${BINS}`
|
||||
|
||||
* Rule:
|
||||
```
|
||||
%.o: %.c
|
||||
@echo "Creating object.."
|
||||
${CC} -c $<
|
||||
```
|
||||
|
||||
Every prerequisite in the previous rule is considered a target for this rule. Below is the rule in its expanded form:
|
||||
```
|
||||
foo.o: foo.c
|
||||
@echo "Creating object.."
|
||||
gcc -c foo.c
|
||||
```
|
||||
|
||||
* Finally, we remove all binaries and object files in target `clean`.
|
||||
|
||||
|
||||
|
||||
|
||||
Below is the rewrite of the above makefile, assuming it is placed in the directory having a single file `foo.c:`
|
||||
```
|
||||
# Usage:
|
||||
# make # compile all binary
|
||||
# make clean # remove ALL binaries and objects
|
||||
|
||||
.PHONY = all clean
|
||||
|
||||
CC = gcc # compiler to use
|
||||
|
||||
LINKERFLAG = -lm
|
||||
|
||||
SRCS := foo.c
|
||||
BINS := foo
|
||||
|
||||
all: foo
|
||||
|
||||
foo: foo.o
|
||||
@echo "Checking.."
|
||||
gcc -lm foo.o -o foo
|
||||
|
||||
foo.o: foo.c
|
||||
@echo "Creating object.."
|
||||
gcc -c foo.c
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm -rvf foo.o foo
|
||||
```
|
||||
|
||||
For more on makefiles, refer to the [GNU Make manual][1], which offers a complete reference and examples.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/what-how-makefile
|
||||
|
||||
作者:[Sachin Patil][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/psachin
|
||||
[1]:https://www.gnu.org/software/make/manual/make.pdf
|
||||
@@ -1,114 +0,0 @@
|
||||
Translating by DavidChenLiang
|
||||
|
||||
An introduction to diffs and patches
|
||||
======
|
||||

|
||||
|
||||
If you’ve ever worked on a large codebase with a distributed development model, you’ve probably heard people say things like “Sue just sent a patch,” or “Rajiv is checking out the diff.” Maybe those terms were new to you and you wondered what they meant. Open source has had an impact here, as the main development model of large projects from Apache web server to the Linux kernel have been “patch-based” development projects throughout their lifetime. In fact, did you know that Apache’s name originated from the set of patches that were collected and collated against the original [NCSA HTTPd server source code][1]?
|
||||
|
||||
You might think this is folklore, but an early [capture of the Apache website][2] claims that the name was derived from this original “patch” collection; hence **APA** t **CH** y server, which was then simplified to Apache.
|
||||
|
||||
But enough history trivia. What exactly are these patches and diffs that developers talk about?
|
||||
|
||||
First, for the sake of this article, let’s assume that these two terms reference one and the same thing. “Diff” is simply short for “difference;” a Unix utility by the same name reveals the difference between one or more files. We will look at a diff utility example below.
|
||||
|
||||
A “patch” refers to a specific collection of differences between files that can be applied to a source code tree using the Unix diff utility. So we can create diffs (or patches) using the diff tool and apply them to an unpatched version of that same source code using the patch tool. As an aside (and breaking my rule of no more history trivia), the word “patch” comes from the physical covering of punchcard holes to make software changes in the early computing days, when punchcards represented the program executed by the computer’s processor. The image below, found on this [Wikipedia page][3] describing software patches, shows this original “patching” concept:
|
||||
|
||||

|
||||
|
||||
Now that you have a basic understanding of patches and diffs, let’s explore how software developers use these tools. If you haven’t used a source code control system like [Git][4] or [Subversion][5], I will set the stage for how most non-trivial software projects are developed. If you think of the life of a software project as a set of actions along a timeline, you might visualize changes to the software—such as adding a feature or a function to a source code file or fixing a bug—appearing at different points on the timeline, with each discrete point representing the state of all the source code files at that time. We will call these points of change “commits,” using the same nomenclature that today’s most popular source code control tool, Git, uses. When you want to see the difference between the source code before and after a certain commit, or between many commits, you can use a tool to show us diffs, or differences.
|
||||
|
||||
If you are developing software using this same source code control tool, Git, you may have changes in your local system that you want to provide for others to potentially add as commits to their own tree. One way to provide local changes to others is to create a diff of your local tree's changes and send this “patch” to others who are working on the same source code. This lets others patch their tree and see the source code tree with your changes applied.
|
||||
|
||||
### Linux, Git, and GitHub
|
||||
|
||||
This model of sharing patch files is how the Linux kernel community operates regarding proposed changes today. If you look at the archives for any of the popular Linux kernel mailing lists—[LKML][6] is the primary one, but others include [linux-containers][7], [fs-devel][8], [Netdev][9], to name a few—you’ll find many developers posting patches that they wish to have others review, test, and possibly bring into the official Linux kernel Git tree at some point. It is outside of the scope of this article to discuss Git, the source code control system written by Linus Torvalds, in more detail, but it's worth noting that Git enables this distributed development model, allowing patches to live separately from a main repository, pushing and pulling into different trees and following their specific development flow.
|
||||
|
||||
Before moving on, we can’t ignore the most popular service in which patches and diffs are relevant: [GitHub][10]. Given its name, you can probably guess that GitHub is based on Git, but it offers a web- and API-based workflow around the Git tool for distributed open source project development. One of the main ways that patches are shared in GitHub is not via email, like the Linux kernel, but by creating a **pull request**. When you commit changes on your own copy of a source code tree, you can share those changes by creating a pull request against a commonly shared repository for that software project. GitHub is used by many active and popular open source projects today, such as [Kubernetes][11], [Docker][12], [the Container Network Interface (CNI)][13], [Istio][14], and many others. In the GitHub world, users tend to use the web-based interface to review the diffs or patches that comprise a pull request, but you can still access the raw patch files and use them at the command line with the patch utility.
|
||||
|
||||
### Getting down to business
|
||||
|
||||
Now that we’ve covered patches and diffs and how they are used in popular open source communities or tools, let's look at a few examples.
|
||||
|
||||
The first example includes two copies of a source tree, and one has changes that we want to visualize using the diff utility. In our examples, we will look at “unified” diffs because that is the expected view for patches in most of the modern software development world. Check the diff manual page for more information on options and ways to produce differences. The original source code is located in sources-orig and our second, modified codebase is located in a directory named sources-fixed. To show the differences in a unified diff format in your terminal, use the following command:
|
||||
```
|
||||
$ diff -Naur sources-orig/ sources-fixed/
|
||||
```
|
||||
|
||||
...which then shows the following diff command output:
|
||||
```
|
||||
diff -Naur sources-orig/officespace/interest.go sources-fixed/officespace/interest.go
|
||||
--- sources-orig/officespace/interest.go 2018-08-10 16:39:11.000000000 -0400
|
||||
+++ sources-fixed/officespace/interest.go 2018-08-10 16:39:40.000000000 -0400
|
||||
@@ -11,15 +11,13 @@
|
||||
InterestRate float64
|
||||
}
|
||||
|
||||
+// compute the rounded interest for a transaction
|
||||
func computeInterest(acct *Account, t Transaction) float64 {
|
||||
|
||||
interest := t.Amount 选题模板.txt 中文排版指北.md comic core.md Dict.md lctt2014.md lctt2016.md LCTT翻译规范.md LICENSE Makefile published README.md sign.md sources translated t.InterestRate
|
||||
roundedInterest := math.Floor(interest*100) / 100.0
|
||||
remainingInterest := interest - roundedInterest
|
||||
|
||||
- // a little extra..
|
||||
- remainingInterest *= 1000
|
||||
-
|
||||
// Save the remaining interest into an account we control:
|
||||
acct.Balance = acct.Balance + remainingInterest
|
||||
```
|
||||
|
||||
The first few lines of the diff command output could use some explanation: The three `---` signs show the original filename; any lines that exist in the original file but not in the compared new file will be prefixed with a single `-` to note that this line was “subtracted” from the sources. The `+++` signs show the opposite: The compared new file and additions found in this file are marked with a single `+` symbol to show they were added in the new version of the file. Each “hunk” (that’s what sections prefixed by `@@` are called) of the difference patch file has contextual line numbers that help the patch tool (or other processors) know where to apply this change. You can see from the "Office Space" movie reference function that we’ve corrected (by removing three lines) the greed of one of our software developers, who added a bit to the rounded-out interest calculation along with a comment to our function.
|
||||
|
||||
If you want someone else to test the changes from this tree, you could save this output from diff into a patch file:
|
||||
```
|
||||
$ diff -Naur sources-orig/ sources-fixed/ >myfixes.patch
|
||||
```
|
||||
|
||||
Now you have a patch file, myfixes.patch, which can be shared with another developer to apply and test this set of changes. A fellow developer can apply the changes using the patch tool, given that their current working directory is in the base of the source code tree:
|
||||
```
|
||||
$ patch -p1 < ../myfixes.patch
|
||||
patching file officespace/interest.go
|
||||
```
|
||||
|
||||
Now your fellow developer’s source tree is patched and ready to build and test the changes that were applied via the patch. What if this developer had made changes to interest.go separately? As long as the changes do not conflict directly—for example, change the same exact lines—the patch tool should be able to solve where to merge the changes in. As an example, an interest.go file with several other changes is used in the following example run of patch:
|
||||
```
|
||||
$ patch -p1 < ../myfixes.patch
|
||||
patching file officespace/interest.go
|
||||
Hunk #1 succeeded at 26 (offset 15 lines).
|
||||
```
|
||||
|
||||
In this case, patch warns that the changes did not apply at the original location in the file, but were offset by 15 lines. If you have heavily changed files, patch may give up trying to find where the changes fit, but it does provide options (with requisite warnings in the documentation) for turning up the matching “fuzziness” (which are beyond the scope of this article).
|
||||
|
||||
If you are using Git and/or GitHub, you will probably not use the diff or patch tools as standalone tools. Git offers much of this functionality so you can use the built-in capabilities of working on a shared source tree with merging and pulling other developer’s changes. One similar capability is to use git diff to provide the unified diff output in your local tree or between any two references (a commit identifier, the name of a tag or branch, and so on). You can even create a patch file that someone not using Git might find useful by simply piping the git diff output to a file, given that it uses the exact format of the diffcommand that patch can consume. Of course, GitHub takes these capabilities into a web-based user interface so you can view file changes on a pull request. In this view, you will note that it is effectively a unified diff view in your web browser, and GitHub allows you to download these changes as a raw patch file.
|
||||
|
||||
### Summary
|
||||
|
||||
You’ve learned what a diff and a patch are, as well as the common Unix/Linux command line tools that interact with them. Unless you are a developer on a project still using a patch file-based development method—like the Linux kernel—you will consume these capabilities primarily through a source code control system like Git. But it’s helpful to know the background and underpinnings of features many developers use daily through higher-level tools like GitHub. And who knows—they may come in handy someday when you need to work with patches from a mailing list in the Linux world.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/diffs-patches
|
||||
|
||||
作者:[Phil Estes][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/estesp
|
||||
[1]:https://github.com/TooDumbForAName/ncsa-httpd
|
||||
[2]:https://web.archive.org/web/19970615081902/http:/www.apache.org/info.html
|
||||
[3]:https://en.wikipedia.org/wiki/Patch_(computing)
|
||||
[4]:https://git-scm.com/
|
||||
[5]:https://subversion.apache.org/
|
||||
[6]:https://lkml.org/
|
||||
[7]:https://lists.linuxfoundation.org/pipermail/containers/
|
||||
[8]:https://patchwork.kernel.org/project/linux-fsdevel/list/
|
||||
[9]:https://www.spinics.net/lists/netdev/
|
||||
[10]:https://github.com/
|
||||
[11]:https://kubernetes.io/
|
||||
[12]:https://www.docker.com/
|
||||
[13]:https://github.com/containernetworking/cni
|
||||
[14]:https://istio.io/
|
||||
@@ -1,110 +0,0 @@
|
||||
heguangzhi Translating
|
||||
|
||||
6 open source tools for making your own VPN
|
||||
======
|
||||
|
||||

|
||||
|
||||
If you want to try your hand at building your own VPN but aren’t sure where to start, you’ve come to the right place. I’ll compare six of the best free and open source tools to set up and use a VPN on your own server. These VPNs work whether you want to set up a site-to-site VPN for your business or just create a remote access proxy to unblock websites and hide your internet traffic from ISPs.
|
||||
|
||||
Which is best depends on your needs and limitations, so take into consideration your own technical expertise, environment, and what you want to achieve with your VPN. In particular, consider the following factors:
|
||||
|
||||
* VPN protocol
|
||||
* Number of clients and types of devices
|
||||
* Server distro compatibility
|
||||
* Technical expertise required
|
||||
|
||||
|
||||
|
||||
### Algo
|
||||
|
||||
[Algo][1] was designed from the bottom up to create VPNs for corporate travelers who need a secure proxy to the internet. It “includes only the minimal software you need,” meaning you sacrifice extensibility for simplicity. Algo is based on StrongSwan but cuts out all the things that you don’t need, which has the added benefit of removing security holes that a novice might otherwise not notice.
|
||||
|
||||
As an added bonus, it even blocks ads!
|
||||
|
||||
Algo supports only the IKEv2 protocol and Wireguard. Because IKEv2 support is built into most devices these days, it doesn’t require a client app like OpenVPN. Algo can be deployed using Ansible on Ubuntu (the preferred option), Windows, RedHat, CentOS, and FreeBSD. Setup is automated using Ansible, which configures the server based on your answers to a short set of questions. It’s also very easy to tear down and re-deploy on demand.
|
||||
|
||||
Algo is probably the easiest and fastest VPN to set up and deploy on this list. It’s extremely tidy and well thought out. If you don’t need any of the more advanced features offered by other tools and just need a secure proxy, it’s a great option. Note that Algo explicitly states it’s not meant for geo-unblocking or evading censorship, and was primarily designed for confidentiality.
|
||||
|
||||
### Streisand
|
||||
|
||||
[Streisand][2] can be installed on any Ubuntu 16.04 server using a single command; the process takes about 10 minutes. It supports L2TP, OpenConnect, OpenSSH, OpenVPN, Shadowsocks, Stunnel, Tor bridge, and WireGuard. Depending on which protocol you choose, you may need to install a client app.
|
||||
|
||||
In many ways, Streisand is similar to Algo, but it offers more protocols and customization. This takes a bit more effort to manage and secure but is also more flexible. Note Streisand does not support IKEv2. I would say Streisand is more effective for bypassing censorship in places like China and Turkey due to its versatility, but Algo is easier and faster to set up.
|
||||
|
||||
The setup is automated using Ansible, so there’s not much technical expertise required. You can easily add more users by sending them custom-generated connection instructions, which include an embedded copy of the server’s SSL certificate.
|
||||
|
||||
Tearing down Streisand is a quick and painless process, and you can re-deploy on demand.
|
||||
|
||||
### OpenVPN
|
||||
|
||||
[OpenVPN][3] requires both client and server applications to set up VPN connections using the protocol of the same name. OpenVPN can be tweaked and customized to fit your needs, but it also requires the most technical expertise of the tools covered here. Both remote access and site-to-site configurations are supported; the former is what you’ll need if you plan on using your VPN as a proxy to the internet. Because client apps are required to use OpenVPN on most devices, the end user must keep them updated.
|
||||
|
||||
Server-side, you can opt to deploy in the cloud or on your Linux server. Compatible distros include CentOS, Ubuntu, Debian, and openSUSE. Client apps are available for Windows, MacOS, iOS, and Android, and there are unofficial apps for other devices. Enterprises can opt to set up an OpenVPN Access Server, but that’s probably overkill for individuals, who will want the Community Edition.
|
||||
|
||||
OpenVPN is relatively easy to configure with static key encryption, but it isn’t all that secure. Instead, I recommend setting it up with [easy-rsa][4], a key management package you can use to set up a public key infrastructure. This allows you to connect multiple devices at a time and protect them with perfect forward secrecy, among other benefits. OpenVPN uses SSL/TLS for encryption, and you can specify DNS servers in your configuration.
|
||||
|
||||
OpenVPN can traverse firewalls and NAT firewalls, which means you can use it to bypass gateways and firewalls that might otherwise block the connection. It supports both TCP and UDP transports.
|
||||
|
||||
### StrongSwan
|
||||
|
||||
You might have come across a few different VPN tools with “Swan” in the name. FreeS/WAN, OpenSwan, LibreSwan, and [strongSwan][5] are all forks of the same project, and the lattermost is my personal favorite. Server-side, strongSwan runs on Linux 2.6, 3.x, and 4x kernels, Android, FreeBSD, macOS, iOS, and Windows.
|
||||
|
||||
StrongSwan uses the IKEv2 protocol and IPSec. Compared to OpenVPN, IKEv2 connects much faster while offering comparable speed and security. This is useful if you prefer a protocol that doesn’t require installing an additional app on the client, as most newer devices manufactured today natively support IKEv2, including Windows, MacOS, iOS, and Android.
|
||||
|
||||
StrongSwan is not particularly easy to use, and despite decent documentation, it uses a different vocabulary than most other tools, which can be confusing. Its modular design makes it great for enterprises, but that also means it’s not the most streamlined. It’s certainly not as straightforward as Algo or Streisand.
|
||||
|
||||
Access control can be based on group memberships using X.509 attribute certificates, a feature unique to strongSwan. It supports EAP authentication methods for integration into other environments like Windows Active Directory. StrongSwan can traverse NAT firewalls.
|
||||
|
||||
### SoftEther
|
||||
|
||||
[SoftEther][6] started out as a project by a graduate student at the University of Tsukuba in Japan. SoftEther VPN Server and VPN Bridge run on Windows, Linux, OSX, FreeBSD, and Solaris, while the client app works on Windows, Linux, and MacOS. VPN Bridge is mainly for enterprises that need to set up site-to-site VPNs, so individual users will just need the server and client programs to set up remote access.
|
||||
|
||||
SoftEther supports the OpenVPN, L2TP, SSTP, and EtherIP protocols, but its own SoftEther protocol claims to be able to be immunized against deep packet inspection thanks to “Ethernet over HTTPS” camouflage. SoftEther also makes a few tweaks to reduce latency and increase throughput. Additionally, SoftEther includes a clone function that allows you to easily transition from OpenVPN to SoftEther.
|
||||
|
||||
SoftEther can traverse NAT firewalls and bypass firewalls. On restricted networks that permit only ICMP and DNS packets, you can utilize SoftEther’s VPN over ICMP or VPN over DNS options to penetrate the firewall. SoftEther works with both IPv4 and IPv6.
|
||||
|
||||
SoftEther is easier to set up than OpenVPN and strongSwan but is a bit more complicated than Streisand and Algo.
|
||||
|
||||
### WireGuard
|
||||
|
||||
[WireGuard][7] is the newest tool on this list; it's so new that it’s not even finished yet. That being said, it offers a fast and easy way to deploy a VPN. It aims to improve on IPSec by making it simpler and leaner like SSH.
|
||||
|
||||
Like OpenVPN, WireGuard is both a protocol and a software tool used to deploy a VPN that uses said protocol. A key feature is “crypto key routing,” which associates public keys with a list of IP addresses allowed inside the tunnel.
|
||||
|
||||
WireGuard is available for Ubuntu, Debian, Fedora, CentOS, MacOS, Windows, and Android. WireGuard works on both IPv4 and IPv6.
|
||||
|
||||
WireGuard is much lighter than most other VPN protocols, and it transmits packets only when data needs to be sent.
|
||||
|
||||
The developers say WireGuard should not yet be trusted because it hasn’t been fully audited yet, but you’re welcome to give it a spin. It could be the next big thing!
|
||||
|
||||
### Homemade VPN vs. commercial VPN
|
||||
|
||||
Making your own VPN adds a layer of privacy and security to your internet connection, but if you’re the only one using it, then it would be relatively easy for a well-equipped third party, such as a government agency, to trace activity back to you.
|
||||
|
||||
Furthermore, if you plan to use your VPN to unblock geo-locked content, a homemade VPN may not be the best option. Since you’ll only be connecting from a single IP address, your VPN server is fairly easy to block.
|
||||
|
||||
Good commercial VPNs don’t have these issues. With a provider like [ExpressVPN][8], you share the server’s IP address with dozens or even hundreds of other users, making it nigh-impossible to track a single user’s activity. You also get a huge range of hundreds or thousands of servers to choose from, so if one has been blacklisted, you can just switch to another.
|
||||
|
||||
The tradeoff of a commercial VPN, however, is that you must trust the provider not to snoop on your internet traffic. Be sure to choose a reputable provider with a clear no-logs policy.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/open-source-tools-vpn
|
||||
|
||||
作者:[Paul Bischoff][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:
|
||||
[1]: https://blog.trailofbits.com/2016/12/12/meet-algo-the-vpn-that-works/
|
||||
[2]: https://github.com/StreisandEffect/streisand
|
||||
[3]: https://openvpn.net/
|
||||
[4]: https://github.com/OpenVPN/easy-rsa
|
||||
[5]: https://www.strongswan.org/
|
||||
[6]: https://www.softether.org/
|
||||
[7]: https://www.wireguard.com/
|
||||
[8]: https://www.comparitech.com/vpn/reviews/expressvpn/
|
||||
@@ -1,68 +0,0 @@
|
||||
ucasFL translating
|
||||
|
||||
8 great Python libraries for side projects
|
||||
======
|
||||
|
||||

|
||||
|
||||
We have a saying in the Python/Django world: We came for the language and stayed for the community. That is true for most of us, but something else that has kept us in the Python world is how easy it is to have an idea and quickly work through it over lunch or in a few hours at night.
|
||||
|
||||
This month we're diving into Python libraries we love to use to quickly scratch those side-project or lunchtime itches.
|
||||
|
||||
### To save data in a database on the fly: Dataset
|
||||
|
||||
[Dataset][1] is our go-to library when we quickly want to collect data and save it into a database before we know what our final database tables will look like. Dataset has a simple, yet powerful API that makes it easy to put data in and sort it out later.
|
||||
|
||||
Dataset is built on top of SQLAlchemy, so extending it will feel familiar. The underlying database models are a breeze to import into Django using Django's built-in [inspectdb][2] management command. This makes working with existing databases pretty painless.
|
||||
|
||||
### To scrape data from web pages: Beautiful Soup
|
||||
|
||||
[Beautiful Soup][3] (BS4 as of this writing) makes extracting information out of HTML pages easy. It's our go-to anytime we need to turn unstructured or loosely structured HTML into structured data. It's also great for working with XML data that might otherwise not be readable.
|
||||
|
||||
### To work with HTTP content: Requests
|
||||
|
||||
[Requests][4] is arguably one of the gold standard libraries for working with HTTP content. Anytime we need to consume an HTML page or even an API, Requests has us covered. It's also very well documented.
|
||||
|
||||
### To write command-line utilities: Click
|
||||
|
||||
When we need to write a native Python script, [Click][5] is our favorite library for writing command-line utilities. The API is straightforward, well thought out, and there are only a few patterns to remember. The docs are great, which makes looking up advanced features easy.
|
||||
|
||||
### To name things: Python Slugify
|
||||
|
||||
As we all know, naming things is hard. [Python Slugify][6] is a useful library for turning a title or description into a unique(ish) identifier. If you are working on a web project and you want to use SEO-friendly URLs, Python Slugify makes this easier.
|
||||
|
||||
### To work with plugins: Pluggy
|
||||
|
||||
[Pluggy][7] is relatively new, but it's also one of the best and easiest ways to add a plugin system to your existing application. If you have ever worked with pytest, you have used pluggy without knowing it.
|
||||
|
||||
### To convert CSV files into APIs: Datasette
|
||||
|
||||
[Datasette][8], not to be confused with Dataset, is an amazing tool for easily turning CSV files into full-featured read-only REST JSON APIs. Datasette has tons of features, including charting and geo (for creating interactive maps), and it's easy to deploy via a container or third-party web host.
|
||||
|
||||
### To handle environment variables and more: Envparse
|
||||
|
||||
If you need to parse environment variables because you don't want to save API keys, database credentials, or other sensitive information in your source code, then [envparse][9] is one of your best bets. Envparse handles environment variables, ENV files, variable types, and even pre- and post-processors (in case you want to ensure that a variable is always upper or lower case, for instance).
|
||||
|
||||
Do you have a favorite Python library for side projects that's not on this list? Please share it in the comments.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/9/python-libraries-side-projects
|
||||
|
||||
作者:[Jeff Triplett][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/laceynwilliams
|
||||
[1]: https://dataset.readthedocs.io/en/latest/
|
||||
[2]: https://docs.djangoproject.com/en/2.1/ref/django-admin/#django-admin-inspectdb
|
||||
[3]: https://www.crummy.com/software/BeautifulSoup/
|
||||
[4]: http://docs.python-requests.org/
|
||||
[5]: http://click.pocoo.org/5/
|
||||
[6]: https://github.com/un33k/python-slugify
|
||||
[7]: https://pluggy.readthedocs.io/en/latest/
|
||||
[8]: https://github.com/simonw/datasette
|
||||
[9]: https://github.com/rconradharris/envparse
|
||||
@@ -1,3 +1,5 @@
|
||||
translating---geekpi
|
||||
|
||||
Two open source alternatives to Flash Player
|
||||
======
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
Linux DNS 查询剖析(第四部分)
|
||||
============================================
|
||||
|
||||
在 [Linux DNS 查询剖析(第一部分)][1],[Linux DNS 查询剖析(第二部分)][2] 和 [Linux DNS 查询剖析(第三部分)][3] 中,我们已经介绍了以下内容:
|
||||
|
||||
* `nsswitch`
|
||||
* `/etc/hosts`
|
||||
* `/etc/resolv.conf`
|
||||
* `ping` 与 `host` 查询方式的对比
|
||||
* `systemd` 和对应的 `networking` 服务
|
||||
* `ifup` 和 `ifdown`
|
||||
* `dhclient`
|
||||
* `resolvconf`
|
||||
* `NetworkManager`
|
||||
* `dnsmasq`
|
||||
|
||||
在第四部分中,我将介绍容器如何完成 DNS 查询。你想的没错,也不是那么简单。
|
||||
|
||||
* * *
|
||||
|
||||
### 1) Docker 和 DNS
|
||||
|
||||
============================================================
|
||||
|
||||
在 [Linux DNS 查询剖析(第三部分)][3] 中,我们介绍了 `dnsmasq`,其工作方式如下:将 DNS 查询指向到 localhost 地址 `127.0.0.1`,同时启动一个进程监听 `53` 端口并处理查询请求。
|
||||
|
||||
在按上述方式配置 DNS 的主机上,如果运行了一个 Docker 容器,容器内的 `/etc/resolv.conf` 文件会是怎样的呢?
|
||||
|
||||
我们来动手试验一下吧。
|
||||
|
||||
按照默认 Docker 创建流程,可以看到如下的默认输出:
|
||||
|
||||
```
|
||||
$ docker run ubuntu cat /etc/resolv.conf
|
||||
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
|
||||
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
|
||||
# 127.0.0.53 is the systemd-resolved stub resolver.
|
||||
# run "systemd-resolve --status" to see details about the actual nameservers.
|
||||
|
||||
search home
|
||||
nameserver 8.8.8.8
|
||||
nameserver 8.8.4.4
|
||||
```
|
||||
|
||||
奇怪!
|
||||
|
||||
#### 地址 `8.8.8.8` 和 `8.8.4.4` 从何而来呢?
|
||||
|
||||
当我思考容器内的 `/etc/resolv.conf` 配置时,我的第一反应是继承主机的 `/etc/resolv.conf`。但只要稍微进一步分析,就会发现这样并不总是有效的。
|
||||
|
||||
如果在主机上配置了 `dnsmasq`,那么 `/etc/resolv.conf` 文件总会指向 `127.0.0.1` 这个<ruby>回环地址<rt>loopback address</rt></ruby>。如果这个地址被容器继承,容器会在其本身的<ruby>网络上下文<rt>networking context</rt></ruby>中使用;由于容器内并没有运行(在 `127.0.0.1` 地址的)DNS 服务器,因此 DNS 查询都会失败。
|
||||
|
||||
“有了!”你可能有了新主意:将 _主机的_ 的 IP 地址用作 DNS 服务器地址,其中这个 IP 地址可以从容器的<ruby>默认路由<rt>default route</rt></ruby>中获取:
|
||||
|
||||
```
|
||||
root@79a95170e679:/# ip route
|
||||
default via 172.17.0.1 dev eth0
|
||||
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2
|
||||
```
|
||||
|
||||
#### 使用主机 IP 地址真的可行吗?
|
||||
|
||||
从默认路由中,我们可以找到主机的 IP 地址 `172.17.0.1`,进而可以通过手动指定 DNS 服务器的方式进行测试(你也可以更新 `/etc/resolv.conf` 文件并使用 `ping` 进行测试;但我觉得这里很适合介绍新的 `dig` 工具及其 `@` 参数,后者用于指定需要查询的 DNS 服务器地址):
|
||||
|
||||
```
|
||||
root@79a95170e679:/# dig @172.17.0.1 google.com | grep -A1 ANSWER.SECTION
|
||||
;; ANSWER SECTION:
|
||||
google.com. 112 IN A 172.217.23.14
|
||||
```
|
||||
|
||||
但是还有一个问题,这种方式仅适用于主机配置了 `dnsmasq` 的情况;如果主机没有配置 `dnsmasq`,主机上并不存在用于查询的 DNS 服务器。
|
||||
|
||||
在这个问题上,Docker 的解决方案是忽略所有可能的复杂情况,即无论主机中使用什么 DNS 服务器,容器内都使用 Google 的 DNS 服务器 `8.8.8.8` 和 `8.8.4.4` 完成 DNS 查询。
|
||||
|
||||
_我的经历:在 2013 年,我遇到了使用 Docker 以来的第一个问题,与 Docker 的这种 DNS 解决方案密切相关。我们公司的网络屏蔽了 `8.8.8.8` 和 `8.8.4.4`,导致容器无法解析域名。_
|
||||
|
||||
这就是 Docker 容器的情况,但对于包括 Kubernetes 在内的容器 _<ruby>编排引擎<rt>orchestrators</rt></ruby>_,情况又有些不同。
|
||||
|
||||
### 2) Kubernetes 和 DNS
|
||||
|
||||
在 Kubernetes 中,最小部署单元是 `pod`;`pod` 是一组相互协作的容器,共享 IP 地址(和其它资源)。
|
||||
|
||||
Kubernetes 面临的一个额外的挑战是,将 Kubernetes 服务请求(例如,`myservice.kubernetes.io`)通过对应的<ruby>解析器<rt>resolver</rt></ruby>,转发到具体服务地址对应的<ruby>内网地址<rt>private network</rt></ruby>。这里提到的服务地址被称为归属于“<ruby>集群域<rt>cluster domain</rt></ruby>”。集群域可由管理员配置,根据配置可以是 `cluster.local` 或 `myorg.badger` 等。
|
||||
|
||||
在 Kubernetes 中,你可以为 `pod` 指定如下四种 `pod` 内 DNS 查询的方式。
|
||||
|
||||
* Default
|
||||
|
||||
在这种(名称容易让人误解)的方式中,`pod` 与其所在的主机采用相同的 DNS 查询路径,与前面介绍的主机 DNS 查询一致。我们说这种方式的名称容易让人误解,因为该方式并不是默认选项!`ClusterFirst` 才是默认选项。
|
||||
|
||||
如果你希望覆盖 `/etc/resolv.conf` 中的条目,你可以添加到 `kubelet` 的配置中。
|
||||
|
||||
* ClusterFirst
|
||||
|
||||
在 `ClusterFirst` 方式中,遇到 DNS 查询请求会做有选择的转发。根据配置的不同,有以下两种方式:
|
||||
|
||||
第一种方式配置相对古老但更简明,即采用一个规则:如果请求的域名不是集群域的子域,那么将其转发到 `pod` 所在的主机。
|
||||
|
||||
第二种方式相对新一些,你可以在内部 DNS 中配置选择性转发。
|
||||
|
||||
下面给出示例配置并从 [Kubernetes 文档][4]中选取一张图说明流程:
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: kube-dns
|
||||
namespace: kube-system
|
||||
data:
|
||||
stubDomains: |
|
||||
{"acme.local": ["1.2.3.4"]}
|
||||
upstreamNameservers: |
|
||||
["8.8.8.8", "8.8.4.4"]
|
||||
```
|
||||
|
||||
在 `stubDomains` 条目中,可以为特定域名指定特定的 DNS 服务器;而 `upstreamNameservers` 条目则给出,待查询域名不是集群域子域情况下用到的 DNS 服务器。
|
||||
|
||||
这是通过在一个 `pod` 中运行我们熟知的 `dnsmasq` 实现的。
|
||||
|
||||

|
||||
|
||||
剩下两种选项都比较小众:
|
||||
|
||||
* ClusterFirstWithHostNet
|
||||
|
||||
适用于 `pod` 使用主机网络的情况,例如绕开 Docker 网络配置,直接使用与 `pod` 对应主机相同的网络。
|
||||
|
||||
* None
|
||||
|
||||
`None` 意味着不改变 DNS,但强制要求你在 `pod` <ruby>规范文件<rt>specification</rt></ruby>的 `dnsConfig` 条目中指定 DNS 配置。
|
||||
|
||||
### CoreDNS 即将到来
|
||||
|
||||
除了上面提到的那些,一旦 `CoreDNS` 取代Kubernetes 中的 `kube-dns`,情况还会发生变化。`CoreDNS` 相比 `kube-dns` 具有可配置性更高、效率更高等优势。
|
||||
|
||||
如果想了解更多,参考[这里][5]。
|
||||
|
||||
如果你对 OpenShift 的网络感兴趣,我曾写过一篇[文章][6]可供你参考。但文章中 OpenShift 的版本是 `3.6`,可能有些过时。
|
||||
|
||||
### 第四部分总结
|
||||
|
||||
第四部分到此结束,其中我们介绍了:
|
||||
|
||||
* Docker DNS 查询
|
||||
* Kubernetes DNS 查询
|
||||
* 选择性转发(子域不转发)
|
||||
* kube-dns
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://zwischenzugs.com/2018/08/06/anatomy-of-a-linux-dns-lookup-part-iv/
|
||||
|
||||
作者:[zwischenzugs][a]
|
||||
译者:[pinewall](https://github.com/pinewall)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://zwischenzugs.com/
|
||||
[1]:https://zwischenzugs.com/2018/06/08/anatomy-of-a-linux-dns-lookup-part-i/
|
||||
[2]:https://zwischenzugs.com/2018/06/18/anatomy-of-a-linux-dns-lookup-part-ii/
|
||||
[3]:https://zwischenzugs.com/2018/07/06/anatomy-of-a-linux-dns-lookup-part-iii/
|
||||
[4]:https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#impacts-on-pods
|
||||
[5]:https://coredns.io/
|
||||
[6]:https://zwischenzugs.com/2017/10/21/openshift-3-6-dns-in-pictures/
|
||||
@@ -0,0 +1,322 @@
|
||||
Makefile及其工作原理
|
||||
======
|
||||
|
||||

|
||||
当你在一些源文件改变后需要运行或更新一个任务时,make工具通常会被用到。make工具需要读取Makefile(或makefile)文件,在该文件中定义了一系列需要执行的任务。make可以用来将源代码编译为可执行程序。大部分开源项目会使用make来实现二进制文件的编译,然后使用make istall命令来执行安装。
|
||||
|
||||
本文将通过一些基础和进阶的示例来展示make和Makefile的使用方法。在开始前,请确保你的系统中安装了make。
|
||||
|
||||
### 基础示例
|
||||
|
||||
依然从打印“Hello World”开始。首先创建一个名字为myproject的目录,目录下新建Makefile文件,文件内容为:
|
||||
```
|
||||
say_hello:
|
||||
|
||||
echo "Hello World"
|
||||
|
||||
```
|
||||
|
||||
在myproject目录下执行make,会有如下输出:
|
||||
```
|
||||
$ make
|
||||
|
||||
echo "Hello World"
|
||||
|
||||
Hello World
|
||||
|
||||
```
|
||||
|
||||
在上面的例子中,“say_hello”类似于其他编程语言中的函数名。在此可以成为target。在target之后的是预置条件和依赖。为了简单期间,我们在示例中没有定义预置条件。“echo ‘Hello World'"命令被称为recipe。recipe基于预置条件来实现target。target、预置条件和recipe共同构成一个规则。
|
||||
|
||||
总结一下,一个典型的规则的语法为:
|
||||
```
|
||||
target: 预置条件
|
||||
|
||||
<TAB> recipe
|
||||
|
||||
```
|
||||
|
||||
在示例中,target是一个基于源代码这个预置条件的二进制文件。另外,在另一规则中,这个预置条件也可以是依赖其他预置条件的target。
|
||||
```
|
||||
final_target: sub_target final_target.c
|
||||
|
||||
Recipe_to_create_final_target
|
||||
|
||||
|
||||
|
||||
sub_target: sub_target.c
|
||||
|
||||
Recipe_to_create_sub_target
|
||||
|
||||
```
|
||||
|
||||
target不要求是一个文件,也可以只是方便recipe使用的名字。我们称之为伪target。
|
||||
|
||||
再回到上面的示例中,当make被执行时,整条指令‘echo "Hello World"’都被打印出来,之后才是真正的执行结果。如果不希望指令本身被打印处理,需要在echo前添加@。
|
||||
```
|
||||
say_hello:
|
||||
|
||||
@echo "Hello World"
|
||||
|
||||
```
|
||||
|
||||
重新运行make,将会只有如下输出:
|
||||
```
|
||||
$ make
|
||||
|
||||
Hello World
|
||||
|
||||
```
|
||||
|
||||
接下来在Makefile中添加如下伪target:generate和clean:
|
||||
```
|
||||
say_hello:
|
||||
@echo "Hello World"
|
||||
|
||||
generate:
|
||||
@echo "Creating empty text files..."
|
||||
touch file-{1..10}.txt
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
随后当我们运行make时,只有‘say_hello’这个target被执行。这是因为makefile中的默认target为第一个target。通常情况下只有默认的target会被调用,大多数项目会将“all”作为默认target。“all”负责来调用其他的target。我们可以通过.DEFAULT_GOAL这个特殊的伪target来覆盖掉默认的行为。
|
||||
|
||||
在makefile文件开头增加.DEFAULT_GOAL:
|
||||
```
|
||||
.DEFAULT_GOAL := generate
|
||||
```
|
||||
|
||||
make会将generate作为默认target:
|
||||
```
|
||||
$ make
|
||||
Creating empty text files...
|
||||
touch file-{1..10}.txt
|
||||
```
|
||||
|
||||
顾名思义,.DEFAULT_GOAL伪target仅能定义一个target。这就是为什么很多项目仍然会有all这个target。这样可以保证多个target的实现。
|
||||
|
||||
下面删除掉.DEFAULT_GOAL,增加all target:
|
||||
```
|
||||
all: say_hello generate
|
||||
|
||||
say_hello:
|
||||
@echo "Hello World"
|
||||
|
||||
generate:
|
||||
@echo "Creating empty text files..."
|
||||
touch file-{1..10}.txt
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
运行之前,我们再增加一些特殊的伪target。.PHONY用来定义这些不是file的target。make会默认调用这写伪target下的recipe,而不去检查文件是否存在或最后修改日期。完整的makefile如下:
|
||||
```
|
||||
.PHONY: all say_hello generate clean
|
||||
|
||||
all: say_hello generate
|
||||
|
||||
say_hello:
|
||||
@echo "Hello World"
|
||||
|
||||
generate:
|
||||
@echo "Creating empty text files..."
|
||||
touch file-{1..10}.txt
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
make命令会调用say_hello和generate:
|
||||
```
|
||||
$ make
|
||||
Hello World
|
||||
Creating empty text files...
|
||||
touch file-{1..10}.txt
|
||||
```
|
||||
|
||||
clean不应该被放入all中,或者被放入第一个target。clean应当在需要清理时手动调用,调用方法为make clean。
|
||||
```
|
||||
$ make clean
|
||||
Cleaning up...
|
||||
rm *.txt
|
||||
```
|
||||
|
||||
现在你应该已经对makefile有了基础的了解,接下来我们看一些进阶的示例。
|
||||
|
||||
### 进阶示例
|
||||
|
||||
#### 变量
|
||||
|
||||
在之前的实例中,大部分target和预置条件是已经固定了的,但在实际项目中,它们通常用变量和模式来代替。
|
||||
|
||||
定义变量最简单的方式是使用‘=’操作符。例如,将命令gcc赋值给变量CC:
|
||||
```
|
||||
CC = gcc
|
||||
```
|
||||
|
||||
这被称为递归扩展变量,用于如下所示的规则中:
|
||||
```
|
||||
hello: hello.c
|
||||
${CC} hello.c -o hello
|
||||
```
|
||||
|
||||
你可能已经想到了,recipe将会在传递给终端时展开为:
|
||||
```
|
||||
gcc hello.c -o hello
|
||||
```
|
||||
|
||||
${CC}和$(CC)都能对gcc进行引用。但如果一个变量尝试将它本身赋值给自己,将会造成死循环。让我们验证一下:
|
||||
```
|
||||
CC = gcc
|
||||
CC = ${CC}
|
||||
|
||||
all:
|
||||
@echo ${CC}
|
||||
```
|
||||
|
||||
此时运行make会导致:
|
||||
```
|
||||
$ make
|
||||
Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.
|
||||
```
|
||||
|
||||
为了避免这种情况发生,可以使用“:=”操作符(这被称为简单扩展变量)。以下代码不会造成上述问题:
|
||||
```
|
||||
CC := gcc
|
||||
CC := ${CC}
|
||||
|
||||
all:
|
||||
@echo ${CC}
|
||||
```
|
||||
|
||||
#### 模式和函数
|
||||
|
||||
下面的makefile使用了变量、模式和函数来实现所有C代码的编译。我们来逐行分析下:
|
||||
```
|
||||
# Usage:
|
||||
# make # compile all binary
|
||||
# make clean # remove ALL binaries and objects
|
||||
|
||||
.PHONY = all clean
|
||||
|
||||
CC = gcc # compiler to use
|
||||
|
||||
LINKERFLAG = -lm
|
||||
|
||||
SRCS := $(wildcard *.c)
|
||||
BINS := $(SRCS:%.c=%)
|
||||
|
||||
all: ${BINS}
|
||||
|
||||
%: %.o
|
||||
@echo "Checking.."
|
||||
${CC} ${LINKERFLAG} $< -o $@
|
||||
|
||||
%.o: %.c
|
||||
@echo "Creating object.."
|
||||
${CC} -c $<
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm -rvf *.o ${BINS}
|
||||
```
|
||||
|
||||
* 以“#”开头的行是评论。
|
||||
|
||||
* `.PHONY = all clean` 定义了“all”和“clean”两个伪代码。
|
||||
|
||||
* 变量`LINKERFLAG` recipe中gcc命令需要用到的参数。
|
||||
|
||||
* `SRCS := $(wildcard *.c)`: `$(wildcard pattern)` 是与文件名相关的一个函数。在本示例中,所有“.c"后缀的文件会被存入“SRCS”变量。
|
||||
|
||||
* `BINS := $(SRCS:%.c=%)`: 这被称为替代引用。本例中,如果“SRCS”的值为“'foo.c bar.c'”,则“BINS”的值为“'foo bar'”。
|
||||
|
||||
* Line `all: ${BINS}`: 伪target “all”调用“${BINS}”变量中的所有值作为子target。
|
||||
|
||||
* 规则:
|
||||
```
|
||||
%: %.o
|
||||
@echo "Checking.."
|
||||
${CC} ${LINKERFLAG} $< -o $@
|
||||
```
|
||||
|
||||
下面通过一个示例来理解这条规则。假定“foo”是变量“${BINS}”中的一个值。“%”会匹配到“foo”(“%”匹配任意一个target)。下面是规则展开后的内容:
|
||||
```
|
||||
foo: foo.o
|
||||
@echo "Checking.."
|
||||
gcc -lm foo.o -o foo
|
||||
|
||||
```
|
||||
|
||||
如上所示,“%”被“foo”替换掉了。“$<”被“foo.o”替换掉。“$<”用于匹配预置条件,`$@`匹配target。对“${BINS}”中的每个值,这条规则都会被调用一遍。
|
||||
|
||||
* 规则:
|
||||
```
|
||||
%.o: %.c
|
||||
@echo "Creating object.."
|
||||
${CC} -c $<
|
||||
```
|
||||
|
||||
之前规则中的每个预置条件在这条规则中都会都被作为一个target。下面是展开后的内容:
|
||||
```
|
||||
foo.o: foo.c
|
||||
@echo "Creating object.."
|
||||
gcc -c foo.c
|
||||
```
|
||||
|
||||
* 最后,在target “clean”中,所有的而简直文件和编译文件将被删除。
|
||||
|
||||
|
||||
|
||||
|
||||
下面是重写后的makefile,该文件应该被放置在一个有foo.c文件的目录下:
|
||||
```
|
||||
# Usage:
|
||||
# make # compile all binary
|
||||
# make clean # remove ALL binaries and objects
|
||||
|
||||
.PHONY = all clean
|
||||
|
||||
CC = gcc # compiler to use
|
||||
|
||||
LINKERFLAG = -lm
|
||||
|
||||
SRCS := foo.c
|
||||
BINS := foo
|
||||
|
||||
all: foo
|
||||
|
||||
foo: foo.o
|
||||
@echo "Checking.."
|
||||
gcc -lm foo.o -o foo
|
||||
|
||||
foo.o: foo.c
|
||||
@echo "Creating object.."
|
||||
gcc -c foo.c
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up..."
|
||||
rm -rvf foo.o foo
|
||||
```
|
||||
|
||||
关于makefiles的更多信息,[GNU Make manual][1]提供了更完整的说明和实例。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/what-how-makefile
|
||||
|
||||
作者:[Sachin Patil][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[Zafiry](https://github.com/zafiry)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/psachin
|
||||
[1]:https://www.gnu.org/software/make/manual/make.pdf
|
||||
124
translated/tech/20180827 An introduction to diffs and patches.md
Normal file
124
translated/tech/20180827 An introduction to diffs and patches.md
Normal file
@@ -0,0 +1,124 @@
|
||||
差异文件(diffs)和补丁文件(patches)简介
|
||||
======
|
||||

|
||||
|
||||
如果你曾有机会在一个使用分布式开发模型的大型代码库上工作过,你就应该听说过类似下面的话,"Sue刚发过来一个补丁","Rajiv 正在签出差异文件", 可能这些词(补丁,差异文件)对你而言很陌生,而你确定很想搞懂他们到底指什么。开源软件对上述提到的名词有很大的贡献,作为从开发 Apache web 服务器到开发Linux 内核的开发模型,"基于补丁文件的开发" 这一模式贯穿了上述项目的始终。实际上,你可能不知道 Apache 的名字就来自一系列的代码补丁,他们被一一收集起来并针对原来的[NCSA HTTPd server source code][1]进行了校对
|
||||
|
||||
你可能认为前面说的只不过是些逸闻,但是一份早期的[capture of the Apache website][2]声称Apache 的名字就是来自于最早的“补丁文件”集合;(译注:Apache 英文音和补丁相似),是“打了补丁的”服务器的英文名字简化。
|
||||
|
||||
好了,言归正传,程序员嘴里说的"差异"和"补丁" 到底是什么?
|
||||
|
||||
首先,在这篇文章里,我们可以认为这两个术语都指向同一个概念。“差异” 就是”补丁“。Unix 下的同名工具程序diff("差异")和patch("补丁")剖析了一个或多个文件之间的”差异”。下面我们看看diff 的例子:
|
||||
|
||||
一个"补丁"指的是文件之间一系列差异,这些差异能被 Unix 的 diff程序应用在源代码树上,使之转变为程序员想要的文件状态。我们能使用diff 工具来创建“差异”( 或“补丁”),然后将他们“打” 在一个没有这个补丁的源代码版本上,此外,(我又要开始跑题了...),“补丁” 这个词真的指在计算机的早期使用打卡机的时候,用来覆盖在纸带上的用于修复代码错误的覆盖纸,那个时代纸带(上面有孔)就是在计算机处理器上运行的程序。下面的这张图,来自[Wikipedia Page][3] 真切的描绘了最初的“ 打补丁”这个词的出处:
|
||||
|
||||
|
||||

|
||||
|
||||
现在你对补丁和差异就了一个基本的概念,让我们来看看软件开发者是怎么使用这些工具的。如果你还没有使用过类似于[Git][4]这样的源代码版本控制工具的话,我将会一步步展示最流行的软件项目是怎么使用它们的。如果你将一个软件的生命周期看成是一条时间线的话,你就能看见这个软件的点滴变化,比如在何时源代码树加上了一个功能,在何时源代码树修复了一个功能缺陷。我们称这些改变的点为“进行了一次提交”,”提交“这个词被当今最流行的源代码版本管理工具Git使用, 当你想检查在一个提交前后的代码变化的话,(或者在许多个提交之间的代码变化),你都可以使用工具来观察文件差异。
|
||||
|
||||
如果你在使用 Git 开发软件的话,你开发环境本地有可能就有你想交给别的开发者的提交,为了给别的开发者你的提交,一个方法就是创建一个你本地文件的差异文件,然后将这个“补丁”发送给和你工作在同一个源代码树的别的开发者。别的开发者在“打”了你的补丁之后,就能看到在你的代码变树上的变化。
|
||||
|
||||
|
||||
### Linux, Git, 和 GitHub
|
||||
|
||||
这种共享补丁的开发模型正是现今 Linux 内核社区如何处理内核修改提议而采用的模型。如果你有机会浏览任何一个主流的 Linux 内核邮件列表-主要是[LKML][6],包括[linux-containers][7],[fs-devel][8],[Netdev][9]等等,你能看到很多开发者会贴出他们想让其他内核开发者审核,测试或者合入Linux官方Git代码树某个提交的补丁。当然,讨论 Git 不在这篇文章范围之内(Git 是由 Linus Torvalds 开发的源代码控制系统,它支持分布式开发模型以及允许独立于主要代码仓库的补丁包,这些补丁包能被推送或拉取到不同的源代码树上并遵守这些代码树各自的开发流程。)
|
||||
|
||||
在继续我们的话题之前,我们当然不能忽略和补丁和差异这个概念最相关的的服务:[GitHub][10]。从它的名字就能猜想出 GitHub 是基于 Git 的,而且它还围绕着 Git对分布式开源代码开发模型提供了基于Web 和 API 的工作流管理。(译注:即Pull Request -- 拉取请求)。在 GitHub 上,分享补丁的方式不是像 Linux 内核社区那样通过邮件列表,而是通过创建一个 **拉取请求** 。当你提交你自己源代码的改动时,你能通过创建一个针对软件项目的主仓库的“拉取请求”来分享你的代码改动(译注:即核心开发者维护一个主仓库,开发者去“fork”这个仓库,待各自的提交后再创建针对这个主仓库的拉取请求,所有的拉取请求由主仓库的核心开发者批准后才能合入主代码库。)GitHub 被当今很多活跃的开源社区所采用,如[Kubernetes][11],[Docker][12],[the Container Network Interface (CNI)][13],[Istio][14]等等。在 GitHub 的世界里,用户会倾向于使用基于 Web 页面的方式来审核一个拉取请求里的补丁或差异,你也可以直接访问原始的补丁并在命令行上直接使用它们。
|
||||
|
||||
|
||||
|
||||
### 该说点干货了
|
||||
|
||||
我们前面已经讲了在流行的开源社区了是怎么应用补丁和 diff的,现在看看一些例子。
|
||||
|
||||
第一个例子包括一个源代码树的两个不同拷贝,其中一个有代码改动,我们想用 diff来看看这些改动是什么。这个例子里,我们想看的是“合并格式”的补丁,这是现在软件开发世界里最通用的格式。如果想知道更详细参数的用法以及如何生成diff,请参考diff手册。原始的代码在sources-orig目录 而改动后的代码在sources-fixed目录. 如果要在你的命令行上用“合并格式”来展示补丁,请运行如下命令。(译注: 参数 N 代表如果比较的文件不存在,则认为是个空文件, a代表将所有文件都作为文本文件对待,u 代表使用合并格式并输出上下文,r 代表递归比较目录)
|
||||
|
||||
|
||||
```
|
||||
$ diff -Naur sources-orig/ sources-fixed/
|
||||
```
|
||||
|
||||
...下面是 diff命令的输出:
|
||||
|
||||
```
|
||||
diff -Naur sources-orig/officespace/interest.go sources-fixed/officespace/interest.go
|
||||
--- sources-orig/officespace/interest.go 2018-08-10 16:39:11.000000000 -0400
|
||||
+++ sources-fixed/officespace/interest.go 2018-08-10 16:39:40.000000000 -0400
|
||||
@@ -11,15 +11,13 @@
|
||||
InterestRate float64
|
||||
}
|
||||
|
||||
+// compute the rounded interest for a transaction
|
||||
func computeInterest(acct *Account, t Transaction) float64 {
|
||||
|
||||
interest := t.Amount * t.InterestRate
|
||||
roundedInterest := math.Floor(interest*100) / 100.0
|
||||
remainingInterest := interest - roundedInterest
|
||||
|
||||
- // a little extra..
|
||||
- remainingInterest *= 1000
|
||||
-
|
||||
// Save the remaining interest into an account we control:
|
||||
acct.Balance = acct.Balance + remainingInterest
|
||||
```
|
||||
最开始几行 diff的输出可以这样解释:三个‘---’显示了原来文件的名字;任何在原文件(译注:不是源文件)里存在而在新文件里不存在的行将会用前缀‘-’,用来表示这些行被从源代码里‘减去’了。而‘+++’表示的则相反:在新文件里被加上的行会被放上前缀‘+’,表示这是在新文件里被'加上'的行。每一个补丁”块“(用@@作为前缀的的部分)都有上下文的行号,这能帮助补丁工具(或其他处理器)知道在代码的哪里应用这个补丁块。你能看到我们已经修改了”办公室“这部电影里提到的那个函数(移除了三行并加上了一行代码注释),电影里那个有点贪心的工程师可是偷偷的在计算利息的函数里加了点”料“哦。( LCTT译注:剧情详情请见电影 https://movie.douban.com/subject/1296424/)
|
||||
|
||||
|
||||
如果你想找人来测试你的代码改动,你可以将差异保存到一个补丁里:
|
||||
|
||||
```
|
||||
$ diff -Naur sources-orig/ sources-fixed/ >myfixes.patch
|
||||
```
|
||||
|
||||
现在你有补丁 myfixes.patch了,你能把它分享给别的开发者,他们可以将这个补丁打在他们自己的源代码树上从而得到和你一样的代码并测试他们。如果一个开发者的当前工作目录就是他的源代码树的根的话,他可以用下面的命令来打补丁:
|
||||
|
||||
|
||||
```
|
||||
$ patch -p1 < ../myfixes.patch
|
||||
patching file officespace/interest.go
|
||||
```
|
||||
现在这个开发者的源代码树已经打好补丁并准备好构建和测试文件的修改了。那么如果这个开发者在打补丁之前已经改动过了怎么办?只要这些改动没有直接冲突(译注:比如改在同一行上),补丁工具就能自动的合并代码的改动。例如下面的interest.go 文件,他有其他几处改动,然后它想打上myfixes.patch 这个补丁:
|
||||
|
||||
|
||||
```
|
||||
$ patch -p1 < ../myfixes.patch
|
||||
patching file officespace/interest.go
|
||||
Hunk #1 succeeded at 26 (offset 15 lines).
|
||||
```
|
||||
在这个例子中,补丁警告说代码改动并不在文件原来的地方而是偏移了15行。如果你文件改动的很厉害,补丁可能干脆说找不到要应用的地方,还好补丁程序提供了提供了打开”模糊“匹配的选项(这个选项在文档里有预置的警告信息,对其讲解已经超出了本文的范围)
|
||||
|
||||
如果你使用 Git 或者 GitHub 的话,你可能不会直接使用diff或patch。Git 已经内置了这些功能,你能使用这些功能和共享一个源代码树的其他开发者交互,拉取或合并代码。Git一个比较相近的功能是可以使用 git diff 来对你的本地代码树生成全局差异,又或者对你的任意两次”引用“(可能是一个代表提交的数字,或一个标记或分支的名字,等等)做全局补丁。你甚至能简单的用管道将 git diff的输出到一个文件里(这个文件必须严格符合将要被使用它的程序的输入要求),然后将这个文件交给一个并不使用 Git 的开发者应用到他的代码上。当然,GitHub 把这些功能放到了 Web 上,你能直接在 Web 页面上查看一个拉取请求的文件变动。在Web 上你能看到所展示的合并差异,GitHub 还允许你将这些代码改动下载为原始的补丁文件。
|
||||
|
||||
|
||||
### 总结
|
||||
|
||||
好了,你已经学到了”差异“和”补丁“是什么,以及在 Unix/Linux 上怎么使用命令行工具和他们交互。除非你还在像 Linux 内核开发这样的项目中工作而使用完全基于补丁的开发方式,你应该会主要通过你的源代码控制系统(如Git)来使用补丁。但熟悉像 GitHub 这样的高级别工具的技术背景和技术底层对你的工作也是大有裨益的。谁知道会不会有一天你需要和一个来自 Linux 世界邮件列表的补丁包打交道呢?
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/diffs-patches
|
||||
|
||||
作者:[Phil Estes][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[David Chen](https://github.com/DavidChenLiang)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://opensource.com/users/estesp
|
||||
[1]:https://github.com/TooDumbForAName/ncsa-httpd
|
||||
[2]:https://web.archive.org/web/19970615081902/http:/www.apache.org/info.html
|
||||
[3]:https://en.wikipedia.org/wiki/Patch_(computing)
|
||||
[4]:https://git-scm.com/
|
||||
[5]:https://subversion.apache.org/
|
||||
[6]:https://lkml.org/
|
||||
[7]:https://lists.linuxfoundation.org/pipermail/containers/
|
||||
[8]:https://patchwork.kernel.org/project/linux-fsdevel/list/
|
||||
[9]:https://www.spinics.net/lists/netdev/
|
||||
[10]:https://github.com/
|
||||
[11]:https://kubernetes.io/
|
||||
[12]:https://www.docker.com/
|
||||
[13]:https://github.com/containernetworking/cni
|
||||
[14]:https://istio.io/
|
||||
@@ -0,0 +1,137 @@
|
||||
heguangzhi Translating
|
||||
|
||||
6个开源工具制作自己的VPN
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
如果您想尝试建立您自己的 VPN,但是不确定从哪里开始,那么您来对地方了。我将挑选6个最好的免费和开源工具在您自己的服务器上搭建和使用 VPN。这些 VPN 软件不管您是想为您的企业建立站点到站点的,还是仅创建远程代理访问以解除访问限制,并隐藏来自ISP的互联网流量都可以得到解决。
|
||||
|
||||
根据您的需求和条件,并参考您自己的技术特长,环境以及您想要通过 VPN 实现的目标。需要考虑以下因素:
|
||||
|
||||
* VPN 协议
|
||||
* 客户端的数量和设备类型
|
||||
* 服务端的兼容性
|
||||
* 技术专业的能力
|
||||
|
||||
### Algo
|
||||
|
||||
|
||||
[Algo][1] 是从下往上的设计的,为需要互联网安全代理的企业创建 VPN 专用网。它“只包括您需要的最小化的软件”,这意味着您为了简单而牺牲了可扩展性。Algo 是基于 StrongSwan 的,但是删除了所有您不需要的东西,这有另外一个好处,那就是删除了新手可能不会注意到的安全漏洞。
|
||||
|
||||
|
||||
作为额外的奖励,它甚至屏蔽了广告!
|
||||
|
||||
Algo supports only the IKEv2 protocol and Wireguard. Because IKEv2 support is built into most devices these days, it doesn’t require a client app like OpenVPN. Algo can be deployed using Ansible on Ubuntu (the preferred option), Windows, RedHat, CentOS, and FreeBSD. Setup is automated using Ansible, which configures the server based on your answers to a short set of questions. It’s also very easy to tear down and re-deploy on demand.
|
||||
|
||||
|
||||
Algo 只支持 IKEv2 协议和 Wireguard 。因为 IKEv2 支持现在已经内置在大多数设备中,所以它不需要像 OpenVPN 这样的客户端应用程序。Algo 可以使用 Ansible 在 Ubuntu (首选选项)、Windows、RedHat、CentOS 和 FreeBSD 上部署。自动化的安装 Ansible,它根据您对一组简短问题的回答来配置服务。终止和重新部署也非常容易。
|
||||
|
||||
|
||||
Algo 可能是在本篇文章中安装和部署的最简单和最快的VPN。它非常简洁,考虑周全。如果您不需要其他工具提供的任何更高级的功能,只需要一个安全的代理,这是一个很好的选择。请注意,Algo 明确表示,它不是为了解除地理封锁或逃避审查,主要是为了加密。
|
||||
|
||||
### Streisand
|
||||
|
||||
|
||||
|
||||
[Streisand][2] 可以使用一个命令安装在任何 Ubuntu 16.04 服务器上;这个过程大约需要10分钟。它支持 L2TP、OpenConnect、OpenSSH、OpenVPN、Shadowsocks、Stunnel、Tor bridge 和 WireGuard。根据您选择的协议,您可能需要安装客户端应用程序。
|
||||
|
||||
|
||||
在很多方面,Streisand 与 Algo 相似,但是它提供了更多的协议和定制。这需要更多的工作来管理和维护,但也更加灵活。注意 Streisand 不支持 IKEv2 。我认为 Streisand 在中国和土耳其这样的地方绕过审查制度更有效,因为它的多功能性,但是 Algo 更容易和更快地安装。
|
||||
|
||||
|
||||
使用 Ansible 可以自动化安装,所以不需要太多的专业技术知识。通过向用户发送自定义生成的连接指令,包括服务器 SSL 证书的嵌入副本,可以轻松添加更多用户。
|
||||
|
||||
|
||||
卸载 Streisand 是一个快速无痛的过程,您可以按需重新部署。
|
||||
|
||||
### OpenVPN
|
||||
|
||||
|
||||
[OpenVPN][3] 要求客户端和服务器应用程序使用同名协议建立 VPN 连接。OpenVPN 可以根据您的需求进行调整和定制,但它也需要更多专业技术知识。支持远程访问和站点到站点配置;如果您计划使 VPN 作为互联网的代理,前者是您需要的。因为客户端应用程序需要在大多数设备上使用 OpenVPN ,最终用户必须保持更新。
|
||||
|
||||
|
||||
在服务器端,您可以选择部署在云中或 Linux 服务器上。兼容的发行版包括 CentOS 、Ubuntu 、Debian 和 openSUSE。Windows 、MacOS 、iOS 和 Android 都有客户端应用程序,其他设备也有非官方应用程序。企业可以选择设置一个 OpenVPN 接入服务器,但是对于想要社区版的个人来说,这可能太过分了。
|
||||
|
||||
|
||||
OpenVPN 相对容易配置静态密钥加密,但并不完全安全。相反,我建议使用 [easy-rsa][4] 来设置它,这是一个密钥管理包,可以用来设置公钥基础设施。这允许您一次连接多个设备,并以完美的前向保密和其他好处来保护它们。OpenVPN 使用 SSL/TLS 进行加密,您可以在配置中指定 DNS 服务器。
|
||||
|
||||
|
||||
OpenVPN 可以穿越防火墙和 NAT 防火墙,这意味着您可以使用它绕过网关和防火墙,否则它们可能会阻止连接。它同时支持 TCP 和 UDP 传输。
|
||||
|
||||
### StrongSwan
|
||||
|
||||
您可能会遇到一些不同的 VPN 工具,名称中有“Swan”。FreeS/WAN, 、OpenSwan、LibreSwan和[strongSwan][5] 都是同一个项目的分叉,后者是我个人最喜欢的。服务器端,strongSwan 运行在 Linux 2.6、3.x和4x内核、Android、FreeBSD、macOS、iOS 和 Windows上。
|
||||
|
||||
|
||||
StrongSwan 使用 IKEv2 协议和 IPSec 。与 OpenVPN 相比,IKEv2 连接速度更快,同时提供了很好的速度和安全性。如果您更喜欢不需要在客户端安装额外应用程序的协议,这将非常有用,因为现在生产的大多数新设备都支持 IKEv2,,包括 Windows、MacOS、iOS和Android。
|
||||
|
||||
StrongSwan 并不特别容易使用,尽管文档不错,但它使用的词汇与大多数其他工具不同,这可能会让人比较困惑。它的模块化设计让它对企业来说很棒,但这也意味着它不是最精简。这当然不像 Algo 或Streisand 那么简单。
|
||||
|
||||
|
||||
访问控制可以基于使用X.509 属性证书的组成员身份,这是 strongSwan 独有的功能。它支持用于集成到其他环境(如Windows Active Directory )中的EAP身份验证方法。strongSwan可以穿越NAT 网络防火墙。
|
||||
|
||||
### SoftEther
|
||||
|
||||
|
||||
[SoftEther][6] 是由日本筑波大学的一名研究生发起的一个项目。SoftEther VPN 服务器和 VPN网桥在 Windows、Linux、OSX、FreeBSD 和 Solaris 上运行,而客户端应用程序在Windows、Linux和 MacOS 上运行。VPN 网桥主要用于需要设置站点到站点VPN的企业,因此单个用户只需要服务器和客户端程序来设置远程访问。
|
||||
|
||||
|
||||
SoftEther 支持 OpenVPN、L2TP、SSTP 和 EtherIP 协议,由于“基于HTTPS的以太网”伪装,它自己的 SoftEther 协议声称能够免疫深度数据包检测。SoftEther 还做了一些调整,以减少延迟并增加吞吐量。此外,SoftEther 还包括一个克隆功能,允许您轻松地从 OpenVPN 过渡到SoftEther。
|
||||
|
||||
SoftEther 可以穿透 NAT 防火墙并绕过防火墙。在只允许 ICMP 和 DNS 数据包的受限网络上,您可以通过 ICMP 利用SoftEther的VPN 或者通过 DNS 利用 VPN 选项来穿透防火墙。SoftEther 可与IPv4 和IPv6 一起工作。
|
||||
|
||||
|
||||
SoftEther 比 OpenVPN 和strongSwan更容易设置,但比 Streisand 和 Algo 更复杂。
|
||||
|
||||
### WireGuard
|
||||
|
||||
|
||||
[WireGuard][7] 是这个名单上最新的工具;它太新了,甚至还没有完成。也就是说,它为部署VPN提供了一种快速简便的方法。它旨在通过使 IPSec 更简单、更精简来改进它,就像SSH一样。
|
||||
|
||||
与OpenVPN一样,WireGuard 既是一种协议,也是一种软件工具,用于部署使用所述协议的VPN。一个关键特性是“加密密钥路由”,它将公钥与隧道内允许的 IP 地址列表相关联。
|
||||
|
||||
|
||||
WireGuard可用于 Ubuntu、Debian、Fedora、CentOS、MacOS、Windows 和安卓系统。WireGuard可在 IPv4和 IPv6 上工作。
|
||||
|
||||
WireGuard比大多数其他VPN协议轻得多,它只在需要发送数据时才发送数据包。
|
||||
|
||||
|
||||
开发人员说,WireGuard还不应该被信任,因为它还没有被完全审计过,但是欢迎你给它一个机会。这可能是下一件大事!
|
||||
|
||||
### 自制 VPN vs. 商业 VPN
|
||||
|
||||
制作您自己的 VPN 为您的互联网连接增加了一层隐私和安全,但是如果您是唯一一个使用它的人,那么装备精良的第三方,比如政府机构,将很容易追踪到你的活动。
|
||||
|
||||
此外,如果您计划使用您的 VPN 来解锁地理锁定的内容,自制的VPN可能不是最好的选择。因为您只能从一个IP地址连接,所以你的 VPN 服务器很容易被阻止。
|
||||
|
||||
|
||||
好的商业 VPN 不存在这些问题。有了像[ExpressVPN][8]这样的提供商,您可以与数十甚至数百个其他用户共享服务器的IP地址,这使得跟踪一个用户的活动几乎变得不可能。您也可以从成百上千的服务器中选择,所以如果其中一台被列入黑名单,你可以切换到另一台。
|
||||
|
||||
|
||||
然而,商业VPN的权衡是,您必须相信提供商不会窥探您的互联网流量。一定要选择一个有明确的无日志政策的信誉良好的供应商。
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/8/open-source-tools-vpn
|
||||
|
||||
作者:[Paul Bischoff][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:
|
||||
[1]: https://blog.trailofbits.com/2016/12/12/meet-algo-the-vpn-that-works/
|
||||
[2]: https://github.com/StreisandEffect/streisand
|
||||
[3]: https://openvpn.net/
|
||||
[4]: https://github.com/OpenVPN/easy-rsa
|
||||
[5]: https://www.strongswan.org/
|
||||
[6]: https://www.softether.org/
|
||||
[7]: https://www.wireguard.com/
|
||||
[8]: https://www.comparitech.com/vpn/reviews/expressvpn/
|
||||
@@ -0,0 +1,66 @@
|
||||
8 个用于<ruby>业余项目<rt>side projects</rt></ruby>的优秀 Python 库
|
||||
======
|
||||
|
||||

|
||||
|
||||
在 Python/Django 的世界里有这样一个谚语:为语言而来,为社区而留。对绝大多数人来说的确是这样的,但是,还有一件事情使得我们一直停留在 Python 的世界里,不愿离开,那就是我们可以很容易地利用一顿午餐或晚上几个小时的时间,把一个想法快速地实现出来。
|
||||
|
||||
这个月,我们来探讨一些我们喜欢用来快速完成<ruby>业余项目<rt>side projects</rt></ruby>或打发午餐时间的 Python 库。
|
||||
|
||||
### 在数据库中即时保存数据:Dataset
|
||||
|
||||
当我们想要在不知道最终数据库表长什么样的情况下,快速收集数据并保存到数据库中的时候,[Dataset][1] 库将是我们的最佳选择。Dataset 库有一个简单但功能强大的 API,因此我们可以很容易的把数据保存下来,之后再进行排序。
|
||||
|
||||
Dataset 建立在 SQLAlchemy 之上,所以如果需要对它进行扩展,你会感到非常熟悉。使用 Django 内建的 [inspectdb][2] 管理命令可以很容易地把底层数据库模型导入 Django 中,这使得和现有数据库一同工作不会出现任何障碍。
|
||||
|
||||
### 从网页抓取数据:Beautiful Soup
|
||||
|
||||
[Beautiful Soup][3](一般写作 BS4)库使得从 HTML 网页中提取信息变得非常简单。当我们需要把非结构化或弱结构化的 HTML 转换为结构化数据的时候,就需要使用 Beautiful Soup 。用它来处理 XML 数据也是一个很好的选择,否则 XML 的可读性或许会很差。
|
||||
|
||||
### 和 HTTP 内容打交道:Requests
|
||||
|
||||
当需要和 HTTP 内容打交道的时候,[Requests][4] 毫无疑问是最好的标准库。当我们想要抓取 HTML 网页或连接 API 的时候,都离不开 Requests 库。同时,它也有很好的文档。
|
||||
|
||||
### 编写命令行工具:Click
|
||||
|
||||
当需要写一个简单的 Python 脚本作为命令行工具的时候,[Click][5] 是我最喜欢用的库。它的 API 非常直观,并且在实现时经过了深思熟虑,我们只需要记住很少的几个模式。它的文档也很优秀,这使得学习其高级特性更加容易。
|
||||
|
||||
### 对事物命名:Python Slugify
|
||||
|
||||
众所周知,命名是一件困难的事情。[Python Slugify][6] 是一个非常有用的库,它可以把一个标题或描述转成一个带有特性的唯一标识符。如果你正在做一个 Web 项目,并且你想要使用对<ruby>搜索引擎优化友好<rt>SEO-friendly</rt></ruby>的链接,那么,使用 Python Slugify 可以让这件事变得很容易。
|
||||
|
||||
### 和插件打交道:Pluggy
|
||||
|
||||
[Pluggy][7] 库相对较新,但是如果你想添加一个插件系统到现有应用中,那么使用 Pluggy 是最好也是最简单的方式。如果你使用过 pytest,那么实际上相当于已经使用过 Pluggy 了,虽然你还不知道它。
|
||||
|
||||
### 把 CSV 文件转换到 API 中:DataSette
|
||||
|
||||
[DataSette][8] 是一个神奇的工具,它可以很容易地把 CSV 文件转换进全特性只读 REST JSON API,同时,不要把它和 Dataset 库混淆。Datasette 有许多特性,包括创建图表和 geo(用于创建交互式图表),并且很容易通过容器或第三方网络主机进行部署。
|
||||
|
||||
### 处理环境变量等:Envparse
|
||||
|
||||
如果你不想在源代码中保存 API 密钥、数据库凭证或其他敏感信息,那么你便需要解析环境变量,这时候 [envparse][9] 是最好的选择。Envparse 能够处理环境变量、ENV 文件、变量类型,甚至还可以进行预处理和后处理(例如,你想要确保变量名总是大写或小写的)
|
||||
|
||||
有什么你最喜欢的用于<ruby>业余项目<rt>side projects</rt></ruby>的 Python 库不在这个列表中吗?请在评论中和我们分享。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/18/9/python-libraries-side-projects
|
||||
|
||||
作者:[Jeff Triplett][a]
|
||||
选题:[lujun9972](https://github.com/lujun9972)
|
||||
译者:[ucasFL](https://github.com/ucasFL)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/laceynwilliams
|
||||
[1]: https://dataset.readthedocs.io/en/latest/
|
||||
[2]: https://docs.djangoproject.com/en/2.1/ref/django-admin/#django-admin-inspectdb
|
||||
[3]: https://www.crummy.com/software/BeautifulSoup/
|
||||
[4]: http://docs.python-requests.org/
|
||||
[5]: http://click.pocoo.org/5/
|
||||
[6]: https://github.com/un33k/python-slugify
|
||||
[7]: https://pluggy.readthedocs.io/en/latest/
|
||||
[8]: https://github.com/simonw/datasette
|
||||
[9]: https://github.com/rconradharris/envparse
|
||||
Reference in New Issue
Block a user